commit 54a842f4abc7fecf1fffefda9293c41556799adc Author: zephyr Date: Mon Jun 1 21:23:12 2026 -0700 看板初始化提交 diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..f28b0ca --- /dev/null +++ b/.htaccess @@ -0,0 +1,34 @@ +# Pass HTTP Authorization header via environment variable to PHP backend +# to make HTTP Basic Authentication work for Apache/FastCGI/php-fpm +# setups (required to authenticate over the API) + + SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0 + + + + Options -MultiViews + + + + SetEnv HTTP_MOD_REWRITE On + + + + # Uncomment this line depending of your Apache configuration + # RewriteBase / + + RewriteEngine On + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [QSA,L] + + ############################ + ## Uncomment the two lines below to enable force HTTPS capabilities + ############################ + + # RewriteCond %{HTTPS} !=on + # RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R,L] + + + + ModPagespeed Off + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..1bc62ca --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,140 @@ +# Contributing to Kanboard + +Kanboard is a free and open source Kanban project management software that welcomes contributions from the community. + +## Project Status + +**Important Note**: Kanboard is currently in maintenance mode. This means: + +- The original author is not actively developing major new features +- New releases are published regularly based on community contributions +- Pull requests for bug fixes and small improvements are welcomed +- The project follows established guidelines for all contributions + +## Ways to Contribute + +### 🐛 Bug Reports + +If you find a bug, please help us improve Kanboard by reporting it: + +1. **Check existing issues** first to avoid duplicates +2. **Use the GitHub issue tracker** to report bugs +3. **Provide detailed information** including: + - Kanboard version + - PHP version + - Web server (Apache, Nginx, etc.) + - Database type and version + - Operating system + - Steps to reproduce the issue + - Expected vs actual behavior + - Screenshots, server and browser logs if applicable + +### 🔧 Bug Fixes and Small Improvements + +We welcome pull requests that fix bugs or make small improvements: + +1. **Fork the repository** and create a new branch +2. **Keep changes focused** - one issue per pull request +3. **Test your changes** thoroughly +4. **Follow the existing code style** +5. **Submit a pull request** with a clear description + +### 📚 Documentation + +Help improve Kanboard's documentation: + +- Fix typos or unclear explanations +- Add missing documentation for features +- Translate documentation to other languages +- Improve code comments + +### 🌐 Translations + +Kanboard supports multiple languages. Help translate the interface: + +1. Check the `app/Locale` directory for existing translations +2. Create or update translation files +3. Follow the existing translation format +4. Test your translations in the application + +Refer to the [Translation Guide](https://docs.kanboard.org/v1/dev/translations/) for more details. + +## Development Setup + +### Prerequisites + +- PHP 8.1 or higher +- Web server (Apache, Nginx, or PHP built-in server) +- Database (MySQL, PostgreSQL, or SQLite) +- Composer (for dependency management) + +### Local Development + +1. **Clone the repository**: + ```bash + git clone https://github.com/kanboard/kanboard.git + cd kanboard + ``` + +2. **Install dependencies**: + ```bash + composer install + ``` + +3. **Set up your environment**: + - Copy `config.default.php` to `config.php` + - Configure your database settings + - Set up your web server to point to the project directory, or use the PHP built-in server: + ```bash + php -S localhost:8000 -t . + ``` + +4. **Run unit tests** to ensure everything is working: + ```bash + make test-sqlite # or make test-mysql, make test-postgresql + ``` + +### Testing + +- Test your changes in different browsers +- Test with multiple database types (MySQL, PostgreSQL, SQLite) +- Test with different PHP versions if possible +- Ensure existing functionalities are not broken by your changes +- Run the unit tests and integration tests + +## Pull Request Guidelines + +Before submitting a pull request, please: + +1. **Read the pull request template** (`.github/pull_request_template.md`) +2. **Create a focused branch** from `main` for your changes +3. **Write clear commit messages** using the [conventional commit format](https://www.conventionalcommits.org/) +4. **Keep your changes small and focused** - large PRs are harder to review +5. **Test your changes thoroughly** to ensure they work as expected +6. **Ensure your code passes all tests** and does not introduce new issues +7. **Add or update tests** if when appropriate +8. **Review your code for style and quality** before submitting +9. **Update documentation** if needed + +## Code Style Guidelines + +- Be consistent with existing code style +- Follow PSR-1 and PSR-2 coding standards +- Configure your code editor to use 4 spaces for indentation +- Use meaningful variable and function names +- Add comments for complex logic +- Keep functions and methods focused and small +- Use type hints where appropriate + +Refer to [Kanboard's coding standards](https://docs.kanboard.org/v1/dev/coding_standards/) for more details. + +## Resources + +- **Official Website**: +- **Documentation**: +- **Forums**: or +- **GitHub Issues**: + +## License + +By contributing to Kanboard, you agree that your contributions will be licensed under the same [MIT License](LICENSE) that covers the project. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..4fb0861 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,2140 @@ +Version 1.2.52 (April 4, 2026) +------------------------------ + +* fix: revoke public tokens for inactive users +* fix: use timing-safe comparison for token validation +* fix: validate task ownership before applying property changes +* fix: enforce visibility controls for public and unprivileged access +* fix: use parameterized queries in task finder and iCal + +Version 1.2.51 (March 7, 2026) +------------------------------ + +* test: upgrade to phpunit 12 +* fix(css): Fix accessibility issue of insufficient text to background contrast +* fix: validate user external ID column +* fix: restrict invite signup input to expected fields only +* fix: prevent unsafe deserialization in database session handler (CWE-502) +* fix: check file attachment ownership before deletion +* fix: add permission checks in API procedures +* fix: add followRedirects parameter to HTTP client methods to prevent SSRF bypasses +* feat: add SSRF protection for webhook notifications +* build(deps): bump pimple/pimple from 3.6.1 to 3.6.2 +* build(deps): bump docker/setup-qemu-action from 3 to 4 +* build(deps): bump docker/setup-buildx-action from 3 to 4 +* build(deps): bump docker/metadata-action from 5 to 6 +* build(deps): bump docker/login-action from 3 to 4 +* build(deps): bump docker/build-push-action from 6 to 7 +* build(deps): bump actions/upload-artifact from 6 to 7 + +Version 1.2.50 (February 7, 2026) +--------------------------------- + +* test: bump phpunit version +* fix: add missing authorization checks +* fix: enforce plugin installer check in PluginController actions +* fix: enable Parsedown safe mode for Markdown rendering +* fix: add missing project authorization checks in controllers +* fix: add CSRF protection for project role changes and enforce JSON content type +* chore: use php-cs-fixer docker image in GitHub workflow instead of Composer +* build(deps): bump pimple/pimple from 3.5.0 to 3.6.1 + +Version 1.2.49 (January 6, 2026) +-------------------------------- + +* fix(ldap): ensure LDAP placeholders are escaped +* fix: restore Ctrl+Enter submission on the task creation form +* feat(locale): update translations +* feat: prevent protocol-relative URL redirects after login +* feat: block private network access for external web link (configurable) +* feat: add `TRUSTED_PROXY_NETWORKS` config option +* ci: add workflow to mirror GitHub repo to Codeberg +* chore: remove outdated `tests/Dockerfile` +* chore: regenerate autoload Composer files +* build(deps): bump alpine from 3.22 to 3.23 +* build(deps): bump actions/upload-artifact from 4 to 6 +* build(deps): bump actions/checkout from 5 to 6 + +Version 1.2.48 (October 18, 2025) +-------------------------------- + +* fix: handle Windows-style paths in `sanitize_path` function +* feat(locale): added missing German translation phrases +* feat(locale): added Arabic translation +* feat(api): add board, rss and ical public links to the API response +* feat: display sub-tasks completion in numbers (x/y) alongside percentage +* feat: add basic support for right-to-left (RTL) languages +* chore: update .gitattributes to ignore additional configuration files +* build(deps): bump actions/setup-python from 5 to 6 +* build(deps): bump actions/checkout from 4 to 5 + +Version 1.2.47 (August 11, 2025) +-------------------------------- + +* refactor: add namespace to test files +* fix: the `$escape` parameter must be provided in PHP 8.4 for CSV functions +* fix: sanitize and validate uploaded files path +* fix: do not load `RememberMeAuth` provider when `REMEMBER_ME_AUTH` is `false` +* fix: avoid PHP warning when external user creation is disabled +* feat!: remove file cache driver to avoid using `unserialize()` +* feat!: ignore legacy events serialized with PHP due to potential security issues +* feat: add new actions: `TaskAssignCurrentUserColumnIfNoUserAlreadySet` and `TaskAssignToUserOnCreationInColumn` +* feat: Add new `pdf()` method in `Core\Http\Response` +* ci: run `php-cs-fixer` on GitHub Actions +* ci: remove unnecessary labels from issue templates +* chore: replace deprecated `gh-cli` feature source in devcontainer configuration + +Version 1.2.46 (June 22, 2025) +------------------------------ + +* refactor: update return type in filter apply methods +* fix(security): prevent potential `Host` header injection via `SERVER_NAME` + - You must specify the Kanboard application URL explicitly to generate correct URLs from email notifications. The default is `http://localhost/`. +* fix: make various PHP 8.x compatibility changes +* fix: avoid `Implicitly nullable parameter declarations` errors in PHP 8.4 +* feat: validate plugin archive URL before downloading +* feat: use PHP 8.4 in the official Docker image +* feat: show CAPTCHA on login form regardless of user existence +* feat: add new option to enable notifications by default for new users +* feat: add healthcheck endpoint `healthcheck.php`, and new Docker Compose files for MariaDB, Postgres, and SQLite +* feat: add `TRUSTED_PROXY_HEADERS` config option + - If you use a reverse proxy, you can now specify which headers to trust for the client IP address. Nothing is trusted by default. +* docs: add `CONTRIBUTING.md` file +* ci(docker): avoid using `set-output` deprecated command +* chore!: PHP 8.1 is now the minimum version supported + - **!! PHP 7.4 is no longer supported !!** +* chore: update `docker-compose.yml` sample file to the latest specs +* chore: remove obsolete `Vagrantfile` +* build(deps): bump Alpine Docker image from 3.21 to 3.22 + +Version 1.2.45 (May 12, 2025) +----------------------------- + +* refactor: reuse existing helpers in tasks import form +* fix(filter): handle `null` input in the `Lexer` class +* fix(docker): legacy key/value format with whitespace separator should not be used +* fix(api): allow and validate creator ID assignment in task creation +* feat(routes): add `view` routes for project and task file browsing +* feat(locale): update all language files using machine translation +* feat(api): add priority fields to `createProject` and `updateProject` procedures +* feat: allow attaching screenshots and files when creating a task +* feat: add task title to overdue notification title +* ci: replace GitHub Issue Markdown templates with YAML forms +* ci: remove broken SQL Server unit tests pipeline +* ci: improve pull request template +* ci: add commit linter to validate conventional commit messages in pull requests + +Version 1.2.44 (March 21, 2025) +------------------------------- + +* fix: prevent internal task titles from wrapping under the dropdown menu icon +* feat(locale): update Greek and French translations +* feat: display tag color squares next to their names in project and global settings +* feat: enable bulk addition/removal of internal links +* feat: provide an option to add tags without replacing existing ones during bulk operations + +Version 1.2.43 (December 18, 2024) +---------------------------------- + +* fix: verify the session hasn't expired before returning data +* fix: avoid PHP 8.4 deprecation notices in third-party libraries +* fix: avoid Composer warnings regarding PSR compatibility +* feat(locale): add missing Brazilian Portuguese translations +* ci: run GitHub Actions tests with `ubuntu-24.04` +* chore: don't `export-ignore` the ChangeLog +* build(deps): bump `symfony/service-contracts` from `2.5.3` to `2.5.4` +* build(deps): bump `symfony/event-dispatcher-contracts` from `2.5.3` to `2.5.4` +* build(deps): bump `symfony/deprecation-contracts` from `2.5.3` to `2.5.4` +* build(deps): bump `alpine` from `3.20` to `3.21` + +Version 1.2.42 (November 10, 2024) +---------------------------------- + +* fix: validate translation filename before loading locales +* fix: avoid path traversal in `FileStorage` +* feat: add Peruvian Sol to the list of currencies +* build(deps): bump `symfony/finder` from `5.4.43` to `5.4.45` +* build(deps-dev): bump `symfony/stopwatch` from `5.4.40` to `5.4.45` + +Version 1.2.41 (October 25, 2024) +--------------------------------- + +* feat: add new plugin hooks in project forms +* feat: add option to add BOM at the beginning of CSV files (required for Microsoft Excel) +* feat: validate app config form values +* feat: add cancel button on 2FA code validation screen +* fix: add CSRF check to the logout endpoint +* fix: add HTML escaping when displaying exception message +* fix: add URL validation for external task links +* fix: correct broken migration logic for Sqlite + +Version 1.2.40 (September 25, 2024) +----------------------------------- + +* build(deps): bump symfony/finder from 5.4.42 to 5.4.43 +* chore: add php83-xmlwriter package to the Docker image +* ci: update GitHub pull-request template +* fix: avoid PHP error if no subtask in progress is found +* fix: avoid potential XSS and HTML injection in comment replies +* fix: prevent duplicated columns when enabling per-swimlane column task limits +* fix(api): check comment visibility in API procedures +* fix(api): verify comment ownership in API procedures +* fix(mssql): escape identifiers in timesheet queries +* fix(mssql): use ANSI OFFSET/FETCH syntax for pagination queries +* fix(test): use explicit ORDER BY for queries returning multiple rows +* test: add unit tests for Subtask Time Tracking query methods +* test: ensure pagination produces correct chunks + +Version 1.2.39 (August 18, 2024) +-------------------------------- + +* fix: remove CSS which caused responsive issues on mobile +* fix: incorrect template condition that set the username field to read only for remote users +* fix: tasks count across swimlanes was incorrect +* fix: avoid warning from libpng when loading PNG image with incorrect iCCP profiles +* feat: improve column header task counts +* feat: add `apple-mobile-web-app-capable` meta tag +* build(deps): bump `symfony/finder` from `5.4.40` to `5.4.42` + +Version 1.2.38 (July 20, 2024) +------------------------------ + +* fix: avoid browser caching issue when showing file attachments +* fix: comments visibility was not taken into consideration on event activities page +* fix: send comment via email was broken due to missing comment visibility logic implemented in v1.2.36 +* feat(locale): update Greek translations +* feat(locale): update Italian translations +* build(deps): bump `symfony/console` from `5.4.40` to `5.4.41` +* build(deps): bump `docker/build-push-action` from `5` to `6` + +Version 1.2.37 (June 5, 2024) +----------------------------- + +* Add CSRF check and remove `project_id` form value for `addUser` and `addGroup` actions ([CVE-2024-36399](https://github.com/kanboard/kanboard/security/advisories/GHSA-x8v7-3ghx-65cv)) +* Update `symfony/*` dependencies +* Update Docker image to Alpine 3.20 +* Update Russian and Hungarian translation +* Add `color_id` argument to `createCategory` and `updateCategory` API procedures +* Add link to create a comment before the list +* Fix: unable to create comments with "c" shortcut or "Add a comment" menu + +Version 1.2.36 (May 2, 2024) +---------------------------- + +* Add comments visibility +* Add explicit int casting to avoid PHP 8 TypeError when having empty automatic action parameters +* Add new config option `DASHBOARD_MAX_PROJECTS` +* Add reply feature to comments +* Fix search bar layout when adding more buttons via third-party plugins +* Introduce a Git hook to automatically update `version.txt` during Git checkout +* Performance improvements: + * Don't count closed tasks when rendering the board + * Force the use of the cache when there is no custom roles +* Use unique plugin name instead of plugin title for plugin registry logic +* Update dependencies + +Version 1.2.35 (February 2, 2024) +--------------------------------- + +* Add missing HTML escaping when showing group membership in user profile ([CVE-2024-22720](https://github.com/kanboard/kanboard/security/advisories/GHSA-8p3h-v7fc-xppj)) +* Update Dutch translation +* Update Bulgarian translation +* Bump `phpunit/phpunit` from `9.6.15` to `9.6.16` +* Bump `symfony/console` from `5.4.32` to `5.4.34` + +Version 1.2.34 (December 13, 2023) +---------------------------------- + +* Upgrade Docker image to Alpine 3.19 and PHP 8.3 +* API: Avoid PHP notice when searching for a project name that does not exist +* Update Bulgarian translation +* Bump `symfony/console` from `5.4.28` to `5.4.32` +* Bump `phpunit/phpunit` from `9.6.13` to `9.6.15` + +Version 1.2.33 (October 15, 2023) +--------------------------------- + +* Do not close modals when clicking on the background +* Add Bulgarian translation +* Update Ukrainian and Russian translations +* Show the two factor form in the middle of the screen like the login form does +* Do not override the `creator_id` with the current logged user if the task is imported +* Add basic Dev Container configs +* Add adaptive SVG favicon and more SVG variants: + * See https://web.dev/building-an-adaptive-favicon/ + * Added more variant of the original Inkscape icon: + - Text SVG + - Vectorized text path SVG + - Optimized SVG icon +* Remove `project_id` from task links (A few were missed in #4892) +* Remove unused and invalid method in `ProjectModel` +* Update `phpunit/phpunit` and `symfony/*` dependencies +* Update vendor folder + +Version 1.2.32 (July 11, 2023) +------------------------------ + +* Fix unexpected EventDispatcher exception in cronjob and during logout +* Integration Tests: Run `apt update` before installing Apache +* Automatic action `TaskMoveColumnClosed` does not log column movement +* Tweak Sqlite connection settings to reduce database locked errors +* Bump `phpunit/phpunit` from `9.6.9` to `9.6.10` + +Version 1.2.31 (July 3, 2023) +----------------------------- + +Security Fixes: + +- [CVE-2023-36813: Avoid potential SQL injections without breaking compatibility with plugins](https://github.com/kanboard/kanboard/security/advisories/GHSA-9gvq-78jp-jxcx) + +Other fixes and updates: + +- Run tests with PHP 8 on GitHub Actions +- Bump Symfony dependencies +- Update Composer dependencies to be able to run tests with PHP 8.2 +- Add `/usr/bin/php` symlink in the Docker image +- Replace usage of `at()` matcher with alternatives in unit tests +- Adjust plugin directory test case to work on released versions +- Fix incorrect background dynamic property in captcha library +- Update translations + +Version 1.2.30 (June 2, 2023) +----------------------------- + +Security Fixes: + +- [CVE-2023-33956: Parameter based Indirect Object Referencing leading to private file exposure](https://github.com/kanboard/kanboard/security/advisories/GHSA-r36m-44gg-wxg2) +- [CVE-2023-33968: Missing access control allows user to move and duplicate tasks to any project in the software](https://github.com/kanboard/kanboard/security/advisories/GHSA-gf8r-4p6m-v8vr) +- [CVE-2023-33969: Stored XSS in the Task External Link Functionality](https://github.com/kanboard/kanboard/security/advisories/GHSA-8qvf-9847-gpc9) +- [CVE-2023-33970: Missing access control in internal task links feature](https://github.com/kanboard/kanboard/security/advisories/GHSA-wfch-8rhv-v286) + +Other Fixes: + +- Avoid PHP warning caused by `session_regenerate_id()` +- Avoid CSS issue when upgrading to v1.2.29 without flushing user sessions + +Version 1.2.29 (May 23, 2023) +----------------------------- + +* Avoid potential clipboard based cross-site scripting ([CVE-2023-32685](https://github.com/kanboard/kanboard/security/advisories/GHSA-hjmw-gm82-r4gv)) +* Upgrade Docker image to PHP 8.2 and Alpine 3.18 +* Add themes support: dark, light and automatic mode +* Fix broken "Hide this Column" feature +* Do not close modals when clicking on the background if the form has changed +* Fix incorrect route for "My Activity Stream" +* Fix incorrect parameter encoding when using URLs rewriting +* Add support for task links in Markdown headings +* Handle 413 responses from Nginx when uploading files too large +* Restore all previously loaded translations when sending user notifications +* Regenerate session ID after successful authentication +* Use `SESSION_DURATION` option to define the session lifetime stored in the database + - The option `SESSION_DURATION` is used to define the cookie lifetime. + - With this change, Kanboard will try to use first `SESSION_DURATION` instead of the + default `session.gc_maxlifetime` value. +* Bump `phpunit/phpunit` from `9.6.6` to `9.6.8` + +Version 1.2.28 (April 8, 2023) +------------------------------ + +* Trigger `EVENT_MOVE_COLUMN` event when moving task to another swimlane +* Allow moving closed tasks when using the API +* Duplicate external links when duplicating tasks +* Add support for comparison operator to priority filter +* Prevents users to convert subtaks to tasks when custom role does not allow it +* Avoid deprecation messages when sending an email with PHP 8.2 +* Declare most common routes to have nice URLs +* Improve wording of bulk action modal to move tasks position +* Allow closing modals by clicking on the background +* Improve wording of the menu to close all tasks in a given column/swimlane +* Fix bug that prevent reordering subtasks after changing the status +* Bump version of `phpunit/phpunit`, `symfony/stopwatch`, and `symfony/finder` +* Use `GITHUB_TOKEN` instead of a personal token to run GitHub Actions +* Duplicate attachments & external links during task duplication & importing +* Move Docker image to run automated tests to GitHub Registry +* Push Docker images to an additional registry Quay.io (RedHat) +* Use the appropriate config for the start column in user iCal export +* Improved translations + +Version 1.2.27 (March 5, 2023) +------------------------------ + +- Fix category filter when the category name is a number +- Better handling of max file upload size according to PHP settings + - Allow unlimited size + - Better parsing of PHP size +- Add dropdown menu on the board to reorder tasks by ID +- Separate `font-family` specification for input and textarea. This avoids the use of `!important` in custom CSS +- Change the total number of tasks displayed in the table header to match the description "Total number of tasks in this column across all swimlanes" +- Allow full name to be retrieved by the reverse proxy authentication +- Fix `pull-right` CSS class alignment +- Use a separate dropdown menu for column sorting +- Use `assertEqualsWithDelta()` to test `time_spent` +- Add `color_id` argument to tag API procedures +- Update task time spent/estimated when removing a subtask +- Command `db:migrate` should work even if `DB_RUN_MIGRATIONS` is false +- Always trim the username before saving changes in the database +- Avoid Postgres SQL error when using project filter with a large integer +- Enable Sqlite WAL mode by default: + - WAL provides more concurrency as readers do not block writers and, + a writer does not block readers. Reading and writing can proceed concurrently. + This change might reduce the number of errors related to locked databases. +- Update translations +- Update PHP dependencies: `phpunit/phpunit`, `symfony/stopwatch` and `symfony/finder` + +Version 1.2.26 (January 14, 2023) +--------------------------------- + +- Fire events after `TaskMoveColumnOnDueDate` action +- Update date parsing logic to be compatible with PHP 8.2 +- Fix potential XSS on the Settings / API page +- Use wildcard operator for tag filter +- Fix broken user mentions in popup comment form +- Test Docker image build on pull-requests +- Bump Alpine Linux Docker image from 3.16 to 3.17 +- Update translations +- Fixed a bug about unselecting in the file `list-item-selection.js` +- Add functionality to import tasks from a project +- Add missing jQuery UI CSS dependency + +Version 1.2.25 (November 12, 2022) +---------------------------------- + +- Add experimental support for Microsoft SQL Server +- Add Open Container labels to Dockerfile +- Update links to the new documentation website +- Update German translation + +Version 1.2.24 (October 9, 2022) +-------------------------------- + +* Fixed deprecation warnings when a project or a task description is null +* Fixed missing condition in `TaskAssignDueDateOnMoveColumn` action +* Fixed Reopening of dropdown menus +* Fixed internal link creation on subtask to task conversion if language is not English +* Use a HMAC to sign and validate CSRF tokens, instead of generating random ones and storing them in the session data +* Set explicitly the time picker control to select instead of slider +* Bump `phpunit/phpunit` from `9.5.24` to `9.5.25` +* Bump `symfony/stopwatch` from `5.4.5` to `5.4.13` +* Moved `version.txt` to `app` folder +* Updated translations + +Version 1.2.23 (September 4, 2022) +---------------------------------- + +* Open SVG, Ogg, and some video file attachments in browser +* Added more video, music, code and spreadsheet extensions to show better file attachment icons +* Updated jQuery to latest stable version +* Updated Docker image to PHP 8.1 and Alpine Linux 3.16 +* Renamed default branch from `master` to `main` +* Bumped `phpunit/phpunit` from `9.5.14` to `9.5.23` +* Bumped `symfony/finder` from `5.4.3` to `5.4.11` +* Fixed subtask translation when using different languages +* Added Project Overview document template hook +* Updated translations +* Fixed wrong foreign key constraint on table `subtask_time_tracking table`. The constraints references a no-longer-existing table `task_has_subtasks` +* Fixed regression regarding subtask reordering +* Changed minimum requirement to PHP 7.4 + - PHP versions lower than 7.4 are end-of-life: https://www.php.net/supported-versions.php + - Libraries used by Kanboard have dropped support for older versions of PHP + +Version 1.2.22 (February 12, 2022) +---------------------------------- + +* Add support for PHP 8.x (Minimum requirement is now PHP >= 7.4) +* Remove `project_id` from task URLs +* Update `da_DK` translations +* Add automatic action to set the due date when the task is moved away from a specific column +* Condense wording on inferred action and update translations +* Add EVENT_CREATE and EVENT_CREATE_UPDATE events to TaskMoveColumnCategoryChange action + +Version 1.2.21 (December 16, 2021) +---------------------------------- + +* Fix and update Composer autoload +* Add plugin hook for document attachments +* Improve board column header alignment +* Ignore `project_id` for file attachments download URL (already checked elsewhere) +* Update translations +* Clarify meaning of `LDAP_USER_CREATION` in `config.default.php` +* Fix wrong internal link when converting a subtask to task (MySQL only) +* Use the overridable Markdown parser for previews +* Update `call_user_func_array()` calls to be compatible with PHP 8 +* Enable external group synchronization deactivation +* Fix tooltip shifting on long descriptions +* Add `position` argument to API procedure `updateSubtask()` +* Bump Docker image to Alpine 3.15.0 +* Bump `symfony/stopwatch` to 5.4.0 +* Bump `pimple/pimple` to 3.5.0 + +Version 1.2.20 (June 8, 2021) +----------------------------- + +* Duplicate tags when moving or duplicating tasks to another project +* Bump symfony/stopwatch to 5.3.0 +* Avoid user enumeration by using avatar image URL +* Invalidate captcha after it is used +* Avoid user enumeration using password reset functionality +* Add missing CSRF checks +* Fix bug in search when using the plus sign +* Close dialogs using Escape key even if focus is in input field +* Add a min="0" attribute to task_list form input +* Keep swimlane headers at the top +* Catch error when trying to upload empty or invalid avatar image +* Added new template hooks +* Update translations + +Version 1.2.19 (April 16, 2021) +------------------------------- + +* Trim user agent for RememberMe sessions because MySQL use a varchar(255) column +* Update Docker image to Alpine 3.13.4 +* Added "Deutsch (du)" language +* Fixed `createLdapUser` API procedure when LDAP groups are not configured +* Write RememberMe cookie only after the two-factor code has been validated +* Avoid warning when removing a plugin zip archive +* Update Hungarian translation +* Add new hook `model:task:duplication:aftersave` +* Bump symfony/stopwatch from 5.2.3 to 5.2.4 +* Bump pimple/pimple from 3.3.1 to 3.4.0 +* Bump gregwar/captcha from 1.1.8 to 1.1.9 +* Added new analytic component: "Estimated vs actual time per column" +* Do not retain any changes between shared plugins variables +* Display number of tasks according to filter +* Add support for LDAP protocol/host/port configuration by URL; make `BASE_DN` optional + - `ldap_connect($host, $port)` function signature is deprecated + - Querying an AD Global Catalog across an entire forest requires an empty base DN +* Use an absolute file path in `AssetHelper` class for `css()` & `js()` functions +* Remove whitespace at the end of `APP_VERSION` constant +* Add IP address to authentication error logs +* Add interpolation expressions to e-mail subject in automatic action "Send a task by email to someone" + - For example: `Email subject = {{column_title}}: {{title}} (#{{id}})` +* Add Hungarian Forint to the list of currencies + +Version 1.2.18 (December 28, 2020) +---------------------------------- + +* Sqlite migrations should have foreign keys disabled outside the transaction + => Existing behavior could lead to data loss if schema is changed + => If you are using Sqlite, skip version 1.2.17, upgrade directly to v1.2.18 +* Use more secure default Nginx SSL configuration in Docker image +* Update vendor folder +* Add missing pt_br translations +* Update ja_JP translations + +Version 1.2.17 (December 27, 2020) +---------------------------------- + +* Fix grammatical errors +* Add autocomplete attribute to HTML forms +* Added "Mexican Peso" to the list of currencies +* Added an option to send a copy of all generated e-mails to a BCC address +* Don't force role of users if no LDAP groups defined +* Keep the tags when converting a subtask to task +* Bump symfony/stopwatch from 5.1.8 to 5.2.0 +* Bump pimple/pimple from 3.3.0 to 3.3.1 +* Bump symfony/stopwatch from 5.2.0 to 5.2.1 +* Publish Docker images to GitHub container registry in addition to Docker Hub +* Use Github Actions to publish Docker images +* Check if the user is assigned to any role in the project +* Fix tasks.swimlane_id foreign key for Sqlite +* Remove unused namespaces +* Add mk_MK (Macedonian) translation +* Update translations + +Version 1.2.16 (October 9, 2020) +-------------------------------- + +* Update Composer dependencies +* Update translations +* Add link to toggle column scrolling in board view +* Add missing environment variables in php-fpm config +* Add setting that makes possible any new LDAP user to be Manager by default +* Add ARIA label to modal link with title attribute +* Add ARIA label to user mention +* Add ARIA label to letter avatars +* Add ARIA label to project select role without label +* Add ARIA label to dropdown autocomplete without label +* Add ARIA label to form text editor without label +* Add ARIA label to icons with title attributes +* Add ARIA label for form inputs without labels +* Add ARIA label for elements with titles +* Add hidden accessible form input labels +* Add hidden accessible titles +* Hide user name from screen readers +* Correct table collapsed column titles +* Prevent the original page from being modified by the opened link +* Allow email to be retrieve by SSO ReverseProxy +* Fix grammatically incorrect error message +* Add option to configure SMTP HELO name +* Add new config parameter SESSION_HANDLER +* Fix clearing of all Javascript storage +* Added standard notification footer to comment email template + +Version 1.2.15 (June 19, 2020) +------------------------------ + +* Update dependencies +* Added PUT method using CURLOPT_CUSTOMREQUEST +* Run integration tests on Github Actions +* Fixed capitalization of sAMAccountName for LDAP_USER_ATTRIBUTE_USERNAME example +* Added missing closing HTML tag in template +* Update Docker image to Alpine 3.12 +* Removed paragonie/random_compat (not required for PHP 7) +* Setup Dependabot on GitHub +* Allow use of the user's DN as the group filter substitution +* Add subtask events to ProjectModificationDateSubscriber +* Update Vagrantfile to Ubuntu 20.04 +* Open large modal when clicking on edit category link +* Set margin-bottom at 0 only for the last child of a tooltip element +* Prevent last swimlane to be hidden if there is only one +* Execute tooltip listeners only once when the DOM is ready +* Use Ajax request for Markdown preview +* Make tooltip events bubble +* Keep newlines in markdown +* Show the color dropdown when creating a new automatic action +* Update translations +* Correct duration calculation +* Copy subtask assignee when duplicating a task +* Save task list order in user session +* Add action to assign a user when the swimlane change + +Version 1.2.14 (April 15, 2020) +------------------------------- + +* Update translations +* Add new event subtask.create_update +* Replace Travis CI by GitHub Actions +* Add option to enable or disable global tags per projects +* Show group membership(s) in user summary and user list +* Docker: use real hostname instead of "localhost" +* Add new task/project image hooks +* Fix invalid RSS feed encoding +* Add new plugin hooks +* Rename "private" projects to "personal" +* Add per-project and per-swimlane task limits +* Use parent task color when converting a subtask to task +* Add environment variables support to configure the application +* Add the possibility to make project tags global from project settings +* Fix regex to detect external links with attachments +* Use KANBOARD_URL to build URIs if specified +* Make time_spent and time_estimated fields editable for updateTask and createTask API calls +* Kanboard now requires PHP >= 7.2 since other versions are deprecated +* Avoid page shrinking when drag and drop cards on iOS devices +* Added a hover color to i elements inside the "dropdown-submenu-open" class +* Avoid duplicating Dockerfiles for each architecture + +Version 1.2.13 (December 15, 2019) +---------------------------------- + +* Adjust width of time tracking column +* Make subtasks not wrap under icons +* Make column scrollable in Kanban view +* Add composer dependency roave/security-advisories +* Add colors to tag and category lists +* Update Parsedown to v1.7.3 (security update) +* Make sure the elements behind the alert notification are clickable after animation +* Make sure incompatible plugins can be uninstalled from the web ui +* Move "data-js-lang" attribute to HTML "lang" attribute +* Update language codes for time picker so the calendars are translated correctly +* Dropdown in project managers view covers heading +* Fix date picker datetime parsing when using pre-defined localized versions of am/pm +* Show ISO date format in application settings +* Datepicker stores its Spanish locales as "es", not "es-ES" or "es-VE" +* Increase width of color picker to avoid text overlap in Polish +* Close open menu when clicking again on the button +* Fix width of filter bar in mobile +* In PHP-7.4, nested ternary operators are to be bracketed +* Change string indexing from {0} to [0] (deprecated in PHP 7.4) +* Update translations + +Version 1.2.12 (October 26, 2019) +--------------------------------- + +* Update Docker image to Alpine Linux 3.10.3 +* Add new template hook: "template:project-permission:after-adduser" +* Upgrade jQuery to version 3.4.1 +* Add Spanish (Venezuela) translation +* Removed color_id requirement for tag API calls +* Fix subtask restriction modal when clicking on the icon instead of link +* Use PHPUnit 5 for Vagrant +* Prevent last project manager role from being removed +* Check API token before LDAP authentication +* Make sure task limit consider all open tasks (not only filtered tasks) +* Update translations +* Change user filter and category icon +* Add "anybody" filter +* Disable user scaling to avoid page shrinking when drag&drop on mobile +* Fix condition for action "Automatically update the start date when task move away from certain column" +* Add tests for task link and subtask assignee filters +* Changes filters from in array to subqueries +* Add hash to image URL to force browser to update avatar image when changed + +Version 1.2.11 (August 24, 2019) +-------------------------------- + +Breaking Changes: + +* Internet Explorer support is now deprecated +* Add project ID to ExternalTaskProviderInterface::fetch() + +Fixes and Improvements: + +* Fixed issue of tooltip not disapearing +* Update Docker image to Alpine Linux 3.10.2 +* Hide due date time on the card if time is 00:00 +* Add new plugin hooks in view switcher +* Ignore Dockerfiles from git archive +* Remove dependency on nodejs and gulp +* Remove dependency on Sass + - Convert *.sass files to vanilla CSS + - Start using CSS variables + - Add PHP minifier +* Add link button to text editor +* Implements check for duplicate default categories +* Implements check for duplicate default columns +* Fix HTML parsing in Markdown editor +* Change checkboxes alignment in task creation form +* Add support for reference:none +* Fix tabindexes on task creation and modification forms +* Add option to clone filters on project duplication + - Fixed missing metadata option from project "create from" + - Added option to clone project custom filters + - Added append option to custom field tests + - Added a test that uses the "append" option + - Fixed disabled swimlane duplication error with Postgresql +* Update translations +* Save thumbnails as PNG to have transparency +* New action to update the start date when a task move away from a column +* Add the possibility to sort columns by due date +* Add "identifier" beside "name" while creating a new project + +Version 1.2.10 (June 21, 2019) +------------------------------ + +* Add Auto-Submitted E-mail header as per RFC 8384 +* Add HTML tag in email notifications +* Add new hook template:export:header +* Do not show duplicated results when multiple comments match +* Add Docker manifest with multiple architectures (arm32v6, arm32v7, arm64v8, amd64) +* Update Docker image to Alpine 3.10.0 +* Add View File on popover to tooltip +* Fix text file preview +* Set "start date" and "end date" on projects from API +* Add cURL support to HTTP Client + - Add HTTP_PROXY_EXCLUDE option when cURL is used + - Show HTTP client backend in about page + - Fallback to legacy Stream Contexts if cURL extension is not available +* Add Bitcoin to the currency list +* Add automatic action to move task between columns based on due date +* Fixes icon opacity when hovered +* Hide one task count when there is only one swimlane +* Update translations + +Version 1.2.9 (April 5, 2019) +----------------------------- + +* Add Slovak translation +* Update translations +* Changes search by reference to case insentive +* Fix postgres explicit schema name usage +* Simplify local Docker image build +* Show a 404 when accessing data folder from URL (Docker Image) +* Clarify the comment about MAIL_SMTP_ENCRYPTION +* Remove dependency on Bower +* Replaces accordion Javascript component by
HTML element +* Fix MySQL migration when using increment values different from 1 +* Add missing webhook event: task.move.project +* Add new actions to reorder tasks by column + +Version 1.2.8 (February 2, 2019) +-------------------------------- + +Breaking Changes: + +* Authorize only API tokens when 2FA is enabled (no user password) +* Disable by default plugin installer for security reasons: + - There is no code review or any approval process to submit a plugin. + - This is up to the Kanboard instance owner to validate if a plugin is legit. + +Fixes and Improvements: + +* Limit avatar image size +* Avoid CSRF in users CSV import +* Avoid XSS in pagination sorting +* Do not show projects dropdown when prompting the 2FA code +* Always returns a 404 instead of 403 to avoid people discovering users +* Check if user role has changed while the session is open +* Add missing CSRF check in TwoFactorController::deactivate() +* Hide edit button when user cannot edit task +* Fix permission check before "Assign to me" +* Fix permission check before showing project options +* Fix assignable users on a group with a custom role +* Fix import of automatic actions when parameters are "unassigned" or "no category" +* Update license year +* Update Docker image to Alpine 3.9 +* Update translations +* Fix PHP error in task views (tag colors) +* Limit assignee drop-down selector scope + +Version 1.2.7 (December 19, 2018) +--------------------------------- + +* Write log entry on file removal +* Auto link duplicated tasks +* Auto link tasks duplicated to another project +* Auto link tasks created from a subtask +* Redirect to board view of the current task after duplication +* Fix broken link to contributor page +* Add automatic action for moving a task to a swimlane based on category change +* Add automatic action to assign a category based on swimlane change +* Add ordering comments by id along with creation date +* Fix custom roles duplication (source and destination column_id) +* Add locale en_GB +* New automatic action: move the task to another swimlane when assigned +* Disable php_uname() warning for restrictive environments +* Add hook to board settings +* Add method remove() to settings model +* Add php7-bcmath to Docker image +* Add sorting by reference in list view +* Added priority, swimlane, and column values from parent task to task converted from subtask +* Update translations + +Version 1.2.6 (October 10, 2018) +-------------------------------- + +* Escape table name 'groups' because groups is a reserved word as of MySql 8.0.2 +* Reduce number of SQL queries when doing groups sync +* Make swimlane filter compatible with numeric title +* Duplicate reference fields when duplicating a task +* Do not try to redirect to login page when offline +* Define fixed width for auto-complete dropdown +* Fix task drag and drop slowdown when a column is hidden +* Make PLUGINS_DIR absolute in config.default.php +* Add custom roles project duplication +* Allow 'No assignee' for external task on single user public boards +* Add tag and category colors +* Exclude task links and user mentions from nesting (Markdown parser) +* When forcing HTTPS, handle subfolder URLs properly +* Add search within a range of dates for completion, modification, creation, and moved fields +* Update Docker image to Alpine Linux 3.8 +* Make sure the presense of mod_env is checked in .htaccess +* Make HTTP client timeout configurable +* Use SET NAMES instead of charset for MySQL connection +* Vendoring deprecated Composer libs +* Update translations and fix typos + +Version 1.2.5 (June 15, 2018) +----------------------------- + +* Update jQuery to latest version +* Defer javascript files loading by default +* Add quick link "assign me" in different views +* Add bulk task operations in list view +* Add checkboxes in list view to move tasks to another column at once +* Make sure automatic actions are applied to all tasks when using bulk operations +* Add ability to run cron jobs by calling URL +* Add basic print stylesheet +* Add dashboard and search task footer hooks +* Reword project settings label +* Improve Docker image config overrides +* Fix cronjob in Docker image +* Increase Nginx fastcgi buffers for Docker image +* Increase size of the "users.language" column +* Update translations and improve English texts + +Version 1.2.4 (May 16, 2018) +---------------------------- + +* Rewrite tooltip code without jQuery +* Update Parsedown library +* Remove all attachments when removing a project +* Improve whitespace handling in "cli locale:compare" command +* Don't markdown project owner's name in header tooltip +* Add SSL to Docker image +* Avoid people to remove themselves from project permissions +* Fix escaping issue in Markdown editor +* Add data/config.php to .gitignore +* Clarified text label for notification settings +* Add Ukrainian translation +* Do not show inactive users in group members dropdown +* Improve dashboard pagination +* Make list view more compact +* Hide private projects checkbox if the feature is disabled +* Make cli locale commands working outside of source tree +* Make subtask title text field wider when editing subtasks +* Add link to open images in a new tab +* Make hardcoded hours string translatable +* Translation updates + +Version 1.2.3 (April 18, 2018) +------------------------------ + +New features: + +* Add Project MetaData API calls +* Add default filter per user + +Improvements: + +* Use utf8mb4 encoding for MySQL instead of utf8 (Emoji support) +* Increase text fields length in several tables +* Move documentation to https://docs.kanboard.org/ +* Make sure no empty group is submitted on project permissions page +* Translate subtasks status and internal links labels in notifications +* Add link to tasks and projects in overdue notifications +* Add missing translations +* Move custom libs to the source tree + +Bug fixes: + +* Fix margin for task recurrence tooltip + +Version 1.2.2 (March 30, 2018) +------------------------------ + +Improvements: + +* Add thumbnail quality parameter (default to 95) +* Always display SQL errors +* Move SimpleLogger lib into app source tree +* Add system log driver and use it by default +* Display exceptions from plugins while refreshing board +* Redirect to original URL after OAuth login +* Add author name and email arguments to mail client +* Improve HTTP client to raise exceptions +* Update translations + +Bug fixes: + +* Fix broken daily summary export +* Fix role precedence in LDAP integration + +Version 1.2.1 (February 28, 2018) +--------------------------------- + +New features: + +* Add automatic action to change column once a start date is reached +* Add automatic action to change color once start date is reached +* Add CSS class to categories to allow custom styling +* Add option to disable Mysql SSL server verification +* Add timeout parameter for database connection +* Add error log for authentication failure to allow fail2ban integration + +Improvements: + +* Set the correct swimlane/column ID when moving a task via its internal dialog +* Allow filtering for tasks without due date +* Add plugin hook 'aftersave' after creating Task +* Run SessionHandler::write() into a transaction +* Remove dependency on PicoFeed +* Add CSRF check for task and project files upload +* Add missing CSRF check on avatar upload form +* Add missing CSRF check in saveUploadDB() method +* Update Vagrantfile to use Ubuntu Xenial +* Send event author in webhook notification +* Update translations +* Update documentation + +Version 1.2.0 (December 27, 2017) +--------------------------------- + +Breaking changes: + +* Kanboard supports only PHP >= 5.6 (PHP 5.3, 5.4 and 5.5 are not supported anymore) + +New features: + +* PHP sessions are now stored into the database, + In this way, it's easier to run Kanboard behind a load-balancer + +Improvements: + +* Copy category from parent task when creating a task from a subtask +* Translation updates and improvements + +Version 1.1.1 (December 9, 2017) +-------------------------------- + +Breaking changes: + +* The Docker tag "stable" is not used anymore, instead use a specific version tag +* Task limit apply across all swimlanes +* Kanboard is now using the domain kanboard.org + +New features: + +* New automatic action to create a subtask assigned to the creator and start the timer +* New automatic action to stop the timer of subtasks +* Add command line tool to remove project activities after one year +* Add command line tool to disable projects not touched during one year +* Add config option to exclude fields from auth providers sync +* Add new plugin hooks + +Improvements: + +* Open audio files in a new tab +* Upgrade Docker image to Alpine Linux 3.7 +* Improve Docker build to use Docker Hub hooks +* The application version is now included into the Docker image +* Disable private projects when disabling a user +* Allow administrators to update username of remote users +* Improve layout on mobile/tablet devices +* Changed board column headings to show swimlane-column total in bold +* Enable dragging to collapsed columns +* Add missing checks for requirements + +Bug fixes: + +* Add class "js-modal-replace" to icons to make it clickable +* Improve permission checks on custom filters page to avoid forbidden access + +Version 1.1.0 (November 20, 2017) +--------------------------------- + +Breaking changes: + +* Remove feature "Allow everybody to access to this project" (You must define project members and groups) +* Composer dependencies are now included in the repository to be able to use git-archive (except development dependencies) + +New features: + +* Add predefined templates for task descriptions +* Add the possibility to send tasks and comments to multiple recipients +* Add users, groups and projects search +* Add command line argument to display Kanboard version +* Add user backend provider system (to be used by external plugins) +* Add Romanian and Chinese (Taiwan) translation + +Improvements: + +* Minor CSS improvements +* Add help message on project sharing page +* Task CSV import is now able to handle the priority, start date, tags and one external link +* Improve iCalendar feed to include tasks with start/end date and due date with a time +* Check if the start date is before due date +* You can get an archive of Kanboard by using the download button in Github or the command git archive +* Translation updates + +Bug fixes: + +* Fix project dropdown visibility when page is scrolled down +* Task move events must be executed synchronously +* Handle CSV files with only "\r" line endings + +Version 1.0.48 (October 23, 2017) +--------------------------------- + +Improvements: + +* Add bulk subtasks creation +* Add filter by score/complexity +* Improved display of the header bar +* Displays bullets from lists in tooltips +* Updated translations +* Add tags and priority to task export +* Make the number of events stored in project activities configurable +* Do not use jQuery tooltip for task title in collapsed mode +* Remove dependency on Yarn +* Improve external task integration +* Add support for array parameters in automatic actions +* Add tooltip to subtask icons +* Add attribute title to external links +* Render a link if the reference is a URL +* Add icon to edit a task quickly on the board +* Improve .htaccess when using HTTP Basic Authentication for Apache/FastCGI +* Add note to specify incompatibility with mod_security + +Version 1.0.47 (October 3, 2017) +-------------------------------- + +New features: + +* Vietnamese translation + +Improvements: + +* Updated translations + +Security Issues: + +* Avoid people to alter other project resources by changing form data + +Version 1.0.46 (August 13, 2017) +-------------------------------- + +Security Issues: + +* Fix two privilege escalation issues: a standard user could reset the password +of another user (including admin) by altering form data. +(CVE-2017-12850 and CVE-2017-12851, discovered by "chbi"). + +Improvements: + +* Add "Create another link" checkbox for internal link as in sub-task creation +* Updated translations + +Bug fixes: + +* Fix parsing issue in phpToBytes() method + +Version 1.0.45 (June 23, 2017) +------------------------------ + +New features: + +* Automatic action to assign tasks to its creator +* Add the possibility to create a comment when a task is sent by email +* Add dropdown menu to autocomplete email field from project members +* Add configurable list of predefined subjects when sending a task or a a comment by email +* Add command line argument to filter overdue notification for a given project + +Improvements: + +* Improve SQL migrations when old default swimlanes have the same name as a normal swimlanes + +Bug fixes: + +* Add missing subtask permissions for project viewer role +* Fix Javascript language mapping + +Version 1.0.44 (May 28, 2017) +----------------------------- + +Improvements: + +* Use datetime field for due date +* Update Docker image to Alpine Linux 3.6 +* Add the possibility to pass API token as environment variable for Docker container +* Add wildcard search for task reference field +* Improve automated action TaskAssignColorOnDueDate to update task only when necessary +* Add task and project API formatters +* Update translations + +Bug fixes: + +* Fix broken user mentions in comment form at the bottom of the task view page +* Ensure project tags are removed when the project is removed +* Avoid PHP notice when regenerating API token for a user +* Fix wrong dropdown menu in group members list +* Show only active users in auto-complete forms (project permissions) +* Check owner existence before to create project + +Version 1.0.43 (April 30, 2017) +------------------------------- + +Improvements: + +* Add "[DUPLICATE]" prefix to duplicated tasks title +* Add sorting by position and start date in task list view +* Update translations + +Bug fixes: + +* Add missing plugin parameter for search box (Gantt and calendar plugin) +* Fix broken start date button + +Version 1.0.42 (April 8, 2017) +------------------------------ + +New features: + +* New restrictions for custom project roles + +Improvements: + +* Improved dashboard + +Breaking Changes: + +* Move calendar to external plugin: https://github.com/kanboard/plugin-calendar +* Move Gantt charts to external plugin: https://github.com/kanboard/plugin-gantt +* Move Gravatar to external plugin: https://github.com/kanboard/plugin-gravatar + +Bug fixes: + +* Fix typo in Sqlite schema + +Version 1.0.41 (March 19, 2017) +------------------------------- + +New features: + +* Add Croatian language translation + +Improvements: + +* Simplify dashboard to use new tasks list view +* Move notifications outside of dashboard +* Render QR code for TwoFactor authentication without Google Chart API +* Add toggle button to show/hide subtasks in task list view +* Use same layout as task listing for task search +* Display tags in task list view +* Make user actions available from contextual menu +* Change users and groups list layout +* Project priority is always rendered now +* Do not list private projects when adding a new user +* Restore link for task title on board + +Breaking Changes: + +* Remove method getQrCodeUrl() from PostAuthenticationProviderInterface + +Version 1.0.40 (February 24, 2017) +---------------------------------- + +New features: + +* Send comments by email +* Send tasks by email +* Add Reply-To header to emails sent from Kanboard +* Upload Sqlite database from user interface +* Automatic action to change task color when due date is expired + +Improvements: + +* Make link to calendar view bookable +* Reintroduce word search in board selector +* Properly resize task list height on column toggle +* Show total score across all swimlanes +* Redesign task list view and project list view +* Allow people to remove missing automatic actions (installed from a removed plugins) +* Improve task view tables +* Simplify automatic actions table +* Show category description in tooltip +* Show category creation form in modal dialog +* Prevent people to remove swimlanes that contains tasks +* Show task count in swimlane table +* Use contextual menu instead of action column in users management + +Breaking changes: + +* The concept of "default swimlane" has been removed +* Previous default swimlanes are migrated to an independent swimlanes +* Columns "default_swimlane" and "show_default_swimlane" from "projects" table are not used anymore +* Remove API method "getDefaultSwimlane()" +* Add mandatory argument "project_id" to API method "updateSwimlane()" +* Change interface for mail transports + +Bug fixes: + +* Upload files button stay disabled when there are other submit buttons on the same page +* Hiding subtasks from hidden tasks in dashboard + +Security: + +* Fix XSS in LetterAvatarProvider (render broken image) + +Those issues are harmless if you use default Kanboard settings for CSP rules: + +* Avoid potential XSS in project overview when listing users +* Avoid potential XSS in Gantt chart + +Version 1.0.39 (February 12, 2017) +---------------------------------- + +Improvements: + +* Add menu entry in task dropdown to add attachments +* Improve error reporting when file upload is not configured properly +* Open comments on board view with a modal dialog instead of tooltip +* Improve card icons alignment on board +* Adjust modal dialog width on mobile devices +* Add priority column in list view +* Change wording for project status (use "closed" instead of "inactive") +* Prevent people to remove columns that contains tasks +* Improve LDAP error reporting +* Add configuration parameter to disable email configuration from user interface +* Add email address field for projects +* Improve forget password behavior (notify the user that an email has been sent or not) +* Do not display current project in board selector +* Do not set default task assignee for team projects +* Comments are highlighted if hash (#comment-123) is present in URL +* Documentation translated in Turkish + +Bug fixes: + +* Search with multiple expressions with double quotes was not working +* Fix broken subtask restriction per user +* Fix CFD chart (stack wrongly ordered) + +Version 1.0.38 (January 28, 2017) +---------------------------------- + +New features: + +* User invitations by email + +Improvements: + +* Simplify user creation form +* Add modification date for comments +* Add project creation links to project management pages +* More API procedures are now available to project members and project viewers +* Simplify date and time configuration to avoid potential validation issues +* Show dashboard column visibility in columns page +* Add new template hooks +* Update translations (id_ID, de_DE, ru_RU, fr_FR, pt_PT) +* Add command to execute individual job (mostly for debugging) + +Regressions: + +* Stay on the same page when a task is closed +* Wrong URL in modal to move task to another project + +Bug fixes: + +* Fix broken link when clicking on user avatar for tasks on board +* Fix wrong datetime formatting when task form shows validation errors +* Empty arrays are serialized to a list instead of a dict (Json API) +* Always unbind internal listeners when closing a modal dialog +* Fix installation errors on MySQL 8.0.0 (unescaped reserved keyword) +* Avoid PHP notice when column form validation failed +* Fix wrong default value for add group member modal +* Add missing filter (completed) for task search + +Version 1.0.37 (January 14, 2017) +--------------------------------- + +Improvements: + +* Improve keyboard shortcuts handling +* Improve auto-complete dropdown elements sorting +* Larger task form +* Rewrite dialog and confirmation boxes (inline popups) +* Remove TaskGanttCreationController +* Add helpers to open modal boxes +* Make icons clickable in menus +* Open task imports in modal box +* Open form to create customer filters in modal box +* Open project activities in modal box +* Display project analytics in modal box +* Display project exports in modal box +* Improve accordion component +* Improve currencies page navigation +* Improve link labels page navigation +* Improve settings page layout +* Offer the possibility to define version compatibility from plugins +* Add task creation event to the automatic action to send task by email + +Bug fixes: + +* Closing screenshot dialog prevent input elements to get focus + +Version 1.0.36 (December 30, 2016) +---------------------------------- + +New features: + +* Add slideshow for images +* Add API calls to manage tags +* Offer the possibility to override internal formatter objects from plugins +* Open PDF attachments in browser tab (preview) + +Improvements: + +* Add pagination details +* Handle username with dots in user mentions +* Rewrite UI component that change user/group roles +* Replace Chosen jQuery plugin by custom UI component +* Remove dependency on Mousetrap Javascript library +* Disable PageSpeed module from .htaccess if present +* Add currency of Chinese Yuan + +Bug fixes: + +* Fix compatibility issue with PHP 5.3 for array_combine function +* Fix wrong controller name on project activity page when using filters +* Uploaded avatar images are now visible in public board view + +Version 1.0.35 (December 4, 2016) +--------------------------------- + +New features: + +* Add external tasks plugin interfaces +* Add personal API access token for users +* Rewrite of Markdown editor (remove CodeMirror) +* Suggest menu for task ID and user mentions in Markdown editor +* Add config parameter to disable automatic SQL migrations + +Improvements: + +* Add button to close inline popups +* Simplify `.htaccess` to avoid potential issues with possible specific Apache configurations +* Replace notifications Javascript code by CSS +* Refactoring of user mentions job +* Remove Nitrous installer +* Update translations +* Rewrite some components in Vanilla Javascript +* Started Javascript code refactoring to avoid to much dependencies on jQuery +* Remove dependency on VueJS and CoreMirror +* Add P3P headers to avoid potential issues with IE + +Breaking changes: + +* Rename command line tool `./kanboard` to `./cli` + +Bug fixes: + +* Change column type for application settings value (field too small) +* Fix link generation when user mention is followed by a punctuation mark +* Make user mentions works again + +Version 1.0.34 (October 11, 2016) +--------------------------------- + +New features: + +* Custom project roles with configurable restrictions +* Duplicate a task to multiple projects during creation +* New automatic action: + - Close a task in a specific column when not moved during a given period + +Improvements: + +* Do not close the popover when clicking on the background +* Add visual icon to show a dropdown action on task +* Avoid 'blur' effect on popover +* Accept more file types for external links +* Restrict search to active projects +* Improve task status filter +* Add filter tag:none +* Always apply merge hooks in task creation controller +* Update task moved date only when the column or swimlane is changed +* Add new subtask hooks +* Add the actual use of TaskStartDateFilter +* Update translations and documentation + +Bug fixes: + +* Send absolute links in email notifications +* Restrict task complexity to a specific range to avoid integer overflow +* Do not show closed tasks in task move position form +* Avoid "Controller not found" in Settings > Links + +Version 1.0.33 (September 5, 2016) +---------------------------------- + +New features: + +* Move a task without drag and drop (smartphones and tablets) +* Add the possibility to unlock users from the user interface +* New API calls for task metadata +* New automatic actions: + - Define color by Swimlane + - Define priority by Swimlane + +Improvements: + +* Introduce Vue.js to manage user interface components +* Add column "Reference" and "Creator Name" in CSV task export +* Show both time spent and estimated on the board +* Store board collapsed mode user preference in the database +* Store comment sorting direction in the database +* Avoid tags overlapping on the board +* Show project name in notifications +* Allow priority changes for inverted priority scales +* Add the possibility to attach template hooks with local variables and callback +* Add "reference" hooks +* Show project name in task forms +* Convert vanilla CSS to SASS +* Make user interface more responsive for smartphones and tablets +* Support version operators for plugin directory: >= and > +* Update Spanish documentation + +Other changes: + +* Time spent (in hours) for subtasks are not rounded too the nearest quarter anymore + +Bug fixes: + +* Fix improper HTML escaping for textarea (potential XSS) +* Do not show closed tasks on public boards +* Fix undefined constant in config example file +* Fix PHP notice when sending overdue notifications +* Fix wrong project date format (shown as 01/01/1970) + - If the dates still not correct, modify and save the date + +Version 1.0.32 (July 31, 2016) +------------------------------ + +New features: + +* New automated actions: + - Close tasks without activity in a specific column + - Set due date automatically + - Move a task to another column when closed + - Move a task to another column when not moved during a given period +* New filter "moved" for moved date of tasks +* Added internal task links to activity stream +* Added new event for removed comments +* Added search filter for task priority +* Added the possibility to hide tasks in dashboard for a specific column +* Documentation translated in Russian + +Improvements: + +* Improve background worker and job handler +* New template hooks +* Removed individual column scrolling on board, columns use the height of all tasks +* Improve project page titles +* Remove sidebar titles when not necessary +* Internal events management refactoring +* Handle header X-Real-IP to get IP address +* Display project name for task auto-complete fields +* Make search attributes not case sensitive +* Display TOTP issuer for 2FA +* Make sure that the table schema_version use InnoDB for Mysql +* Use the library PicoFeed to generate RSS/Atom feeds +* Change all links to the new repository + +Bug fixes: + +* Allow users to see inactive projects +* Fixed typo in template that prevent project permissions to be duplicated +* Fixed search query with multiple assignees (nested OR conditions) +* Fixed Markdown editor auto-grow on the task form (Safari) +* Fixed compatibility issue with PHP 5.3 for OAuthUserProvider class + +Version 1.0.31 (July 3, 2016) +----------------------------- + +New features: + +* Added tags: global and specific by project +* Added application and project roles validation for API procedure calls +* Added new API call: "getProjectByIdentifier" +* Added new API calls for external task links, project attachments and subtask time tracking + +Improvements: + +* Use PHP 7 for the Docker image +* Preserve role for existing users when using ReverseProxy authentication +* Handle priority for task and project duplication +* Expose task reference field to the user interface +* Improve ICal export +* Added argument owner_id and identifier to project API calls +* Rewrite integration tests to run with Docker containers +* Use the same task form layout everywhere +* Removed some tasks dropdown menus that are now available with task edit form +* Make embedded documentation readable in multiple languages (if a translation is available) +* Added acceptance tests (browser tests) + +Bug fixes: + +* Fixed broken CSV exports +* Fixed identical background color for LetterAvatar on 32bits platforms (Hash greater than PHP_MAX_INT) +* Fixed lexer issue with non word characters +* Flush memory cache in worker to get latest config values +* Fixed empty title for web notification with only one overdue task +* Take default swimlane into consideration for SwimlaneModel::getFirstActiveSwimlane() +* Fixed "due today" highlighting + +Breaking changes: + +* Docker volume paths are changed to /var/www/app/{data,plugins} + +Version 1.0.30 (June 8, 2016) +----------------------------- + +Improvements: + +* Show tasks that are due today in a different color + +Bug fixes: + +* Fixed wrong controller for search in dashboard +* Fixed plural form in alert message +* Fixed CSS cosmetic issue with popover and tooltips + +Version 1.0.29 (June 5, 2016) +----------------------------- + +New features: + +* Manage plugin from the user interface and from the command line +* Added support for background workers +* Added the possibility to convert a subtask to a task +* Added menu entry to add tasks from all project views +* Add tasks in bulk from the board +* Add dropdown for projects +* Added config parameter to allow self-signed certificates for the HTTP client + +Improvements: + +* Display local date format in date picker +* Configure email settings with the user interface in addition to config file +* Upgrade Docker image to Alpine Linux 3.4 +* Move task import to a separate section +* Mark web notification as read when clicking on it +* Support strtotime strings for date search +* Reset failed login counter and unlock user when changing password +* Task do not open anymore in a new window on the Gantt chart +* Do not display task progress for tasks with no start/end date +* Use Gulp and Bower to manage assets +* Controller and Middleware refactoring +* Replace jQuery mobile detection by the library isMobile + +Bug fixes: + +* Fixed user date format parsing for dates that can be valid in multiple formats +* Do not sync user role if LDAP groups are not configured +* Fixed issue with unicode handling for letter based avatars and user initials +* Do not send notifications to disabled users +* Fixed wrong redirect when removing a task from the task view page + +Breaking changes: + +* Webhook to create tasks have been removed, use the API instead +* All controllers have been renamed, people who are not using URL rewriting will see different URLs +* All models have been renamed, plugin maintainers will have to update their plugins + +Version 1.0.28 (May 8, 2016) +---------------------------- + +New features: + +* Added automated action to change task color based on the priority +* Added support for LDAP Posix Groups (OpenLDAP with memberUid or groupOfNames) +* Added support for LDAP user photo attribute (Avatar image) +* Added support for language LDAP attribute +* Added support for Mysql SSL connection +* Search in activity stream +* Search in comments +* Search by task creator +* Added command line utility to reset user password and to disable 2FA + +Improvements: + +* Improve Avatar upload form +* User roles are now synced with LDAP at each login +* Improve web page title on the task view +* Unify task drop-down menu between different views +* Improve LDAP user group membership synchronization +* Category and user filters do not append anymore in search field +* Added more template hooks +* Added tasks search with the API +* Added priority field to API procedures +* Added API procedure "getMemberGroups" +* Added parameters for overdue tasks notifications: group by projects and send only to managers +* Allow people to install Kanboard outside of the DocumentRoot +* Allow plugins to be loaded from another folder +* Filter/Lexer/QueryBuilder refactoring + +Bug fixes: + +* Allow a project owner to manage his own public project +* Fixed PHP warning when removing a user with no Avatar image +* Fixed improper Markdown escaping for some tooltips +* Closing all tasks by column, also update closed tasks +* Fixed wrong task link generation within Markdown text +* Fixed wrong URL on comment toggle link for sorting +* Fixed form submission with Meta+Enter keyboard shortcut +* Removed PHP notices in comment suppression view + +Version 1.0.27 (March 27, 2016) +------------------------------- + +New features: + +* Added Markdown editor +* Added user avatars with pluggable system + - Default is a letter based avatar + - Gravatar + - Avatar Image upload +* Added Korean translation + +Improvements: + +* Added more logging for LDAP client +* Improve schema migration process +* Improve notification configuration form +* Handle state in OAuth2 client +* Allow to use the original template in overridden templates +* Unification of the project header +* Refactoring of Javascript code +* Improve comments design +* Improve task summary sections +* Put back the action sidebar in task view +* Added support for multiple placeholders for LDAP_USER_FILTER +* Added local file link provider +* Show configuration in settings page +* Added "?" to display list of keyboard shortcuts +* Added new keyboard shortcuts for task view +* Always display project name and task title in task views +* Improve automatic action creation +* Move notifications to the bottom of the screen +* Added the possibility to import automatic actions from another project +* Added Ajax loading icon for submit buttons +* Added support for HTTP header "X-Forwarded-Proto: https" + +Bug fixes: + +* Fix bad unique constraints in Mysql table user_has_notifications +* Force integer type for aggregated metrics (Burndown chart concat values instead of summing) +* Fixes cycle time calculation when the start date is defined in the future +* Access allowed to any tasks from the shared public board by changing the URL parameters +* Fix invalid user filter for API procedure createLdapUser() +* Ambiguous column name with very old version of Sqlite + +Version 1.0.26 (February 28, 2016) +---------------------------------- + +Breaking changes: + +* API procedures: + - "moveColumnUp" and "moveColumnDown" are replaced by "changeColumnPosition" + - "moveSwimlaneUp" and "moveSwimlaneDown" are replaced by "changeSwimlanePosition" + +New features: + +* Add drag and drop to change subtasks, swimlanes and columns positions +* Add file drag and drop and asynchronous upload +* Enable/Disable users +* Add setting option to disable private projects +* Add new config option to disable logout + +Improvements: + +* Use inline popup to create new columns +* Improve filter box design +* Improve image thumbnails and files table +* Add confirmation inline popup to remove custom filter +* Increase client_max_body_size value for Nginx +* Split Board model into multiple classes +* Improve logging for the Docker image + +Bug fixes: + +* Fix PHP notices during creation of first project and in subtasks table +* Fix filter dropdown not accessible when there are too many items +* Fix regression: unable to change project in "task move/duplicate to another project" + +Version 1.0.25 (February 7, 2016) +--------------------------------- + +Breaking changes: + +* Core functionalities moved to external plugins: + - Google Auth: https://github.com/kanboard/plugin-google-auth + - Github Auth: https://github.com/kanboard/plugin-github-auth + - Gitlab Auth: https://github.com/kanboard/plugin-gitlab-auth + +New features: + +* When creating a new project, have the possibility to select another project to duplicate +* Add a "Me" button to assignee form element +* Add external links for tasks with plugin api +* Add project owner (Directly Responsible Individual) +* Add configurable task priority +* Add Greek translation +* Add automatic actions to close tasks with no activity +* Add automatic actions to send an email when there is no activity on a task +* Regroup all daily background tasks in one command: "cronjob" +* Add task dropdown menu on listing pages + +Improvements: + +* New Dockerfile based on Alpine Linux and Nginx/PHP-FPM +* The date time format can be chosen in application settings +* Export only open tasks in iCal feed +* Remove time form on task summary page and move that to task edit form +* Replace box shadow by a larger border width when a task is recently modified +* Do not refresh the whole page when changing subtask status +* Add dropdown menu with inline popup for all task actions +* Change sidebar style +* Change task summary layout +* Use inline popup for subtasks, categories, swimlanes, actions and columns +* Move homepage menus to the user dropdown +* Have a new task assigned to the creator by default instead of "no assignee" +* Show progress for task links in board tooltips +* Simplify code to handle ajax popover and redirects +* Simplify layout and templates generation +* Move task form elements to Task helper + +Bug fixes: + +* Category label is broken on the board if there's a url in the description +* Fix pagination on task time tracking page + +Version 1.0.24 (January 23, 2016) +--------------------------------- + +New features: + +* Forgot Password +* Add drop-down menu on each board column title to close all tasks +* Add Malay language +* Add new API procedures for groups, roles, project permissions and to move/duplicate tasks to another project + +Improvements: + +* Avoid to send XHR request when a task has not moved after a drag and drop +* Set maximum dropzone height when the individual column scrolling is disabled +* Always show the search box in board selector +* Replace logout link by a drop-down menu +* Handle notification for group members attached to a project +* Return the highest role for a project when a user is member of multiple groups +* Show in user interface the saving state of the task +* Add drop-down menu for subtasks, categories, swimlanes, columns, custom filters, task links and groups +* Add new template hooks +* Application settings are not cached anymore in the session +* Do not check board status during task move +* Move validators to a separate namespace +* Improve and write unit tests for reports +* Reduce the number of SQL queries for project daily column stats +* Remove event subscriber to update date_moved field +* Make sure that some event subscribers are not executed multiple times +* Show rendering time of individual templates when debug mode is enabled +* Make sure that no events are fired if nothing has been modified in the task +* Make dashboard section title clickable +* Add unit tests for LastLogin + +Bug fixes: + +* Automatic action listeners were using the same instance +* Fix wrong link for category in task footer +* Unable to set currency rate with Postgres database +* Avoid automatic actions that change the color to fire subsequent events +* Unable to unassign a task from the API +* Revert back previous optimizations of TaskPosition (incompatibility with some environment) + +Version 1.0.23 (January 9, 2016) +-------------------------------- + +Breaking changes: + +* Plugin API changes for Automatic Actions +* Automatic Action to close a task doesn't have the column parameter anymore (use the action "Close a task in a specific column") +* Action name stored in the database is now the absolute class name +* Core functionalities moved to external plugins: + - Github Webhook: https://github.com/kanboard/plugin-github-webhook + - Gitlab Webhook: https://github.com/kanboard/plugin-gitlab-webhook + - Bitbucket Webhook: https://github.com/kanboard/plugin-bitbucket-webhook + +New features: + +* Added support of user mentions (@username) +* Added report to compare working hours between open and closed tasks +* Added the possibility to define custom routes from plugins +* Added new method to remove metadata + +Improvements: + +* Improve Two-Factor activation and plugin API +* Improving performance during task position change (SQL queries are 3 times faster than before) +* Do not show window scrollbars when individual column scrolling is enabled +* Automatic Actions code improvements and unit tests +* Increase action name column length in actions table + +Bug fixes: + +* Fix compatibility issue with FreeBSD for session.hash_function parameter +* Fix wrong constant name that causes a PHP error in project management section +* Fix pagination in group members listing +* Avoid PHP error when enabling LDAP group provider with PHP < 5.5 + +Version 1.0.22 (December 13, 2015) +---------------------------------- + +Breaking changes: + +* LDAP configuration parameters changes (See documentation) +* SQL table changes: + - "users" table: added new column "role" and removed columns "is_admin" and "is_project_admin" + - "project_has_users" table: replaced column "is_owner" with column "role" + - Sqlite does not support alter table, old columns still there but unused +* API procedure changes: + - createUser + - createLdapUser + - updateUser + - updateTask +* Event removed: "session.bootstrap", use "app.boostrap" instead + +New features: + +* Add pluggable authentication and authorization system (complete rewrite) +* Add groups (teams/organization) +* Add LDAP groups synchronization +* Add project group permissions +* Add new project role Viewer +* Add generic LDAP client library +* Add search query attribute for task link +* Add the possibility to define API token in config file +* Add capability to reopen Gitlab issues +* Try to load config.php from /data if not available + +Version 1.0.21 (November 22, 2015) +---------------------------------- + +Breaking changes: + +* Projects with duplicate names are now allowed: + - For Postgres and Mysql the unique constraint is removed by database migration + - However Sqlite does not support alter table, only new databases will have the unique constraint removed + +New features: + +* New automatic action: Assign a category based on a link +* Added Bosnian translation + +Improvements: + +* Dropdown menu entries are now clickable outside of the html link +* Improve error handling of plugins +* Use PHP7 function random_bytes() to generate tokens if available +* CSV task export show the assignee name in addition to the assignee username +* Add new hooks for plugins +* Remove workaround for "INSERT ON DUPLICATE KEY UPDATE..." + +Internal code refactoring: + +* Rewrite of session management +* Move some classes to a new namespace Kanboard\Core\Http + +Bug fixes: + +* Loading cs_CZ locale display the wrong language in datetime picker +* Datepicker is closed unexpectedly on blur event +* Fix bug in daily project summary CSV export +* Fix PHP error when adding a new user with email notification enabled +* Add missing template for activity stream to show event "file.create" +* Fix wrong value for PLUGINS_DIR in config.default.php +* Make CSV export compatible with PHP 5.3 +* Avoid Safari to append .html at the end of downloaded files + +Version 1.0.20 (October 24, 2015) +--------------------------------- + +Breaking changes: + +* Add namespace Kanboard (update your plugins) +* Move Mailgun, Sendgrid, Postmark, Slack, Hipchat and Jabber to plugins +* ReverseProxy authentication check for each request that the username match the user session + +New features: + +* Add CSV import for users and tasks +* Add Task, User and Project metadata for plugin creators + +Improvements: + +* Allow to change comments sorting +* Add the possibility to append or not custom filters +* Make mail transports pluggable +* Do not show scroll-bars when a column is collapsed on Windows systems +* Regenerate thumbnails if missing + +Bug fixes: + +* People should not see any tasks during a search when they are not associated to a project +* Avoid disabling the default swimlane during renaming when there is no other activated swimlane + +Version 1.0.19 (October 11, 2015) +--------------------------------- + +New features: + +* Added web notifications +* Added LDAP group sync +* Added swimlane description +* New plugin system (alpha) +* Added Bahasa Indonesia translation +* Added API procedures: getMyOverdueTasks, getOverdueTasksByProject and GetMyProjects +* Added user API access for procedure getProjectActivity() +* Added config parameter to enable/disable Syslog +* Added custom filters +* Added http client proxy support + +Core functionalities moved to plugins: + +* Budget planning: https://github.com/kanboard/plugin-budget +* SubtaskForecast: https://github.com/kanboard/plugin-subtask-forecast +* Timetable: https://github.com/kanboard/plugin-timetable + +Improvements: + +* When duplicating a task redirect to the new task +* Include more shortcut links into the view "My projects" +* Duplicate a project with tasks will copy the new tasks in the same columns +* Offer alternative method to create Mysql and Postgres databases (import sql dump) +* Make sure there is always a trailing slash for application_url +* Do not show the checkbox "Show default swimlane" when there is no active swimlanes +* Append filters instead of replacing value for users and categories drop-downs +* Do not show empty swimlanes in public view +* Change swimlane layout to save space on the screen +* Add the possibility to set/unset max column height (column scrolling) +* Show "Open this task" in drop-down menu for closed tasks +* Show assignee on card only when someone is assigned (hide nobody text) +* Highlight selected item in drop-down menus +* Gantt chart: change bar color according to task progress +* Replace color drop-down by color picker in task forms +* Creating another task stay in the popover (no full page refresh anymore) +* Avoid scrollbar in Gantt chart for row title on Windows platform +* Remove unnecessary margin for calendar header +* Show localized documentation if available +* Add event subtask.delete +* Add abstract storage layer +* Add abstract cache layer +* Add Docker tag for stable version + +Others: + +* Data directory permission are not checked anymore +* Data directory is not mandatory anymore for people that use a remote database and remote object storage + +Bug fixes: + +* Fix typo in template that prevents Gitlab OAuth link to be displayed +* Fix Markdown preview links focus +* Avoid drop-down menu to be truncated inside a column with scrolling +* Deleting subtask doesn't update task time tracking +* Fix Mysql error about gitlab_id when creating remote user +* Fix subtask timer bug (event called recursively) +* Fix Postgres issue "Cardinality violation" when there is multiple "is_milestone_of" links +* Fix issue with due date greater than year 2038 + +Version 1.0.18 (August 30, 2015) +-------------------------------- + +New features: + +* Include documentation in the application +* Add Gitlab authentication +* Add users and categories filters on the board +* Add hide/show columns +* Add Gantt chart for projects and tasks +* Add new role "Project Administrator" +* Add login brute force protection with captcha and account lockdown +* Add new api procedures: getDefaultTaskColor(), getDefaultTaskColors() and getColorList() +* Add user api access +* Add config parameter to define session duration +* Add config parameter to disable/enable RememberMe authentication +* Add start/end date for projects +* Add new automated action to change task color based on the task link +* Add milestone marker in board task +* Add search for task title when using an integer only input +* Add Portuguese (European) translation +* Add Norwegian translation + +Improvements: + +* Add handle to move tasks on touch devices +* Improve file attachments tooltip on the board +* Adjust automatically the height of the placeholder during drag and drop +* Show all tasks when using no search criteria +* Add column vertical scrolling +* Set dynamically column height based on viewport size +* Enable support for Github Enterprise when using Github Authentication +* Update iCalendar library to display organizer name +* Improve sidebar menus +* Add no referrer policy in meta tags +* Run automated unit tests with Sqlite/Mysql/Postgres on Travis-ci +* Add Makefile and remove the "scripts" directory + +Bug fixes: + +* Wrong template name for subtasks tooltip due to previous refactoring +* Fix broken url for closed tasks in project view +* Fix permission issue when changing the url manually +* Fix bug task estimate is reset when using subtask timer +* Fix screenshot feature with Firefox 40 +* Fix bug when uploading files with Cyrilic characters + +Version 1.0.17 (July 27, 2015) +------------------------------ + +New features: + +* Added url rewrite and new routes +* Added new search engine with advanced syntax +* Added global search section +* Added search form on the dashboard +* Added new dashboard layout +* Added new layout for board/calendar/list views +* Added filters helper for search forms +* Added setting option to disable subtask timer +* Added setting option to include or exclude closed tasks into CFD +* Added setting option to define the default task color +* Added new config option to disable automatic creation of LDAP accounts +* Added loading icon on board view +* Prompt user when moving or duplicate a task to another project +* Added current values when moving/duplicate a task to another project and add a loading icon +* Added memory consumption to debug log +* Added form to create remote user +* Added edit form for user authentication +* Added config option to hide login form +* Display OAuth2 urls on integration page +* Added keyboard shortcuts to switch between board/calendar/list view +* Added keyboard shortcut to focus on the search box +* Added Slack channel override +* Added new report: Lead and cycle time for projects +* Added new report: Average time spent into each column +* Added task analytics +* Added icon to set the start date automatically +* Added datetime picker for start date + +Improvements: + +* Updated documentation +* Display user initials when tasks are in collapsed mode +* Show title in tooltip for collapsed tasks +* Improve alert box fadeout to avoid an empty space +* Set focus on the drop-down for category popover +* Make escape keyboard shortcut global +* Check the box remember me by default +* Store redirect login url in session instead of using url parameter +* Update Gitlab webhook +* Do not rewrite remember me cookie for each request +* Set the assignee as organizer for ical events +* Increase date range for ics export +* Reduce spacing on cards +* Move board collapse/expand mode to server side to avoid board flickering +* Use ajax requests for board collapse/expand +* Do not set anchor for the default swimlane on the link back to board +* Replace timeserie axis to category axis for charts +* Hide task age in compact mode +* Improve quick-add subtasks form +* Reduce the size of the filter box for smaller screen +* Added icon to hide/show sidebar +* Update GitLab logo +* Improve Dockerfile + +Translations: + +* Added Czech translation +* Updated Spanish translation +* Updated German Translation + +Bug fixes: + +* Screenshot drop-down: unexpected scroll down on the board view and focus lost when clicking on the drop zone +* No creator when duplicating a task +* Avoid the creation of multiple subtask timer for the same task and user + +Code refactoring: + +* Split task controller into smaller classes +* Remove method Category::getBoardCategories() +* Rewrite movePosition() to improve performances +* Refactoring of Github and Google authentication + +Breaking changes: + +* New OAuth url for Google and Github authentication + +API: + +* Add urls in api response for tasks and projects + +Other: + +* Added automated Docker build +* Remove edit recurrence from the task menu on the board +* Switch to MIT License instead of AGPLv3 + +Version 1.0.0 to 1.0.16 +----------------------- + +* See commit history and website news diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..821d033 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Frédéric Guillot + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/app/.htaccess b/app/.htaccess new file mode 100644 index 0000000..c47998c --- /dev/null +++ b/app/.htaccess @@ -0,0 +1,7 @@ += 2.3> + Require all denied + + + Order allow,deny + Deny from all + diff --git a/app/Action/Base.php b/app/Action/Base.php new file mode 100644 index 0000000..78b8c20 --- /dev/null +++ b/app/Action/Base.php @@ -0,0 +1,305 @@ +params as $key => $value) { + $params[] = $key.'='.var_export($value, true); + } + + return $this->getName().'('.implode('|', $params).')'; + } + + /** + * Set project id + * + * @access public + * @param integer $project_id + * @return Base + */ + public function setProjectId($project_id) + { + $this->projectId = $project_id; + return $this; + } + + /** + * Get project id + * + * @access public + * @return integer + */ + public function getProjectId() + { + return $this->projectId; + } + + /** + * Set an user defined parameter + * + * @access public + * @param string $name Parameter name + * @param mixed $value Value + * @return Base + */ + public function setParam($name, $value) + { + $this->params[$name] = $value; + return $this; + } + + /** + * Get an user defined parameter + * + * @access public + * @param string $name Parameter name + * @param mixed $default Default value + * @return mixed + */ + public function getParam($name, $default = null) + { + return isset($this->params[$name]) ? $this->params[$name] : $default; + } + + /** + * Check if an action is executable (right project and required parameters) + * + * @access public + * @param array $data + * @param string $eventName + * @return bool + */ + public function isExecutable(array $data, $eventName) + { + return $this->hasCompatibleEvent($eventName) && + $this->hasRequiredProject($data) && + $this->hasRequiredParameters($data) && + $this->hasRequiredCondition($data); + } + + /** + * Check if the event is compatible with the action + * + * @access public + * @param string $eventName + * @return bool + */ + public function hasCompatibleEvent($eventName) + { + return in_array($eventName, $this->getEvents()); + } + + /** + * Check if the event data has the required project + * + * @access public + * @param array $data Event data dictionary + * @return bool + */ + public function hasRequiredProject(array $data) + { + return (isset($data['project_id']) && $data['project_id'] == $this->getProjectId()) || + (isset($data['task']['project_id']) && $data['task']['project_id'] == $this->getProjectId()); + } + + /** + * Check if the event data has required parameters to execute the action + * + * @access public + * @param array $data Event data dictionary + * @return bool True if all keys are there + */ + public function hasRequiredParameters(array $data, array $parameters = array()) + { + $parameters = $parameters ?: $this->getEventRequiredParameters(); + + foreach ($parameters as $key => $value) { + if (is_array($value)) { + return isset($data[$key]) && $this->hasRequiredParameters($data[$key], $value); + } elseif (! isset($data[$value])) { + return false; + } + } + + return true; + } + + /** + * Execute the action + * + * @access public + * @param \Kanboard\Event\GenericEvent $event + * @param string $eventName + * @return bool + */ + public function execute(GenericEvent $event, $eventName) + { + $data = $event->getAll(); + $hash = md5(serialize($data).$eventName); + + // Do not call twice the same action with the same arguments. + if (isset($this->callStack[$hash])) { + return false; + } else { + $this->callStack[$hash] = true; + } + + $executable = $this->isExecutable($data, $eventName); + $executed = false; + + if ($executable) { + $executed = $this->doAction($data); + } + + $this->logger->debug($this.' ['.$eventName.'] => executable='.var_export($executable, true).' exec_success='.var_export($executed, true)); + + return $executed; + } + + /** + * Register a new event for the automatic action + * + * @access public + * @param string $event + * @param string $description + * @return Base + */ + public function addEvent($event, $description = '') + { + if ($description !== '') { + $this->eventManager->register($event, $description); + } + + $this->compatibleEvents[] = $event; + return $this; + } + + /** + * Get all compatible events of an automatic action + * + * @access public + * @return array + */ + public function getEvents() + { + return array_unique(array_merge($this->getCompatibleEvents(), $this->compatibleEvents)); + } +} diff --git a/app/Action/CommentCreation.php b/app/Action/CommentCreation.php new file mode 100644 index 0000000..301d2cf --- /dev/null +++ b/app/Action/CommentCreation.php @@ -0,0 +1,87 @@ +commentModel->create(array( + 'reference' => isset($data['reference']) ? $data['reference'] : '', + 'comment' => $data['comment'], + 'task_id' => $data['task_id'], + 'user_id' => isset($data['user_id']) && $this->projectPermissionModel->isAssignable($this->getProjectId(), $data['user_id']) ? $data['user_id'] : 0, + )); + } + + /** + * 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 ! empty($data['comment']); + } +} diff --git a/app/Action/CommentCreationMoveTaskColumn.php b/app/Action/CommentCreationMoveTaskColumn.php new file mode 100644 index 0000000..d5bdd80 --- /dev/null +++ b/app/Action/CommentCreationMoveTaskColumn.php @@ -0,0 +1,100 @@ + t('Column')); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'column_id', + 'project_id', + ), + ); + } + + /** + * Execute the action (append to the task description). + * + * @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) + { + if (! $this->userSession->isLogged()) { + return false; + } + + $column = $this->columnModel->getById($data['task']['column_id']); + + return (bool) $this->commentModel->create(array( + 'comment' => t('Moved to column %s', $column['title']), + 'task_id' => $data['task_id'], + 'user_id' => $this->userSession->getId(), + )); + } + + /** + * 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['task']['column_id'] == $this->getParam('column_id'); + } +} diff --git a/app/Action/StopSubtaskTimerMoveTaskColumn.php b/app/Action/StopSubtaskTimerMoveTaskColumn.php new file mode 100644 index 0000000..e6f9e43 --- /dev/null +++ b/app/Action/StopSubtaskTimerMoveTaskColumn.php @@ -0,0 +1,102 @@ + t('Column'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'id', + 'column_id', + 'project_id', + ), + ); + } + + /** + * Execute the action (append to the task description). + * + * @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) + { + $subtasks = $this->subtaskModel->getAll($data['task']['id']); + $results = array(); + + foreach ($subtasks as $subtask) { + $results[] = $this->subtaskModel->update(array('id' => $subtask['id'], 'status' => SubtaskModel::STATUS_DONE)); + $results[] = $this->subtaskTimeTrackingModel->logEndTime($subtask['id'], $subtask['user_id']); + } + + return !in_array(false, $results, true); + } + + /** + * 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['task']['column_id'] == $this->getParam('column_id'); + } +} diff --git a/app/Action/SubtaskTimerMoveTaskColumn.php b/app/Action/SubtaskTimerMoveTaskColumn.php new file mode 100644 index 0000000..896f24b --- /dev/null +++ b/app/Action/SubtaskTimerMoveTaskColumn.php @@ -0,0 +1,107 @@ + t('Column'), + 'subtask' => t('Subtask Title'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'id', + 'column_id', + 'project_id', + 'creator_id', + ), + ); + } + + /** + * Execute the action (append to the task description). + * + * @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) + { + $subtaskID = $this->subtaskModel->create(array( + 'title' => $this->getParam('subtask'), + 'user_id' => $data['task']['creator_id'], + 'task_id' => $data['task']['id'], + 'status' => SubtaskModel::STATUS_INPROGRESS, + )); + + if ($subtaskID !== false) { + return $this->subtaskTimeTrackingModel->toggleTimer($subtaskID, $data['task']['creator_id'], SubtaskModel::STATUS_INPROGRESS); + } + + return 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['task']['column_id'] == $this->getParam('column_id'); + } +} diff --git a/app/Action/TaskAssignCategoryColor.php b/app/Action/TaskAssignCategoryColor.php new file mode 100644 index 0000000..9228e1f --- /dev/null +++ b/app/Action/TaskAssignCategoryColor.php @@ -0,0 +1,98 @@ + t('Color'), + 'category_id' => t('Category'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + 'color_id', + ), + ); + } + + /** + * Execute the action (change the category) + * + * @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'], + 'category_id' => $this->getParam('category_id'), + ); + + return $this->taskModificationModel->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['task']['color_id'] == $this->getParam('color_id'); + } +} diff --git a/app/Action/TaskAssignCategoryLabel.php b/app/Action/TaskAssignCategoryLabel.php new file mode 100644 index 0000000..c390414 --- /dev/null +++ b/app/Action/TaskAssignCategoryLabel.php @@ -0,0 +1,91 @@ + t('Label'), + 'category_id' => t('Category'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'label', + ); + } + + /** + * Execute the action (change the category) + * + * @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'], + 'category_id' => $this->getParam('category_id'), + ); + + return $this->taskModificationModel->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['label'] == $this->getParam('label') && empty($data['category_id']); + } +} diff --git a/app/Action/TaskAssignCategoryLink.php b/app/Action/TaskAssignCategoryLink.php new file mode 100644 index 0000000..6c4b6c9 --- /dev/null +++ b/app/Action/TaskAssignCategoryLink.php @@ -0,0 +1,102 @@ + t('Category'), + 'link_id' => t('Link type'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_link' => array( + 'task_id', + 'link_id', + ) + ); + } + + /** + * Execute the action (change the category) + * + * @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_link']['task_id'], + 'category_id' => $this->getParam('category_id'), + ); + + return $this->taskModificationModel->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) + { + if ($data['task_link']['link_id'] == $this->getParam('link_id')) { + return empty($data['task']['category_id']); + } + + return false; + } +} diff --git a/app/Action/TaskAssignCategorySwimlaneChange.php b/app/Action/TaskAssignCategorySwimlaneChange.php new file mode 100644 index 0000000..3299858 --- /dev/null +++ b/app/Action/TaskAssignCategorySwimlaneChange.php @@ -0,0 +1,99 @@ + t('Swimlane'), + 'category_id' => t('Category'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + 'swimlane_id', + ) + ); + } + + /** + * Execute the action (set the task category) + * + * @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'], + 'category_id' => $this->getParam('category_id'), + ); + + return $this->taskModificationModel->update($values, 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['task']['swimlane_id'] == $this->getParam('swimlane_id'); + } +} diff --git a/app/Action/TaskAssignColorCategory.php b/app/Action/TaskAssignColorCategory.php new file mode 100644 index 0000000..a136ffd --- /dev/null +++ b/app/Action/TaskAssignColorCategory.php @@ -0,0 +1,98 @@ + t('Color'), + 'category_id' => t('Category'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + 'category_id', + ), + ); + } + + /** + * Execute the action (change 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->taskModificationModel->update($values, 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['task']['category_id'] == $this->getParam('category_id'); + } +} diff --git a/app/Action/TaskAssignColorColumn.php b/app/Action/TaskAssignColorColumn.php new file mode 100644 index 0000000..da6e3ae --- /dev/null +++ b/app/Action/TaskAssignColorColumn.php @@ -0,0 +1,99 @@ + t('Column'), + 'color_id' => t('Color'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_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->taskModificationModel->update($values, 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['task']['column_id'] == $this->getParam('column_id'); + } +} diff --git a/app/Action/TaskAssignColorLink.php b/app/Action/TaskAssignColorLink.php new file mode 100644 index 0000000..19c37af --- /dev/null +++ b/app/Action/TaskAssignColorLink.php @@ -0,0 +1,97 @@ + t('Color'), + 'link_id' => t('Link type'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_link' => array( + 'task_id', + 'link_id', + ) + ); + } + + /** + * Execute the action (change 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_link']['task_id'], + 'color_id' => $this->getParam('color_id'), + ); + + return $this->taskModificationModel->update($values, 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['task_link']['link_id'] == $this->getParam('link_id'); + } +} diff --git a/app/Action/TaskAssignColorOnDueDate.php b/app/Action/TaskAssignColorOnDueDate.php new file mode 100644 index 0000000..dfec317 --- /dev/null +++ b/app/Action/TaskAssignColorOnDueDate.php @@ -0,0 +1,99 @@ + t('Color'), + ); + } + + /** + * Get all tasks + * + * @access public + * @return array + */ + + public function getEventRequiredParameters() + { + return array('tasks'); + } + + /** + * Execute the action (change 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) + { + $results = array(); + + foreach ($data['tasks'] as $task) { + if ($task['date_due'] <= time() && $task['date_due'] > 0 && $task['color_id'] != $this->getParam('color_id')) { + $values = array( + 'id' => $task['id'], + 'color_id' => $this->getParam('color_id'), + ); + $results[] = $this->taskModificationModel->update($values, false); + } + } + + return in_array(true, $results, true); + } + + /** + * 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 count($data['tasks']) > 0; + } +} diff --git a/app/Action/TaskAssignColorOnStartDate.php b/app/Action/TaskAssignColorOnStartDate.php new file mode 100644 index 0000000..364c283 --- /dev/null +++ b/app/Action/TaskAssignColorOnStartDate.php @@ -0,0 +1,99 @@ + t('Color'), + ); + } + + /** + * Get all tasks + * + * @access public + * @return array + */ + + public function getEventRequiredParameters() + { + return array('tasks'); + } + + /** + * Execute the action (change 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) + { + $results = array(); + + foreach ($data['tasks'] as $task) { + if ($task['date_started'] <= time() && $task['date_started'] > 0 && $task['color_id'] != $this->getParam('color_id')) { + $values = array( + 'id' => $task['id'], + 'color_id' => $this->getParam('color_id'), + ); + $results[] = $this->taskModificationModel->update($values, false); + } + } + + return in_array(true, $results, true); + } + + /** + * 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 count($data['tasks']) > 0; + } +} diff --git a/app/Action/TaskAssignColorPriority.php b/app/Action/TaskAssignColorPriority.php new file mode 100644 index 0000000..37f7ffe --- /dev/null +++ b/app/Action/TaskAssignColorPriority.php @@ -0,0 +1,98 @@ + t('Color'), + 'priority' => t('Priority'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + 'priority', + ), + ); + } + + /** + * Execute the action (change 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->taskModificationModel->update($values, 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['task']['priority'] == $this->getParam('priority'); + } +} diff --git a/app/Action/TaskAssignColorSwimlane.php b/app/Action/TaskAssignColorSwimlane.php new file mode 100644 index 0000000..31f2d25 --- /dev/null +++ b/app/Action/TaskAssignColorSwimlane.php @@ -0,0 +1,99 @@ + t('Swimlane'), + 'color_id' => t('Color'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + 'swimlane_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->taskModificationModel->update($values, 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['task']['swimlane_id'] == $this->getParam('swimlane_id'); + } +} diff --git a/app/Action/TaskAssignColorUser.php b/app/Action/TaskAssignColorUser.php new file mode 100644 index 0000000..468d019 --- /dev/null +++ b/app/Action/TaskAssignColorUser.php @@ -0,0 +1,99 @@ + t('Color'), + 'user_id' => t('Assignee'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + 'owner_id', + ), + ); + } + + /** + * Execute the action (change 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->taskModificationModel->update($values, 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['task']['owner_id'] == $this->getParam('user_id'); + } +} diff --git a/app/Action/TaskAssignCreator.php b/app/Action/TaskAssignCreator.php new file mode 100644 index 0000000..ce10b35 --- /dev/null +++ b/app/Action/TaskAssignCreator.php @@ -0,0 +1,98 @@ + t('Column'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + 'column_id', + 'creator_id', + ), + ); + } + + /** + * Execute the action + * + * @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'], + 'owner_id' => $data['task']['creator_id'], + ); + + return $this->taskModificationModel->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['task']['column_id'] == $this->getParam('column_id'); + } +} diff --git a/app/Action/TaskAssignCurrentUser.php b/app/Action/TaskAssignCurrentUser.php new file mode 100644 index 0000000..dee5e7d --- /dev/null +++ b/app/Action/TaskAssignCurrentUser.php @@ -0,0 +1,95 @@ +userSession->isLogged()) { + return false; + } + + $values = array( + 'id' => $data['task_id'], + 'owner_id' => $this->userSession->getId(), + ); + + return $this->taskModificationModel->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 true; + } +} diff --git a/app/Action/TaskAssignCurrentUserColumn.php b/app/Action/TaskAssignCurrentUserColumn.php new file mode 100644 index 0000000..60ada7e --- /dev/null +++ b/app/Action/TaskAssignCurrentUserColumn.php @@ -0,0 +1,101 @@ + t('Column'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + 'column_id', + ), + ); + } + + /** + * Execute the action + * + * @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) + { + if (! $this->userSession->isLogged()) { + return false; + } + + $values = array( + 'id' => $data['task_id'], + 'owner_id' => $this->userSession->getId(), + ); + + return $this->taskModificationModel->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['task']['column_id'] == $this->getParam('column_id'); + } +} diff --git a/app/Action/TaskAssignCurrentUserColumnIfNoUserAlreadySet.php b/app/Action/TaskAssignCurrentUserColumnIfNoUserAlreadySet.php new file mode 100644 index 0000000..23d9e1c --- /dev/null +++ b/app/Action/TaskAssignCurrentUserColumnIfNoUserAlreadySet.php @@ -0,0 +1,103 @@ + t('Column'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + 'column_id', + 'owner_id', + ), + ); + } + + /** + * Execute the action + * + * @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) + { + if (!$this->userSession->isLogged()) { + return false; + } + + if (!$data['task']['owner_id']) { + $values = array( + 'id' => $data['task_id'], + 'owner_id' => $this->userSession->getId(), + ); + return $this->taskModificationModel->update($values); + } + return 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['task']['column_id'] == $this->getParam('column_id'); + } +} diff --git a/app/Action/TaskAssignDueDateOnCreation.php b/app/Action/TaskAssignDueDateOnCreation.php new file mode 100644 index 0000000..5c6e2b6 --- /dev/null +++ b/app/Action/TaskAssignDueDateOnCreation.php @@ -0,0 +1,96 @@ + t('Duration in days') + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_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'], + 'date_due' => strtotime('+'.$this->getParam('duration').'days'), + ); + + return $this->taskModificationModel->update($values, 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 true; + } +} diff --git a/app/Action/TaskAssignDueDateOnMoveColumn.php b/app/Action/TaskAssignDueDateOnMoveColumn.php new file mode 100644 index 0000000..103be27 --- /dev/null +++ b/app/Action/TaskAssignDueDateOnMoveColumn.php @@ -0,0 +1,97 @@ + t('Duration in days'), + 'column_id' => t('Column'), + ); + } + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + ), + 'src_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'], + 'date_due' => strtotime('+'.$this->getParam('duration').'days'), + ); + + return $this->taskModificationModel->update($values, 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 !empty($data['src_column_id']) && $data['src_column_id'] == $this->getParam('column_id'); + } +} diff --git a/app/Action/TaskAssignPrioritySwimlane.php b/app/Action/TaskAssignPrioritySwimlane.php new file mode 100644 index 0000000..7eaca7c --- /dev/null +++ b/app/Action/TaskAssignPrioritySwimlane.php @@ -0,0 +1,99 @@ + t('Swimlane'), + 'priority' => t('Priority'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + 'swimlane_id', + ), + ); + } + + /** + * Execute the action (set the priority) + * + * @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'], + 'priority' => $this->getParam('priority'), + ); + + return $this->taskModificationModel->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['task']['swimlane_id'] == $this->getParam('swimlane_id'); + } +} diff --git a/app/Action/TaskAssignSpecificUser.php b/app/Action/TaskAssignSpecificUser.php new file mode 100644 index 0000000..daf9e1d --- /dev/null +++ b/app/Action/TaskAssignSpecificUser.php @@ -0,0 +1,99 @@ + t('Column'), + 'user_id' => t('Assignee'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + 'column_id', + ), + ); + } + + /** + * Execute the action (assign the given user) + * + * @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'], + 'owner_id' => $this->getParam('user_id'), + ); + + return $this->taskModificationModel->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['task']['column_id'] == $this->getParam('column_id'); + } +} diff --git a/app/Action/TaskAssignToUserOnCreationInColumn.php b/app/Action/TaskAssignToUserOnCreationInColumn.php new file mode 100644 index 0000000..6501b5a --- /dev/null +++ b/app/Action/TaskAssignToUserOnCreationInColumn.php @@ -0,0 +1,109 @@ + t('Column'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + 'column_id', + 'creator_id', + ), + ); + } + + /** + * Execute the action + * + * @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) + { + + $assignee_id = $this->userModel->getIdByUsername($data['task']['assignee_username']); + + if ($data['task']['assignee_username']) { + $values = array( + 'id' => $data['task_id'], + 'owner_id' => $assignee_id, + ); + return $this->taskModificationModel->update($values); + } + + $values = array( + 'id' => $data['task_id'], + 'owner_id' => $data['task']['creator_id'], + ); + + return $this->taskModificationModel->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['task']['column_id'] == $this->getParam('column_id'); + } +} diff --git a/app/Action/TaskAssignUser.php b/app/Action/TaskAssignUser.php new file mode 100644 index 0000000..8727b67 --- /dev/null +++ b/app/Action/TaskAssignUser.php @@ -0,0 +1,88 @@ + $data['task_id'], + 'owner_id' => $data['owner_id'], + ); + + return $this->taskModificationModel->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 $this->projectPermissionModel->isAssignable($this->getProjectId(), $data['owner_id']); + } +} diff --git a/app/Action/TaskAssignUserSwimlaneChange.php b/app/Action/TaskAssignUserSwimlaneChange.php new file mode 100644 index 0000000..3f97b20 --- /dev/null +++ b/app/Action/TaskAssignUserSwimlaneChange.php @@ -0,0 +1,99 @@ + t('Swimlane'), + 'user_id' => t('Assignee'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + 'swimlane_id', + ) + ); + } + + /** + * Execute the action (set the task category) + * + * @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'], + 'owner_id' => $this->getParam('user_id'), + ); + + return $this->taskModificationModel->update($values, 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['task']['swimlane_id'] == $this->getParam('swimlane_id'); + } +} diff --git a/app/Action/TaskClose.php b/app/Action/TaskClose.php new file mode 100644 index 0000000..e476e9b --- /dev/null +++ b/app/Action/TaskClose.php @@ -0,0 +1,80 @@ +taskStatusModel->close($data['task_id']); + } + + /** + * 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 true; + } +} diff --git a/app/Action/TaskCloseColumn.php b/app/Action/TaskCloseColumn.php new file mode 100644 index 0000000..523996f --- /dev/null +++ b/app/Action/TaskCloseColumn.php @@ -0,0 +1,90 @@ + t('Column')); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + 'column_id', + ) + ); + } + + /** + * Execute the action (close the task) + * + * @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) + { + return $this->taskStatusModel->close($data['task_id']); + } + + /** + * 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['task']['column_id'] == $this->getParam('column_id'); + } +} diff --git a/app/Action/TaskCloseNoActivity.php b/app/Action/TaskCloseNoActivity.php new file mode 100644 index 0000000..b1f75a8 --- /dev/null +++ b/app/Action/TaskCloseNoActivity.php @@ -0,0 +1,95 @@ + t('Duration in days') + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array('tasks'); + } + + /** + * Execute the action (close the task) + * + * @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) + { + $results = array(); + $max = (int)$this->getParam('duration') * 86400; + + foreach ($data['tasks'] as $task) { + $duration = time() - $task['date_modification']; + + if ($duration > $max) { + $results[] = $this->taskStatusModel->close($task['id']); + } + } + + return in_array(true, $results, true); + } + + /** + * 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 count($data['tasks']) > 0; + } +} diff --git a/app/Action/TaskCloseNoActivityColumn.php b/app/Action/TaskCloseNoActivityColumn.php new file mode 100644 index 0000000..10c9fc0 --- /dev/null +++ b/app/Action/TaskCloseNoActivityColumn.php @@ -0,0 +1,96 @@ + t('Duration in days'), + 'column_id' => t('Column') + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array('tasks'); + } + + /** + * Execute the action (close the task) + * + * @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) + { + $results = array(); + $max = (int)$this->getParam('duration') * 86400; + + foreach ($data['tasks'] as $task) { + $duration = time() - $task['date_modification']; + + if ($duration > $max && $task['column_id'] == $this->getParam('column_id')) { + $results[] = $this->taskStatusModel->close($task['id']); + } + } + + return in_array(true, $results, true); + } + + /** + * 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 count($data['tasks']) > 0; + } +} diff --git a/app/Action/TaskCloseNotMovedColumn.php b/app/Action/TaskCloseNotMovedColumn.php new file mode 100644 index 0000000..dd002dd --- /dev/null +++ b/app/Action/TaskCloseNotMovedColumn.php @@ -0,0 +1,96 @@ + t('Duration in days'), + 'column_id' => t('Column') + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array('tasks'); + } + + /** + * Execute the action (close the task) + * + * @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) + { + $results = array(); + $max = (int)$this->getParam('duration') * 86400; + + foreach ($data['tasks'] as $task) { + $duration = time() - $task['date_moved']; + + if ($duration > $max && $task['column_id'] == $this->getParam('column_id')) { + $results[] = $this->taskStatusModel->close($task['id']); + } + } + + return in_array(true, $results, true); + } + + /** + * 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 count($data['tasks']) > 0; + } +} diff --git a/app/Action/TaskCreation.php b/app/Action/TaskCreation.php new file mode 100644 index 0000000..01d9122 --- /dev/null +++ b/app/Action/TaskCreation.php @@ -0,0 +1,89 @@ +taskCreationModel->create(array( + 'project_id' => $data['project_id'], + 'title' => $data['title'], + 'reference' => $data['reference'], + 'description' => isset($data['description']) ? $data['description'] : '', + )); + } + + /** + * 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 true; + } +} diff --git a/app/Action/TaskDuplicateAnotherProject.php b/app/Action/TaskDuplicateAnotherProject.php new file mode 100644 index 0000000..0ad7713 --- /dev/null +++ b/app/Action/TaskDuplicateAnotherProject.php @@ -0,0 +1,101 @@ + t('Column'), + 'project_id' => t('Project'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + 'column_id', + ) + ); + } + + /** + * Execute the action (duplicate the task to another project) + * + * @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) + { + $destination_column_id = $this->columnModel->getFirstColumnId($this->getParam('project_id')); + return (bool) $this->taskProjectDuplicationModel->duplicateToProject( + $data['task_id'], + $this->getParam('project_id'), + null, + $destination_column_id + ); + } + + /** + * 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['task']['column_id'] == $this->getParam('column_id') && $data['task']['project_id'] != $this->getParam('project_id'); + } +} diff --git a/app/Action/TaskEmail.php b/app/Action/TaskEmail.php new file mode 100644 index 0000000..13210a7 --- /dev/null +++ b/app/Action/TaskEmail.php @@ -0,0 +1,119 @@ + t('Column'), + 'user_id' => t('User that will receive the email'), + 'subject' => t('Email subject'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + 'column_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) + { + $user = $this->userModel->getById($this->getParam('user_id')); + $subject = $this->getParam('subject'); + + foreach ($data["task"] as $key => $value) { + if ($value !== null) { + $placeholder = sprintf('{{%s}}', $key); + $subject = str_replace($placeholder, $value, $subject); + } + } + + if (! empty($user['email'])) { + $this->emailClient->send( + $user['email'], + $user['name'] ?: $user['username'], + $subject, + $this->template->render('notification/task_create', array( + 'task' => $data['task'], + )) + ); + + return true; + } + + return 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['task']['column_id'] == $this->getParam('column_id'); + } +} diff --git a/app/Action/TaskEmailNoActivity.php b/app/Action/TaskEmailNoActivity.php new file mode 100644 index 0000000..9d59ac8 --- /dev/null +++ b/app/Action/TaskEmailNoActivity.php @@ -0,0 +1,124 @@ + t('User that will receive the email'), + 'subject' => t('Email subject'), + 'duration' => t('Duration in days'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array('tasks'); + } + + /** + * 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 count($data['tasks']) > 0; + } + + /** + * 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) + { + $results = array(); + $max = (int)$this->getParam('duration') * 86400; + $user = $this->userModel->getById($this->getParam('user_id')); + + if (! empty($user['email'])) { + foreach ($data['tasks'] as $task) { + $duration = time() - $task['date_modification']; + + if ($duration > $max) { + $results[] = $this->sendEmail($task['id'], $user); + } + } + } + + return in_array(true, $results, true); + } + + /** + * Send email + * + * @access private + * @param integer $task_id + * @param array $user + * @return boolean + */ + private function sendEmail($task_id, array $user) + { + $task = $this->taskFinderModel->getDetails($task_id); + + $this->emailClient->send( + $user['email'], + $user['name'] ?: $user['username'], + $this->getParam('subject'), + $this->template->render('notification/task_create', array('task' => $task)) + ); + + return true; + } +} diff --git a/app/Action/TaskMoveAnotherProject.php b/app/Action/TaskMoveAnotherProject.php new file mode 100644 index 0000000..0fa22b1 --- /dev/null +++ b/app/Action/TaskMoveAnotherProject.php @@ -0,0 +1,94 @@ + t('Column'), + 'project_id' => t('Project'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + 'column_id', + ) + ); + } + + /** + * Execute the action (move the task to another project) + * + * @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) + { + return $this->taskProjectMoveModel->moveToProject($data['task_id'], $this->getParam('project_id')); + } + + /** + * 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['task']['column_id'] == $this->getParam('column_id') && $data['task']['project_id'] != $this->getParam('project_id'); + } +} diff --git a/app/Action/TaskMoveColumnAssigned.php b/app/Action/TaskMoveColumnAssigned.php new file mode 100644 index 0000000..1cfe674 --- /dev/null +++ b/app/Action/TaskMoveColumnAssigned.php @@ -0,0 +1,104 @@ + 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', + 'task' => array( + 'project_id', + 'column_id', + 'owner_id', + 'position', + 'swimlane_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) + { + return $this->taskPositionModel->movePosition( + $data['task']['project_id'], + $data['task_id'], + $this->getParam('dest_column_id'), + $data['task']['position'], + $data['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['task']['column_id'] == $this->getParam('src_column_id') && $data['task']['owner_id'] > 0; + } +} diff --git a/app/Action/TaskMoveColumnCategoryChange.php b/app/Action/TaskMoveColumnCategoryChange.php new file mode 100644 index 0000000..58108ea --- /dev/null +++ b/app/Action/TaskMoveColumnCategoryChange.php @@ -0,0 +1,105 @@ + t('Destination column'), + 'category_id' => t('Category'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + 'column_id', + 'category_id', + 'position', + 'swimlane_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) + { + return $this->taskPositionModel->movePosition( + $data['task']['project_id'], + $data['task_id'], + $this->getParam('dest_column_id'), + $data['task']['position'], + $data['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['task']['column_id'] != $this->getParam('dest_column_id') && $data['task']['category_id'] == $this->getParam('category_id'); + } +} diff --git a/app/Action/TaskMoveColumnClosed.php b/app/Action/TaskMoveColumnClosed.php new file mode 100644 index 0000000..da86d89 --- /dev/null +++ b/app/Action/TaskMoveColumnClosed.php @@ -0,0 +1,102 @@ + t('Destination column'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + 'column_id', + 'swimlane_id', + 'is_active', + ) + ); + } + + /** + * 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) + { + return $this->taskPositionModel->movePosition( + $data['task']['project_id'], + $data['task']['id'], + $this->getParam('dest_column_id'), + 1, + $data['task']['swimlane_id'], + true, + 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['task']['column_id'] != $this->getParam('dest_column_id') && $data['task']['is_active'] == 0; + } +} diff --git a/app/Action/TaskMoveColumnNotMovedPeriod.php b/app/Action/TaskMoveColumnNotMovedPeriod.php new file mode 100644 index 0000000..db7617a --- /dev/null +++ b/app/Action/TaskMoveColumnNotMovedPeriod.php @@ -0,0 +1,104 @@ + t('Duration in days'), + 'src_column_id' => 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('tasks'); + } + + /** + * Execute the action (close the task) + * + * @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) + { + $results = array(); + $max = (int)$this->getParam('duration') * 86400; + + foreach ($data['tasks'] as $task) { + $duration = time() - $task['date_moved']; + + if ($duration > $max && $task['column_id'] == $this->getParam('src_column_id')) { + $results[] = $this->taskPositionModel->movePosition( + $task['project_id'], + $task['id'], + $this->getParam('dest_column_id'), + 1, + $task['swimlane_id'], + false + ); + } + } + + return in_array(true, $results, true); + } + + /** + * 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 count($data['tasks']) > 0; + } +} diff --git a/app/Action/TaskMoveColumnOnDueDate.php b/app/Action/TaskMoveColumnOnDueDate.php new file mode 100644 index 0000000..3f91dd0 --- /dev/null +++ b/app/Action/TaskMoveColumnOnDueDate.php @@ -0,0 +1,103 @@ + t('Duration in days'), + 'src_column_id' => 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('tasks'); + } + + /** + * Execute the action (close the task) + * + * @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) + { + $results = array(); + $min = (int)$this->getParam('duration') * 86400; + + foreach ($data['tasks'] as $task) { + $duration = $task['date_due'] - time(); + + if ($task['date_due'] > 0 && $duration < $min && $task['column_id'] == $this->getParam('src_column_id')) { + $results[] = $this->taskPositionModel->movePosition( + $task['project_id'], + $task['id'], + $this->getParam('dest_column_id'), + 1, + $task['swimlane_id'], + true + ); + } + } + + return in_array(true, $results, true); + } + + /** + * 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 count($data['tasks']) > 0; + } +} diff --git a/app/Action/TaskMoveColumnOnStartDate.php b/app/Action/TaskMoveColumnOnStartDate.php new file mode 100644 index 0000000..e4f4dc1 --- /dev/null +++ b/app/Action/TaskMoveColumnOnStartDate.php @@ -0,0 +1,101 @@ + 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('tasks'); + } + + /** + * Execute the action (close the task) + * + * @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) + { + $results = array(); + + foreach ($data['tasks'] as $task) { + + if ($task['date_started'] <= time() && $task['date_started'] > 0 && $task['column_id'] == $this->getParam('src_column_id')) { + $results[] = $this->taskPositionModel->movePosition( + $task['project_id'], + $task['id'], + $this->getParam('dest_column_id'), + 1, + $task['swimlane_id'], + false + ); + } + } + + return in_array(true, $results, true); + } + + /** + * 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 count($data['tasks']) > 0; + } +} diff --git a/app/Action/TaskMoveColumnUnAssigned.php b/app/Action/TaskMoveColumnUnAssigned.php new file mode 100644 index 0000000..ab63d62 --- /dev/null +++ b/app/Action/TaskMoveColumnUnAssigned.php @@ -0,0 +1,104 @@ + 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', + 'task' => array( + 'project_id', + 'column_id', + 'owner_id', + 'position', + 'swimlane_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) + { + return $this->taskPositionModel->movePosition( + $data['task']['project_id'], + $data['task_id'], + $this->getParam('dest_column_id'), + $data['task']['position'], + $data['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['task']['column_id'] == $this->getParam('src_column_id') && $data['task']['owner_id'] == 0; + } +} diff --git a/app/Action/TaskMoveSwimlaneAssigned.php b/app/Action/TaskMoveSwimlaneAssigned.php new file mode 100644 index 0000000..f9fafd3 --- /dev/null +++ b/app/Action/TaskMoveSwimlaneAssigned.php @@ -0,0 +1,107 @@ + t('Assignee'), + 'dest_swimlane_id' => t('Destination swimlane'), + + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + 'column_id', + 'owner_id', + 'position', + 'swimlane_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) + { + return $this->taskPositionModel->movePosition( + $data['task']['project_id'], + $data['task_id'], + $data['task']['column_id'], + $data['task']['position'], + $this->getParam('dest_swimlane_id'), + true + ); + } + + /** + * 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['task']['swimlane_id'] != $this->getParam('dest_swimlane_id') && $data['task']['owner_id'] == $this->getParam("user_id"); + } +} diff --git a/app/Action/TaskMoveSwimlaneCategoryChange.php b/app/Action/TaskMoveSwimlaneCategoryChange.php new file mode 100644 index 0000000..4e80dd8 --- /dev/null +++ b/app/Action/TaskMoveSwimlaneCategoryChange.php @@ -0,0 +1,103 @@ + t('Destination swimlane'), + 'category_id' => t('Category'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + 'column_id', + 'category_id', + 'position', + 'swimlane_id', + ) + ); + } + + /** + * Execute the action (move the task to another swimlane) + * + * @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) + { + return $this->taskPositionModel->movePosition( + $data['task']['project_id'], + $data['task_id'], + $data['task']['column_id'], + $data['task']['position'], + $this->getParam('dest_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['task']['swimlane_id'] != $this->getParam('dest_swimlane_id') && $data['task']['category_id'] == $this->getParam('category_id'); + } +} diff --git a/app/Action/TaskOpen.php b/app/Action/TaskOpen.php new file mode 100644 index 0000000..4901783 --- /dev/null +++ b/app/Action/TaskOpen.php @@ -0,0 +1,80 @@ +taskStatusModel->open($data['task_id']); + } + + /** + * 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 true; + } +} diff --git a/app/Action/TaskUpdateStartDate.php b/app/Action/TaskUpdateStartDate.php new file mode 100644 index 0000000..160f6ee --- /dev/null +++ b/app/Action/TaskUpdateStartDate.php @@ -0,0 +1,97 @@ + t('Column'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_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'], + 'date_started' => time(), + ); + + return $this->taskModificationModel->update($values, 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['task']['column_id'] == $this->getParam('column_id'); + } +} diff --git a/app/Action/TaskUpdateStartDateOnMoveColumn.php b/app/Action/TaskUpdateStartDateOnMoveColumn.php new file mode 100644 index 0000000..2255e83 --- /dev/null +++ b/app/Action/TaskUpdateStartDateOnMoveColumn.php @@ -0,0 +1,96 @@ + t('Column'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + ), + 'src_column_id', + ); + } + + /** + * Execute the action (set the task date_started) + * + * @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'], + 'date_started' => time(), + ); + + return $this->taskModificationModel->update($values, 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 empty($data['task']['date_started']) && $data['src_column_id'] == $this->getParam('column_id'); + } +} diff --git a/app/Analytic/AverageLeadCycleTimeAnalytic.php b/app/Analytic/AverageLeadCycleTimeAnalytic.php new file mode 100644 index 0000000..d6cd1f8 --- /dev/null +++ b/app/Analytic/AverageLeadCycleTimeAnalytic.php @@ -0,0 +1,116 @@ + 0, + 'total_lead_time' => 0, + 'total_cycle_time' => 0, + 'avg_lead_time' => 0, + 'avg_cycle_time' => 0, + ); + + $tasks = $this->getTasks($project_id); + + foreach ($tasks as &$task) { + $stats['count']++; + $stats['total_lead_time'] += $this->calculateLeadTime($task); + $stats['total_cycle_time'] += $this->calculateCycleTime($task); + } + + $stats['avg_lead_time'] = $this->calculateAverage($stats, 'total_lead_time'); + $stats['avg_cycle_time'] = $this->calculateAverage($stats, 'total_cycle_time'); + + return $stats; + } + + /** + * Calculate average + * + * @access private + * @param array &$stats + * @param string $field + * @return float + */ + private function calculateAverage(array &$stats, $field) + { + if ($stats['count'] > 0) { + return (int) ($stats[$field] / $stats['count']); + } + + return 0; + } + + /** + * Calculate lead time + * + * @access private + * @param array &$task + * @return integer + */ + private function calculateLeadTime(array &$task) + { + $end = $task['date_completed'] ?: time(); + $start = $task['date_creation']; + + return $end - $start; + } + + /** + * Calculate cycle time + * + * @access private + * @param array &$task + * @return integer + */ + private function calculateCycleTime(array &$task) + { + $end = (int) $task['date_completed'] ?: time(); + $start = (int) $task['date_started']; + + // Start date can be in the future when defined with the Gantt chart + if ($start > 0 && $end > $start) { + return $end - $start; + } + + return 0; + } + + /** + * Get the 1000 last created tasks + * + * @access private + * @param integer $project_id + * @return array + */ + private function getTasks($project_id) + { + return $this->db + ->table(TaskModel::TABLE) + ->columns('date_completed', 'date_creation', 'date_started') + ->eq('project_id', $project_id) + ->desc('id') + ->limit(1000) + ->findAll(); + } +} diff --git a/app/Analytic/AverageTimeSpentColumnAnalytic.php b/app/Analytic/AverageTimeSpentColumnAnalytic.php new file mode 100644 index 0000000..3556fb9 --- /dev/null +++ b/app/Analytic/AverageTimeSpentColumnAnalytic.php @@ -0,0 +1,154 @@ +initialize($project_id); + + $this->processTasks($stats, $project_id); + $this->calculateAverage($stats); + + return $stats; + } + + /** + * Initialize default values for each column + * + * @access private + * @param integer $project_id + * @return array + */ + private function initialize($project_id) + { + $stats = array(); + $columns = $this->columnModel->getList($project_id); + + foreach ($columns as $column_id => $column_title) { + $stats[$column_id] = array( + 'count' => 0, + 'time_spent' => 0, + 'average' => 0, + 'title' => $column_title, + ); + } + + return $stats; + } + + /** + * Calculate time spent for each tasks for each columns + * + * @access private + * @param array $stats + * @param integer $project_id + */ + private function processTasks(array &$stats, $project_id) + { + $tasks = $this->getTasks($project_id); + + foreach ($tasks as &$task) { + foreach ($this->getTaskTimeByColumns($task) as $column_id => $time_spent) { + if (isset($stats[$column_id])) { + $stats[$column_id]['count']++; + $stats[$column_id]['time_spent'] += $time_spent; + } + } + } + } + + /** + * Calculate averages + * + * @access private + * @param array $stats + */ + private function calculateAverage(array &$stats) + { + foreach ($stats as &$column) { + $this->calculateColumnAverage($column); + } + } + + /** + * Calculate column average + * + * @access private + * @param array $column + */ + private function calculateColumnAverage(array &$column) + { + if ($column['count'] > 0) { + $column['average'] = (int) ($column['time_spent'] / $column['count']); + } + } + + /** + * Get time spent for each column for a given task + * + * @access private + * @param array $task + * @return array + */ + private function getTaskTimeByColumns(array &$task) + { + $columns = $this->transitionModel->getTimeSpentByTask($task['id']); + + if (! isset($columns[$task['column_id']])) { + $columns[$task['column_id']] = 0; + } + + $columns[$task['column_id']] += $this->getTaskTimeSpentInCurrentColumn($task); + + return $columns; + } + + /** + * Calculate time spent of a task in the current column + * + * @access private + * @param array $task + * @return integer + */ + private function getTaskTimeSpentInCurrentColumn(array &$task) + { + $end = $task['date_completed'] ?: time(); + return $end - $task['date_moved']; + } + + /** + * Fetch the last 1000 tasks + * + * @access private + * @param integer $project_id + * @return array + */ + private function getTasks($project_id) + { + return $this->db + ->table(TaskModel::TABLE) + ->columns('id', 'date_completed', 'date_moved', 'column_id') + ->eq('project_id', $project_id) + ->desc('id') + ->limit(1000) + ->findAll(); + } +} diff --git a/app/Analytic/EstimatedActualColumnAnalytic.php b/app/Analytic/EstimatedActualColumnAnalytic.php new file mode 100644 index 0000000..a0bc1d1 --- /dev/null +++ b/app/Analytic/EstimatedActualColumnAnalytic.php @@ -0,0 +1,49 @@ +db->table(TaskModel::TABLE) + ->columns('SUM(time_estimated) AS hours_estimated', 'SUM(time_spent) AS hours_spent', 'column_id') + ->eq('project_id', $project_id) + ->groupBy('column_id') + ->findAll(); + + $columns = $this->columnModel->getList($project_id); + + $metrics = []; + foreach ($columns as $column_id => $column_title) { + $metrics[$column_id] = array( + 'hours_spent' => 0, + 'hours_estimated' => 0, + 'title' => $column_title, + ); + } + + foreach ($rows as $row) { + $metrics[$row['column_id']]['hours_spent'] = (float) $row['hours_spent']; + $metrics[$row['column_id']]['hours_estimated'] = (float) $row['hours_estimated']; + } + + return $metrics; + } +} diff --git a/app/Analytic/EstimatedTimeComparisonAnalytic.php b/app/Analytic/EstimatedTimeComparisonAnalytic.php new file mode 100644 index 0000000..020478f --- /dev/null +++ b/app/Analytic/EstimatedTimeComparisonAnalytic.php @@ -0,0 +1,50 @@ +db->table(TaskModel::TABLE) + ->columns('SUM(time_estimated) AS time_estimated', 'SUM(time_spent) AS time_spent', 'is_active') + ->eq('project_id', $project_id) + ->groupBy('is_active') + ->findAll(); + + $metrics = array( + 'open' => array( + 'time_spent' => 0, + 'time_estimated' => 0, + ), + 'closed' => array( + 'time_spent' => 0, + 'time_estimated' => 0, + ), + ); + + foreach ($rows as $row) { + $key = $row['is_active'] == TaskModel::STATUS_OPEN ? 'open' : 'closed'; + $metrics[$key]['time_spent'] = (float) $row['time_spent']; + $metrics[$key]['time_estimated'] = (float) $row['time_estimated']; + } + + return $metrics; + } +} diff --git a/app/Analytic/TaskDistributionAnalytic.php b/app/Analytic/TaskDistributionAnalytic.php new file mode 100644 index 0000000..447d88a --- /dev/null +++ b/app/Analytic/TaskDistributionAnalytic.php @@ -0,0 +1,48 @@ +columnModel->getAll($project_id); + + foreach ($columns as $column) { + $nb_tasks = $this->taskFinderModel->countByColumnId($project_id, $column['id']); + $total += $nb_tasks; + + $metrics[] = array( + 'column_title' => $column['title'], + 'nb_tasks' => $nb_tasks, + ); + } + + if ($total === 0) { + return array(); + } + + foreach ($metrics as &$metric) { + $metric['percentage'] = round(($metric['nb_tasks'] * 100) / $total, 2); + } + + return $metrics; + } +} diff --git a/app/Analytic/UserDistributionAnalytic.php b/app/Analytic/UserDistributionAnalytic.php new file mode 100644 index 0000000..421d424 --- /dev/null +++ b/app/Analytic/UserDistributionAnalytic.php @@ -0,0 +1,56 @@ +taskFinderModel->getAll($project_id); + $users = $this->projectUserRoleModel->getAssignableUsersList($project_id); + + foreach ($tasks as $task) { + $user = isset($users[$task['owner_id']]) ? $users[$task['owner_id']] : $users[0]; + $total++; + + if (! isset($metrics[$user])) { + $metrics[$user] = array( + 'nb_tasks' => 0, + 'percentage' => 0, + 'user' => $user, + ); + } + + $metrics[$user]['nb_tasks']++; + } + + if ($total === 0) { + return array(); + } + + foreach ($metrics as &$metric) { + $metric['percentage'] = round(($metric['nb_tasks'] * 100) / $total, 2); + } + + ksort($metrics); + + return array_values($metrics); + } +} diff --git a/app/Api/Authorization/ActionAuthorization.php b/app/Api/Authorization/ActionAuthorization.php new file mode 100644 index 0000000..4b41ad8 --- /dev/null +++ b/app/Api/Authorization/ActionAuthorization.php @@ -0,0 +1,19 @@ +userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $this->actionModel->getProjectId($action_id)); + } + } +} diff --git a/app/Api/Authorization/CategoryAuthorization.php b/app/Api/Authorization/CategoryAuthorization.php new file mode 100644 index 0000000..f17265a --- /dev/null +++ b/app/Api/Authorization/CategoryAuthorization.php @@ -0,0 +1,19 @@ +userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $this->categoryModel->getProjectId($category_id)); + } + } +} diff --git a/app/Api/Authorization/ColumnAuthorization.php b/app/Api/Authorization/ColumnAuthorization.php new file mode 100644 index 0000000..37aecda --- /dev/null +++ b/app/Api/Authorization/ColumnAuthorization.php @@ -0,0 +1,19 @@ +userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $this->columnModel->getProjectId($column_id)); + } + } +} diff --git a/app/Api/Authorization/CommentAuthorization.php b/app/Api/Authorization/CommentAuthorization.php new file mode 100644 index 0000000..56125ac --- /dev/null +++ b/app/Api/Authorization/CommentAuthorization.php @@ -0,0 +1,46 @@ +userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $this->commentModel->getProjectId($comment_id)); + $this->checkCommentAccess($comment_id); + } + } + + /** + * @param $comment_id ID of the comment to check + * @return void + * @throws AccessDeniedException + */ + protected function checkCommentAccess($comment_id) + { + if (empty($comment_id)) { + throw new AccessDeniedException('Comment Not Found'); + } + + $commentVisibility = $this->commentModel->getVisibility($comment_id); + $userRole = $this->userSession->getRole(); + + if ($userRole === Role::APP_MANAGER && $commentVisibility === Role::APP_ADMIN) { + throw new AccessDeniedException('Comment Access Denied'); + } + + if ($userRole === Role::APP_USER && $commentVisibility !== Role::APP_USER) { + throw new AccessDeniedException('Comment Access Denied'); + } + } +} diff --git a/app/Api/Authorization/ProcedureAuthorization.php b/app/Api/Authorization/ProcedureAuthorization.php new file mode 100644 index 0000000..070a637 --- /dev/null +++ b/app/Api/Authorization/ProcedureAuthorization.php @@ -0,0 +1,32 @@ +userSession->isLogged() && in_array($procedure, $this->userSpecificProcedures)) { + throw new AccessDeniedException('This procedure is not available with the API credentials'); + } + } +} diff --git a/app/Api/Authorization/ProjectAuthorization.php b/app/Api/Authorization/ProjectAuthorization.php new file mode 100644 index 0000000..7dcdc44 --- /dev/null +++ b/app/Api/Authorization/ProjectAuthorization.php @@ -0,0 +1,35 @@ +userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $project_id); + } + } + + protected function checkProjectPermission($class, $method, $project_id) + { + if (empty($project_id)) { + throw new AccessDeniedException('Project Not Found'); + } + + $role = $this->projectUserRoleModel->getUserRole($project_id, $this->userSession->getId()); + + if (! $this->apiProjectAuthorization->isAllowed($class, $method, $role)) { + throw new AccessDeniedException('Project Access Denied'); + } + } +} diff --git a/app/Api/Authorization/SubtaskAuthorization.php b/app/Api/Authorization/SubtaskAuthorization.php new file mode 100644 index 0000000..fcb5792 --- /dev/null +++ b/app/Api/Authorization/SubtaskAuthorization.php @@ -0,0 +1,19 @@ +userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $this->subtaskModel->getProjectId($subtask_id)); + } + } +} diff --git a/app/Api/Authorization/TagAuthorization.php b/app/Api/Authorization/TagAuthorization.php new file mode 100644 index 0000000..247f57d --- /dev/null +++ b/app/Api/Authorization/TagAuthorization.php @@ -0,0 +1,23 @@ +userSession->isLogged()) { + $tag = $this->tagModel->getById($tag_id); + + if (! empty($tag)) { + $this->checkProjectPermission($class, $method, $tag['project_id']); + } + } + } +} diff --git a/app/Api/Authorization/TaskAuthorization.php b/app/Api/Authorization/TaskAuthorization.php new file mode 100644 index 0000000..6e04421 --- /dev/null +++ b/app/Api/Authorization/TaskAuthorization.php @@ -0,0 +1,19 @@ +userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $this->taskFinderModel->getProjectId($task_id)); + } + } +} diff --git a/app/Api/Authorization/TaskFileAuthorization.php b/app/Api/Authorization/TaskFileAuthorization.php new file mode 100644 index 0000000..e40783e --- /dev/null +++ b/app/Api/Authorization/TaskFileAuthorization.php @@ -0,0 +1,19 @@ +userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $this->taskFileModel->getProjectId($file_id)); + } + } +} diff --git a/app/Api/Authorization/TaskLinkAuthorization.php b/app/Api/Authorization/TaskLinkAuthorization.php new file mode 100644 index 0000000..2f5fc8d --- /dev/null +++ b/app/Api/Authorization/TaskLinkAuthorization.php @@ -0,0 +1,19 @@ +userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $this->taskLinkModel->getProjectId($task_link_id)); + } + } +} diff --git a/app/Api/Authorization/UserAuthorization.php b/app/Api/Authorization/UserAuthorization.php new file mode 100644 index 0000000..3fd6865 --- /dev/null +++ b/app/Api/Authorization/UserAuthorization.php @@ -0,0 +1,22 @@ +userSession->isLogged() && ! $this->apiAuthorization->isAllowed($class, $method, $this->userSession->getRole())) { + throw new AccessDeniedException('You are not allowed to access to this resource'); + } + } +} diff --git a/app/Api/Middleware/AuthenticationMiddleware.php b/app/Api/Middleware/AuthenticationMiddleware.php new file mode 100644 index 0000000..42ac208 --- /dev/null +++ b/app/Api/Middleware/AuthenticationMiddleware.php @@ -0,0 +1,101 @@ +dispatcher->dispatch(new Event, 'app.bootstrap'); + session_set('scope', 'API'); + + if ($this->isUserAuthenticated($username, $password)) { + $this->userSession->initialize($this->userCacheDecorator->getByUsername($username)); + } elseif (! $this->isAppAuthenticated($username, $password)) { + $this->logger->error('API authentication failure for '.$username); + throw new AuthenticationFailureException('Wrong credentials'); + } + } + + /** + * Check user credentials + * + * @access public + * @param string $username + * @param string $password + * @return boolean + */ + private function isUserAuthenticated($username, $password) + { + if ($username === 'jsonrpc') { + return false; + } + + if ($this->userLockingModel->isLocked($username)) { + return false; + } + + if ($this->userModel->has2FA($username)) { + $this->logger->info('This API user ('.$username.') as 2FA enabled: only API keys are authorized'); + $this->authenticationManager->reset(); + $this->authenticationManager->register(new ApiAccessTokenAuth($this->container)); + } + + return $this->authenticationManager->passwordAuthentication($username, $password); + } + + /** + * Check administrative credentials + * + * @access public + * @param string $username + * @param string $password + * @return boolean + */ + private function isAppAuthenticated($username, $password) + { + return $username === 'jsonrpc' && hash_equals($this->getApiToken(), $password); + } + + /** + * Get API Token + * + * @access private + * @return string + */ + private function getApiToken() + { + if (defined('API_AUTHENTICATION_TOKEN')) { + return API_AUTHENTICATION_TOKEN; + } + + if (getenv('API_AUTHENTICATION_TOKEN')) { + return getenv('API_AUTHENTICATION_TOKEN'); + } + + return $this->configModel->get('api_token'); + } +} diff --git a/app/Api/Procedure/ActionProcedure.php b/app/Api/Procedure/ActionProcedure.php new file mode 100644 index 0000000..72fb9bb --- /dev/null +++ b/app/Api/Procedure/ActionProcedure.php @@ -0,0 +1,91 @@ +actionManager->getAvailableActions(); + } + + public function getAvailableActionEvents() + { + return (object) $this->eventManager->getAll(); + } + + public function getCompatibleActionEvents($action_name) + { + return (object) $this->actionManager->getCompatibleEvents($action_name); + } + + public function removeAction($action_id) + { + ActionAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeAction', $action_id); + return $this->actionModel->remove($action_id); + } + + public function getActions($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getActions', $project_id); + return $this->actionModel->getAllByProject($project_id); + } + + public function createAction($project_id, $event_name, $action_name, array $params) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'createAction', $project_id); + $values = array( + 'project_id' => $project_id, + 'event_name' => $event_name, + 'action_name' => $action_name, + 'params' => $params, + ); + + list($valid, ) = $this->actionValidator->validateCreation($values); + + if (! $valid) { + return false; + } + + // Check if the action exists + $actions = $this->actionManager->getAvailableActions(); + + if (! isset($actions[$action_name])) { + return false; + } + + // Check the event + $action = $this->actionManager->getAction($action_name); + + if (! in_array($event_name, $action->getEvents())) { + return false; + } + + $required_params = $action->getActionRequiredParameters(); + + // Check missing parameters + foreach ($required_params as $param => $value) { + if (! isset($params[$param])) { + return false; + } + } + + // Check extra parameters + foreach ($params as $param => $value) { + if (! isset($required_params[$param])) { + return false; + } + } + + return $this->actionModel->create($values); + } +} diff --git a/app/Api/Procedure/AppProcedure.php b/app/Api/Procedure/AppProcedure.php new file mode 100644 index 0000000..60af4a6 --- /dev/null +++ b/app/Api/Procedure/AppProcedure.php @@ -0,0 +1,47 @@ +timezoneModel->getCurrentTimezone(); + } + + public function getVersion() + { + return APP_VERSION; + } + + public function getDefaultTaskColor() + { + return $this->colorModel->getDefaultColor(); + } + + public function getDefaultTaskColors() + { + return $this->colorModel->getDefaultColors(); + } + + public function getColorList() + { + return $this->colorModel->getList(); + } + + public function getApplicationRoles() + { + return $this->role->getApplicationRoles(); + } + + public function getProjectRoles() + { + return $this->role->getProjectRoles(); + } +} diff --git a/app/Api/Procedure/BaseProcedure.php b/app/Api/Procedure/BaseProcedure.php new file mode 100644 index 0000000..16ef5e0 --- /dev/null +++ b/app/Api/Procedure/BaseProcedure.php @@ -0,0 +1,40 @@ +container)->check($procedure); + UserAuthorization::getInstance($this->container)->check($this->getClassName(), $procedure); + } + + protected function filterValues(array $values) + { + foreach ($values as $key => $value) { + if (is_null($value)) { + unset($values[$key]); + } + } + + return $values; + } + + protected function getClassName() + { + $reflection = new ReflectionClass(get_called_class()); + return $reflection->getShortName(); + } +} diff --git a/app/Api/Procedure/BoardProcedure.php b/app/Api/Procedure/BoardProcedure.php new file mode 100644 index 0000000..69daaf0 --- /dev/null +++ b/app/Api/Procedure/BoardProcedure.php @@ -0,0 +1,24 @@ +container)->check($this->getClassName(), 'getBoard', $project_id); + + return $this->boardFormatter + ->withProjectId($project_id) + ->withQuery($this->taskFinderModel->getExtendedQuery()) + ->format(); + } +} diff --git a/app/Api/Procedure/CategoryProcedure.php b/app/Api/Procedure/CategoryProcedure.php new file mode 100644 index 0000000..aab0ade --- /dev/null +++ b/app/Api/Procedure/CategoryProcedure.php @@ -0,0 +1,61 @@ +container)->check($this->getClassName(), 'getCategory', $category_id); + return $this->categoryModel->getById($category_id); + } + + public function getAllCategories($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllCategories', $project_id); + return $this->categoryModel->getAll($project_id); + } + + public function removeCategory($category_id) + { + CategoryAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeCategory', $category_id); + return $this->categoryModel->remove($category_id); + } + + public function createCategory($project_id, $name, $color_id = null) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'createCategory', $project_id); + + $values = array( + 'project_id' => $project_id, + 'name' => $name, + 'color_id' => $color_id, + ); + + list($valid, ) = $this->categoryValidator->validateCreation($values); + return $valid ? $this->categoryModel->create($values) : false; + } + + public function updateCategory($id, $name, $color_id = null) + { + CategoryAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateCategory', $id); + + $values = array( + 'id' => $id, + 'name' => $name, + 'color_id' => $color_id, + ); + + list($valid, ) = $this->categoryValidator->validateModification($values); + return $valid && $this->categoryModel->update($values); + } +} diff --git a/app/Api/Procedure/ColumnProcedure.php b/app/Api/Procedure/ColumnProcedure.php new file mode 100644 index 0000000..e9e2ea8 --- /dev/null +++ b/app/Api/Procedure/ColumnProcedure.php @@ -0,0 +1,66 @@ +container)->check($this->getClassName(), 'getColumns', $project_id); + return $this->columnModel->getAll($project_id); + } + + public function getColumn($column_id) + { + ColumnAuthorization::getInstance($this->container)->check($this->getClassName(), 'getColumn', $column_id); + return $this->columnModel->getById($column_id); + } + + public function updateColumn($column_id, $title, $task_limit = 0, $description = '') + { + ColumnAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateColumn', $column_id); + return $this->columnModel->update($column_id, $title, $task_limit, $description); + } + + public function addColumn($project_id, $title, $task_limit = 0, $description = '') + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'addColumn', $project_id); + return $this->columnModel->create($project_id, $title, $task_limit, $description); + } + + public function removeColumn($column_id) + { + ColumnAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeColumn', $column_id); + + $projectId = $this->columnModel->getProjectId($column_id); + $nbTasks = $this->taskFinderModel->countByColumnId($projectId, $column_id, array(TaskModel::STATUS_OPEN, TaskModel::STATUS_CLOSED)); + + if ($nbTasks > 0) { + $this->logger->error(__METHOD__.': This column cannot be removed because it contains '.$nbTasks.' tasks'); + return false; + } + + return $this->columnModel->remove($column_id); + } + + public function changeColumnPosition($project_id, $column_id, $position) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'changeColumnPosition', $project_id); + + if ((int) $this->columnModel->getProjectId($column_id) !== (int) $project_id) { + return false; + } + + return $this->columnModel->changePosition($project_id, $column_id, $position); + } +} diff --git a/app/Api/Procedure/CommentProcedure.php b/app/Api/Procedure/CommentProcedure.php new file mode 100644 index 0000000..3209924 --- /dev/null +++ b/app/Api/Procedure/CommentProcedure.php @@ -0,0 +1,113 @@ +container)->check($this->getClassName(), 'getComment', $comment_id); + return $this->commentModel->getById($comment_id); + } + + public function getAllComments($task_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllComments', $task_id); + $comments = $this->commentModel->getAll($task_id); + $filteredComments = []; + $userRole = $this->userSession->getRole(); + + foreach ($comments as $comment) { + if ($userRole === Role::APP_MANAGER && $comment['visibility'] === Role::APP_ADMIN) { + continue; + } + + if ($userRole === Role::APP_USER && $comment['visibility'] !== Role::APP_USER) { + continue; + } + + $filteredComments[] = $comment; + } + + return $filteredComments; + } + + public function removeComment($comment_id) + { + CommentAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeComment', $comment_id); + + if ($this->userSession->isLogged()) { + $loggedUserID = $this->userSession->getId(); + $comment = $this->commentModel->getById($comment_id); + if ($comment['user_id'] != $loggedUserID) { + return false; + } + } + + return $this->commentModel->remove($comment_id); + } + + public function createComment($task_id, $user_id, $content, $reference = '', $visibility = Role::APP_USER) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'createComment', $task_id); + + if ($this->userSession->isLogged()) { + $loggedUserID = $this->userSession->getId(); + if ($user_id != $loggedUserID) { + return false; + } + + $userRole = $this->userSession->getRole(); + if ($userRole === Role::APP_MANAGER && $visibility === Role::APP_ADMIN) { + return false; + } + + if ($userRole === Role::APP_USER && $visibility !== Role::APP_USER) { + return false; + } + } + + $values = array( + 'task_id' => $task_id, + 'user_id' => $user_id, + 'comment' => $content, + 'reference' => $reference, + 'visibility' => $visibility, + ); + + list($valid, ) = $this->commentValidator->validateCreation($values); + + return $valid ? $this->commentModel->create($values) : false; + } + + public function updateComment($id, $content) + { + CommentAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateComment', $id); + + if ($this->userSession->isLogged()) { + $loggedUserID = $this->userSession->getId(); + $comment = $this->commentModel->getById($id); + if ($comment['user_id'] != $loggedUserID) { + return false; + } + } + + $values = array( + 'id' => $id, + 'comment' => $content, + ); + + list($valid, ) = $this->commentValidator->validateModification($values); + return $valid && $this->commentModel->update($values); + } +} diff --git a/app/Api/Procedure/GroupMemberProcedure.php b/app/Api/Procedure/GroupMemberProcedure.php new file mode 100644 index 0000000..081d6ac --- /dev/null +++ b/app/Api/Procedure/GroupMemberProcedure.php @@ -0,0 +1,37 @@ +groupMemberModel->getGroups($user_id); + } + + public function getGroupMembers($group_id) + { + return $this->groupMemberModel->getMembers($group_id); + } + + public function addGroupMember($group_id, $user_id) + { + return $this->groupMemberModel->addUser($group_id, $user_id); + } + + public function removeGroupMember($group_id, $user_id) + { + return $this->groupMemberModel->removeUser($group_id, $user_id); + } + + public function isGroupMember($group_id, $user_id) + { + return $this->groupMemberModel->isMember($group_id, $user_id); + } +} diff --git a/app/Api/Procedure/GroupProcedure.php b/app/Api/Procedure/GroupProcedure.php new file mode 100644 index 0000000..804940a --- /dev/null +++ b/app/Api/Procedure/GroupProcedure.php @@ -0,0 +1,49 @@ +groupModel->create($name, $external_id); + } + + public function updateGroup($group_id, $name = null, $external_id = null) + { + $values = array( + 'id' => $group_id, + 'name' => $name, + 'external_id' => $external_id, + ); + + foreach ($values as $key => $value) { + if (is_null($value)) { + unset($values[$key]); + } + } + + return $this->groupModel->update($values); + } + + public function removeGroup($group_id) + { + return $this->groupModel->remove($group_id); + } + + public function getGroup($group_id) + { + return $this->groupModel->getById($group_id); + } + + public function getAllGroups() + { + return $this->groupModel->getAll(); + } +} diff --git a/app/Api/Procedure/LinkProcedure.php b/app/Api/Procedure/LinkProcedure.php new file mode 100644 index 0000000..b4cecf3 --- /dev/null +++ b/app/Api/Procedure/LinkProcedure.php @@ -0,0 +1,111 @@ +linkModel->getById($link_id); + } + + /** + * Get a link by name + * + * @access public + * @param string $label + * @return array + */ + public function getLinkByLabel($label) + { + return $this->linkModel->getByLabel($label); + } + + /** + * Get the opposite link id + * + * @access public + * @param integer $link_id Link id + * @return integer + */ + public function getOppositeLinkId($link_id) + { + return $this->linkModel->getOppositeLinkId($link_id); + } + + /** + * Get all links + * + * @access public + * @return array + */ + public function getAllLinks() + { + return $this->linkModel->getAll(); + } + + /** + * Create a new link label + * + * @access public + * @param string $label + * @param string $opposite_label + * @return boolean|integer + */ + public function createLink($label, $opposite_label = '') + { + $values = array( + 'label' => $label, + 'opposite_label' => $opposite_label, + ); + + list($valid, ) = $this->linkValidator->validateCreation($values); + return $valid ? $this->linkModel->create($label, $opposite_label) : false; + } + + /** + * Update a link + * + * @access public + * @param integer $link_id + * @param integer $opposite_link_id + * @param string $label + * @return boolean + */ + public function updateLink($link_id, $opposite_link_id, $label) + { + $values = array( + 'id' => $link_id, + 'opposite_id' => $opposite_link_id, + 'label' => $label, + ); + + list($valid, ) = $this->linkValidator->validateModification($values); + return $valid && $this->linkModel->update($values); + } + + /** + * Remove a link a the relation to its opposite + * + * @access public + * @param integer $link_id + * @return boolean + */ + public function removeLink($link_id) + { + return $this->linkModel->remove($link_id); + } +} diff --git a/app/Api/Procedure/MeProcedure.php b/app/Api/Procedure/MeProcedure.php new file mode 100644 index 0000000..3ccba0e --- /dev/null +++ b/app/Api/Procedure/MeProcedure.php @@ -0,0 +1,67 @@ +userSession->getId(); + + return $this->taskListSubtaskAssigneeFormatter + ->withQuery($this->taskFinderModel->getUserQuery($userId)) + ->withUserId($userId) + ->format(); + } + + public function getMyActivityStream() + { + $project_ids = $this->projectPermissionModel->getActiveProjectIds($this->userSession->getId()); + return $this->helper->projectActivity->getProjectsEvents($project_ids, 100); + } + + public function createMyPrivateProject($name, $description = null) + { + if ($this->configModel->get('disable_private_project', 0) == 1) { + return false; + } + + $values = array( + 'name' => $name, + 'description' => $description, + 'is_private' => 1, + ); + + list($valid, ) = $this->projectValidator->validateCreation($values); + return $valid ? $this->projectModel->create($values, $this->userSession->getId(), true) : false; + } + + public function getMyProjectsList() + { + return (object) $this->projectUserRoleModel->getProjectsByUser($this->userSession->getId()); + } + + public function getMyOverdueTasks() + { + return $this->taskFinderModel->getOverdueTasksByUser($this->userSession->getId()); + } + + public function getMyProjects() + { + $project_ids = $this->projectPermissionModel->getActiveProjectIds($this->userSession->getId()); + $projects = $this->projectModel->getAllByIds($project_ids); + + return $this->projectsApiFormatter->withProjects($projects)->format(); + } +} diff --git a/app/Api/Procedure/ProjectFileProcedure.php b/app/Api/Procedure/ProjectFileProcedure.php new file mode 100644 index 0000000..232019d --- /dev/null +++ b/app/Api/Procedure/ProjectFileProcedure.php @@ -0,0 +1,80 @@ +container)->check($this->getClassName(), 'getProjectFile', $project_id); + $file = $this->projectFileModel->getById($file_id); + + if (empty($file) || (int) $file['project_id'] !== (int) $project_id) { + return false; + } + + return $file; + } + + public function getAllProjectFiles($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllProjectFiles', $project_id); + return $this->projectFileModel->getAll($project_id); + } + + public function downloadProjectFile($project_id, $file_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'downloadProjectFile', $project_id); + + try { + $file = $this->projectFileModel->getById($file_id); + + if (! empty($file) && (int) $file['project_id'] === (int) $project_id) { + return base64_encode($this->objectStorage->get($file['path'])); + } + } catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); + } + + return ''; + } + + public function createProjectFile($project_id, $filename, $blob) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'createProjectFile', $project_id); + + try { + return $this->projectFileModel->uploadContent($project_id, $filename, $blob); + } catch (ObjectStorageException $e) { + $this->logger->error(__METHOD__.': '.$e->getMessage()); + return false; + } + } + + public function removeProjectFile($project_id, $file_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeProjectFile', $project_id); + $file = $this->projectFileModel->getById($file_id); + + if (empty($file) || (int) $file['project_id'] !== (int) $project_id) { + return false; + } + + return $this->projectFileModel->remove($file_id); + } + + public function removeAllProjectFiles($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeAllProjectFiles', $project_id); + return $this->projectFileModel->removeAll($project_id); + } +} diff --git a/app/Api/Procedure/ProjectMetaDataProcedure.php b/app/Api/Procedure/ProjectMetaDataProcedure.php new file mode 100644 index 0000000..f14dc63 --- /dev/null +++ b/app/Api/Procedure/ProjectMetaDataProcedure.php @@ -0,0 +1,38 @@ +container)->check($this->getClassName(), 'getProjectMetadata', $project_id); + return (object) $this->projectMetadataModel->getAll($project_id); + } + + public function getProjectMetadataByName($project_id, $name) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getProjectMetadataByName', $project_id); + return $this->projectMetadataModel->get($project_id, $name); + } + + public function saveProjectMetadata($project_id, array $values) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'saveProjectMetadata', $project_id); + return $this->projectMetadataModel->save($project_id, $values); + } + + public function removeProjectMetadata($project_id, $name) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeProjectMetadata', $project_id); + return $this->projectMetadataModel->remove($project_id, $name); + } +} diff --git a/app/Api/Procedure/ProjectPermissionProcedure.php b/app/Api/Procedure/ProjectPermissionProcedure.php new file mode 100644 index 0000000..1938a06 --- /dev/null +++ b/app/Api/Procedure/ProjectPermissionProcedure.php @@ -0,0 +1,69 @@ +container)->check($this->getClassName(), 'getProjectUsers', $project_id); + return (object) $this->projectUserRoleModel->getAllUsers($project_id); + } + + public function getAssignableUsers($project_id, $prepend_unassigned = false) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAssignableUsers', $project_id); + return (object) $this->projectUserRoleModel->getAssignableUsersList($project_id, $prepend_unassigned); + } + + public function addProjectUser($project_id, $user_id, $role = Role::PROJECT_MEMBER) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'addProjectUser', $project_id); + return $this->projectUserRoleModel->addUser($project_id, $user_id, $role); + } + + public function addProjectGroup($project_id, $group_id, $role = Role::PROJECT_MEMBER) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'addProjectGroup', $project_id); + return $this->projectGroupRoleModel->addGroup($project_id, $group_id, $role); + } + + public function removeProjectUser($project_id, $user_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeProjectUser', $project_id); + return $this->projectUserRoleModel->removeUser($project_id, $user_id); + } + + public function removeProjectGroup($project_id, $group_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeProjectGroup', $project_id); + return $this->projectGroupRoleModel->removeGroup($project_id, $group_id); + } + + public function changeProjectUserRole($project_id, $user_id, $role) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'changeProjectUserRole', $project_id); + return $this->projectUserRoleModel->changeUserRole($project_id, $user_id, $role); + } + + public function changeProjectGroupRole($project_id, $group_id, $role) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'changeProjectGroupRole', $project_id); + return $this->projectGroupRoleModel->changeGroupRole($project_id, $group_id, $role); + } + + public function getProjectUserRole($project_id, $user_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getProjectUserRole', $project_id); + return $this->projectUserRoleModel->getUserRole($project_id, $user_id); + } +} diff --git a/app/Api/Procedure/ProjectProcedure.php b/app/Api/Procedure/ProjectProcedure.php new file mode 100644 index 0000000..6da00ae --- /dev/null +++ b/app/Api/Procedure/ProjectProcedure.php @@ -0,0 +1,152 @@ +container)->check($this->getClassName(), 'getProjectById', $project_id); + $project = $this->projectModel->getById($project_id); + return $this->projectApiFormatter->withProject($project)->format(); + } + + public function getProjectByName($name) + { + $project = $this->projectModel->getByName($name); + + if (empty($project)) { + return false; + } + + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getProjectByName', $project['id']); + return $this->projectApiFormatter->withProject($project)->format(); + } + + public function getProjectByIdentifier($identifier) + { + $project = $this->projectModel->getByIdentifier($identifier); + + if (empty($project)) { + return false; + } + + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getProjectByIdentifier', $project['id']); + return $this->projectApiFormatter->withProject($project)->format(); + } + + public function getProjectByEmail($email) + { + $project = $this->projectModel->getByEmail($email); + + if (empty($project)) { + return false; + } + + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getProjectByEmail', $project['id']); + return $this->projectApiFormatter->withProject($project)->format(); + } + + public function getAllProjects() + { + return $this->projectsApiFormatter->withProjects($this->projectModel->getAll())->format(); + } + + public function removeProject($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeProject', $project_id); + return $this->projectModel->remove($project_id); + } + + public function enableProject($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'enableProject', $project_id); + return $this->projectModel->enable($project_id); + } + + public function disableProject($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'disableProject', $project_id); + return $this->projectModel->disable($project_id); + } + + public function enableProjectPublicAccess($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'enableProjectPublicAccess', $project_id); + return $this->projectModel->enablePublicAccess($project_id); + } + + public function disableProjectPublicAccess($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'disableProjectPublicAccess', $project_id); + return $this->projectModel->disablePublicAccess($project_id); + } + + public function getProjectActivities(array $project_ids) + { + foreach ($project_ids as $project_id) { + ProjectAuthorization::getInstance($this->container) + ->check($this->getClassName(), 'getProjectActivities', $project_id); + } + + return $this->helper->projectActivity->getProjectsEvents($project_ids); + } + + public function getProjectActivity($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getProjectActivity', $project_id); + return $this->helper->projectActivity->getProjectEvents($project_id); + } + + public function createProject($name, $description = null, $owner_id = 0, $identifier = null, $start_date = null, $end_date = null, $priority_default = null, $priority_start = null, $priority_end = null, $email = null) + { + if ($owner_id === 0 && $this->userSession->isLogged()) { + $owner_id = $this->userSession->getId(); + } + + $values = $this->filterValues(array( + 'name' => $name, + 'description' => $description, + 'identifier' => $identifier, + 'start_date' => $start_date, + 'end_date' => $end_date, + 'priority_default' => $priority_default, + 'priority_start' => $priority_start, + 'priority_end' => $priority_end, + 'email' => $email, + )); + + list($valid, ) = $this->projectValidator->validateCreation($values); + return $valid ? $this->projectModel->create($values, $owner_id, $this->userSession->isLogged()) : false; + } + + public function updateProject($project_id, $name = null, $description = null, $owner_id = null, $identifier = null, $start_date = null, $end_date = null, $priority_default = null, $priority_start = null, $priority_end = null, $email = null) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateProject', $project_id); + + $values = $this->filterValues(array( + 'id' => $project_id, + 'name' => $name, + 'description' => $description, + 'owner_id' => $owner_id, + 'identifier' => $identifier, + 'start_date' => $start_date, + 'end_date' => $end_date, + 'priority_default' => $priority_default, + 'priority_start' => $priority_start, + 'priority_end' => $priority_end, + 'email' => $email, + )); + + list($valid, ) = $this->projectValidator->validateModification($values); + return $valid && $this->projectModel->update($values); + } +} diff --git a/app/Api/Procedure/SubtaskProcedure.php b/app/Api/Procedure/SubtaskProcedure.php new file mode 100644 index 0000000..ad0a9d3 --- /dev/null +++ b/app/Api/Procedure/SubtaskProcedure.php @@ -0,0 +1,80 @@ +container)->check($this->getClassName(), 'getSubtask', $subtask_id); + return $this->subtaskModel->getById($subtask_id); + } + + public function getAllSubtasks($task_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllSubtasks', $task_id); + return $this->subtaskModel->getAll($task_id); + } + + public function removeSubtask($subtask_id) + { + SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeSubtask', $subtask_id); + return $this->subtaskModel->remove($subtask_id); + } + + public function createSubtask($task_id, $title, $user_id = 0, $time_estimated = 0, $time_spent = 0, $status = 0) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'createSubtask', $task_id); + + $values = array( + 'title' => $title, + 'task_id' => $task_id, + 'user_id' => $user_id, + 'time_estimated' => $time_estimated, + 'time_spent' => $time_spent, + 'status' => $status, + ); + + list($valid, ) = $this->subtaskValidator->validateCreation($values); + return $valid ? $this->subtaskModel->create($values) : false; + } + + public function updateSubtask($id, $task_id, $title = null, $user_id = null, $time_estimated = null, $time_spent = null, $status = null, $position = null) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateSubtask', $task_id); + + $subtask = $this->subtaskModel->getById($id); + if (empty($subtask) || (int) $subtask['task_id'] !== (int) $task_id) { + return false; + } + + $values = array( + 'id' => $id, + 'task_id' => $task_id, + 'title' => $title, + 'user_id' => $user_id, + 'time_estimated' => $time_estimated, + 'time_spent' => $time_spent, + 'status' => $status, + 'position' => $position + ); + + foreach ($values as $key => $value) { + if (is_null($value)) { + unset($values[$key]); + } + } + + list($valid, ) = $this->subtaskValidator->validateApiModification($values); + return $valid && $this->subtaskModel->update($values); + } +} diff --git a/app/Api/Procedure/SubtaskTimeTrackingProcedure.php b/app/Api/Procedure/SubtaskTimeTrackingProcedure.php new file mode 100644 index 0000000..5ceaa08 --- /dev/null +++ b/app/Api/Procedure/SubtaskTimeTrackingProcedure.php @@ -0,0 +1,39 @@ +container)->check($this->getClassName(), 'hasSubtaskTimer', $subtask_id); + return $this->subtaskTimeTrackingModel->hasTimer($subtask_id, $user_id); + } + + public function setSubtaskStartTime($subtask_id, $user_id) + { + SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'setSubtaskStartTime', $subtask_id); + return $this->subtaskTimeTrackingModel->logStartTime($subtask_id, $user_id); + } + + public function setSubtaskEndTime($subtask_id, $user_id) + { + SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'setSubtaskEndTime', $subtask_id); + return $this->subtaskTimeTrackingModel->logEndTime($subtask_id, $user_id); + } + + public function getSubtaskTimeSpent($subtask_id, $user_id) + { + SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getSubtaskTimeSpent', $subtask_id); + return $this->subtaskTimeTrackingModel->getTimeSpent($subtask_id, $user_id); + } +} diff --git a/app/Api/Procedure/SwimlaneProcedure.php b/app/Api/Procedure/SwimlaneProcedure.php new file mode 100644 index 0000000..0840ea6 --- /dev/null +++ b/app/Api/Procedure/SwimlaneProcedure.php @@ -0,0 +1,121 @@ +container)->check($this->getClassName(), 'getActiveSwimlanes', $project_id); + return $this->swimlaneModel->getAllByStatus($project_id, SwimlaneModel::ACTIVE); + } + + public function getAllSwimlanes($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllSwimlanes', $project_id); + return $this->swimlaneModel->getAll($project_id); + } + + public function getSwimlaneById($swimlane_id) + { + $swimlane = $this->swimlaneModel->getById($swimlane_id); + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getSwimlaneById', $swimlane['project_id']); + return $swimlane; + } + + public function getSwimlaneByName($project_id, $name) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getSwimlaneByName', $project_id); + return $this->swimlaneModel->getByName($project_id, $name); + } + + public function getSwimlane($swimlane_id) + { + $swimlane = $this->swimlaneModel->getById($swimlane_id); + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getSwimlane', $swimlane['project_id']); + return $swimlane; + } + + public function addSwimlane($project_id, $name, $description = '') + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'addSwimlane', $project_id); + return $this->swimlaneModel->create($project_id, $name, $description); + } + + public function updateSwimlane($project_id, $swimlane_id, $name, $description = null) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateSwimlane', $project_id); + + $swimlane = $this->swimlaneModel->getById($swimlane_id); + + if (empty($swimlane) || (int) $swimlane['project_id'] !== (int) $project_id) { + return false; + } + + $values = array( + 'project_id' => $project_id, + 'id' => $swimlane_id, + 'name' => $name, + ); + + if (! is_null($description)) { + $values['description'] = $description; + } + + list($valid, $errors) = $this->swimlaneValidator->validateModification($values); + + if (! $valid) { + $this->logger->debug(__METHOD__.': Validation error: '.var_export($errors, true)); + return false; + } + + return $this->swimlaneModel->update($swimlane_id, $values); + } + + public function removeSwimlane($project_id, $swimlane_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeSwimlane', $project_id); + + $swimlane = $this->swimlaneModel->getById($swimlane_id); + + if (empty($swimlane) || (int) $swimlane['project_id'] !== (int) $project_id) { + return false; + } + + return $this->swimlaneModel->remove($project_id, $swimlane_id); + } + + public function disableSwimlane($project_id, $swimlane_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'disableSwimlane', $project_id); + return $this->swimlaneModel->disable($project_id, $swimlane_id); + } + + public function enableSwimlane($project_id, $swimlane_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'enableSwimlane', $project_id); + return $this->swimlaneModel->enable($project_id, $swimlane_id); + } + + public function changeSwimlanePosition($project_id, $swimlane_id, $position) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'changeSwimlanePosition', $project_id); + + $swimlane = $this->swimlaneModel->getById($swimlane_id); + + if (empty($swimlane) || (int) $swimlane['project_id'] !== (int) $project_id) { + return false; + } + + return $this->swimlaneModel->changePosition($project_id, $swimlane_id, $position); + } +} diff --git a/app/Api/Procedure/TagProcedure.php b/app/Api/Procedure/TagProcedure.php new file mode 100644 index 0000000..c28b9bc --- /dev/null +++ b/app/Api/Procedure/TagProcedure.php @@ -0,0 +1,49 @@ +userSession->isLogged() && ! $this->userSession->isAdmin()) { + $project_ids = $this->projectPermissionModel->getActiveProjectIds($this->userSession->getId()); + return $this->tagModel->getAllByProjectIds($project_ids); + } + + return $this->tagModel->getAll(); + } + + public function getTagsByProject($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getTagsByProject', $project_id); + return $this->tagModel->getAllByProject($project_id); + } + + public function createTag($project_id, $tag, $color_id = null) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'createTag', $project_id); + return $this->tagModel->create($project_id, $tag, $color_id); + } + + public function updateTag($tag_id, $tag, $color_id = null) + { + TagAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateTag', $tag_id); + return $this->tagModel->update($tag_id, $tag, $color_id); + } + + public function removeTag($tag_id) + { + TagAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeTag', $tag_id); + return $this->tagModel->remove($tag_id); + } +} diff --git a/app/Api/Procedure/TaskExternalLinkProcedure.php b/app/Api/Procedure/TaskExternalLinkProcedure.php new file mode 100644 index 0000000..67043b8 --- /dev/null +++ b/app/Api/Procedure/TaskExternalLinkProcedure.php @@ -0,0 +1,123 @@ +externalLinkManager->getTypes(); + } + + public function getExternalTaskLinkProviderDependencies($providerName) + { + try { + return $this->externalLinkManager->getProvider($providerName)->getDependencies(); + } catch (ExternalLinkProviderNotFound $e) { + $this->logger->error(__METHOD__.': '.$e->getMessage()); + return false; + } + } + + public function getExternalTaskLinkById($task_id, $link_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getExternalTaskLink', $task_id); + $link = $this->taskExternalLinkModel->getById($link_id); + + if (empty($link) || (int) $link['task_id'] !== (int) $task_id) { + return false; + } + + return $link; + } + + public function getAllExternalTaskLinks($task_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getExternalTaskLinks', $task_id); + return $this->taskExternalLinkModel->getAll($task_id); + } + + public function createExternalTaskLink($task_id, $url, $dependency, $type = ExternalLinkManager::TYPE_AUTO, $title = '') + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'createExternalTaskLink', $task_id); + + try { + $provider = $this->externalLinkManager + ->setUserInputText($url) + ->setUserInputType($type) + ->find(); + + $link = $provider->getLink(); + + $values = array( + 'task_id' => $task_id, + 'title' => $title ?: $link->getTitle(), + 'url' => $link->getUrl(), + 'link_type' => $provider->getType(), + 'dependency' => $dependency, + ); + + list($valid, $errors) = $this->externalLinkValidator->validateCreation($values); + + if (! $valid) { + $this->logger->error(__METHOD__.': '.var_export($errors)); + return false; + } + + return $this->taskExternalLinkModel->create($values); + } catch (ExternalLinkProviderNotFound $e) { + $this->logger->error(__METHOD__.': '.$e->getMessage()); + } + + return false; + } + + public function updateExternalTaskLink($task_id, $link_id, $title = null, $url = null, $dependency = null) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateExternalTaskLink', $task_id); + + $link = $this->taskExternalLinkModel->getById($link_id); + + if (empty($link) || (int) $link['task_id'] !== (int) $task_id) { + return false; + } + + $values = $this->filterValues(array( + 'title' => $title, + 'url' => $url, + 'dependency' => $dependency, + )); + + $values = array_merge($link, $values); + list($valid, $errors) = $this->externalLinkValidator->validateModification($values); + + if (! $valid) { + $this->logger->error(__METHOD__.': '.var_export($errors)); + return false; + } + + return $this->taskExternalLinkModel->update($values); + } + + public function removeExternalTaskLink($task_id, $link_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeExternalTaskLink', $task_id); + $link = $this->taskExternalLinkModel->getById($link_id); + + if (empty($link) || (int) $link['task_id'] !== (int) $task_id) { + return false; + } + + return $this->taskExternalLinkModel->remove($link_id); + } +} diff --git a/app/Api/Procedure/TaskFileProcedure.php b/app/Api/Procedure/TaskFileProcedure.php new file mode 100644 index 0000000..b8aeac4 --- /dev/null +++ b/app/Api/Procedure/TaskFileProcedure.php @@ -0,0 +1,81 @@ +container)->check($this->getClassName(), 'getTaskFile', $file_id); + return $this->taskFileModel->getById($file_id); + } + + public function getAllTaskFiles($task_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllTaskFiles', $task_id); + return $this->taskFileModel->getAll($task_id); + } + + public function downloadTaskFile($file_id) + { + TaskFileAuthorization::getInstance($this->container)->check($this->getClassName(), 'downloadTaskFile', $file_id); + + try { + $file = $this->taskFileModel->getById($file_id); + + if (! empty($file)) { + return base64_encode($this->objectStorage->get($file['path'])); + } + } catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); + } + + return ''; + } + + public function createTaskFile($project_id, $task_id, $filename, $blob) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'createTaskFile', $project_id); + + if (!$this->taskFinderModel->exists($task_id)) { + return false; + } + + $taskProjectID = $this->taskFinderModel->getProjectId($task_id); + if ($taskProjectID != $project_id) { + return false; + } + + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'createTaskFile', $task_id); + + try { + return $this->taskFileModel->uploadContent($task_id, $filename, $blob); + } catch (ObjectStorageException $e) { + $this->logger->error(__METHOD__.': '.$e->getMessage()); + return false; + } + } + + public function removeTaskFile($file_id) + { + TaskFileAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeTaskFile', $file_id); + return $this->taskFileModel->remove($file_id); + } + + public function removeAllTaskFiles($task_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeAllTaskFiles', $task_id); + return $this->taskFileModel->removeAll($task_id); + } +} diff --git a/app/Api/Procedure/TaskLinkProcedure.php b/app/Api/Procedure/TaskLinkProcedure.php new file mode 100644 index 0000000..f9d72a3 --- /dev/null +++ b/app/Api/Procedure/TaskLinkProcedure.php @@ -0,0 +1,109 @@ +container)->check($this->getClassName(), 'getTaskLinkById', $task_link_id); + return $this->taskLinkModel->getById($task_link_id); + } + + /** + * Get all links attached to a task + * + * @access public + * @param integer $task_id Task id + * @return array + */ + public function getAllTaskLinks($task_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllTaskLinks', $task_id); + return $this->taskLinkModel->getAll($task_id); + } + + /** + * Create a new link + * + * @access public + * @param integer $task_id Task id + * @param integer $opposite_task_id Opposite task id + * @param integer $link_id Link id + * @return integer Task link id + */ + public function createTaskLink($task_id, $opposite_task_id, $link_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'createTaskLink', $task_id); + + if ($this->userSession->isLogged()) { + $opposite_task = $this->taskFinderModel->getById($opposite_task_id); + + if (! $this->projectPermissionModel->isUserAllowed($opposite_task['project_id'], $this->userSession->getId())) { + return false; + } + } + + return $this->taskLinkModel->create($task_id, $opposite_task_id, $link_id); + } + + /** + * Update a task link + * + * @access public + * @param integer $task_link_id Task link id + * @param integer $task_id Task id + * @param integer $opposite_task_id Opposite task id + * @param integer $link_id Link id + * @return boolean + */ + public function updateTaskLink($task_link_id, $task_id, $opposite_task_id, $link_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateTaskLink', $task_id); + + $taskLink = $this->taskLinkModel->getById($task_link_id); + + if (empty($taskLink) || (int) $taskLink['task_id'] !== (int) $task_id) { + return false; + } + + if ($this->userSession->isLogged()) { + $opposite_task = $this->taskFinderModel->getById($opposite_task_id); + + if (! $this->projectPermissionModel->isUserAllowed($opposite_task['project_id'], $this->userSession->getId())) { + return false; + } + } + + return $this->taskLinkModel->update($task_link_id, $task_id, $opposite_task_id, $link_id); + } + + /** + * Remove a link between two tasks + * + * @access public + * @param integer $task_link_id + * @return boolean + */ + public function removeTaskLink($task_link_id) + { + TaskLinkAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeTaskLink', $task_link_id); + return $this->taskLinkModel->remove($task_link_id); + } +} diff --git a/app/Api/Procedure/TaskMetadataProcedure.php b/app/Api/Procedure/TaskMetadataProcedure.php new file mode 100644 index 0000000..03ec96e --- /dev/null +++ b/app/Api/Procedure/TaskMetadataProcedure.php @@ -0,0 +1,38 @@ +container)->check($this->getClassName(), 'getTaskMetadata', $task_id); + return (object) $this->taskMetadataModel->getAll($task_id); + } + + public function getTaskMetadataByName($task_id, $name) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getTaskMetadataByName', $task_id); + return $this->taskMetadataModel->get($task_id, $name); + } + + public function saveTaskMetadata($task_id, array $values) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'saveTaskMetadata', $task_id); + return $this->taskMetadataModel->save($task_id, $values); + } + + public function removeTaskMetadata($task_id, $name) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeTaskMetadata', $task_id); + return $this->taskMetadataModel->remove($task_id, $name); + } +} diff --git a/app/Api/Procedure/TaskProcedure.php b/app/Api/Procedure/TaskProcedure.php new file mode 100644 index 0000000..d614ce3 --- /dev/null +++ b/app/Api/Procedure/TaskProcedure.php @@ -0,0 +1,222 @@ +container)->check($this->getClassName(), 'searchTasks', $project_id); + return $this->taskLexer->build($query)->withFilter(new TaskProjectFilter($project_id))->toArray(); + } + + public function getTask($task_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getTask', $task_id); + $task = $this->taskFinderModel->getById($task_id); + return $this->taskApiFormatter->withTask($task)->format(); + } + + public function getTaskByReference($project_id, $reference) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getTaskByReference', $project_id); + $task = $this->taskFinderModel->getByReference($project_id, $reference); + return $this->taskApiFormatter->withTask($task)->format(); + } + + public function getAllTasks($project_id, $status_id = TaskModel::STATUS_OPEN) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllTasks', $project_id); + $tasks = $this->taskFinderModel->getAll($project_id, $status_id); + return $this->tasksApiFormatter->withTasks($tasks)->format(); + } + + public function getOverdueTasks() + { + return $this->taskFinderModel->getOverdueTasks(); + } + + public function getOverdueTasksByProject($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getOverdueTasksByProject', $project_id); + return $this->taskFinderModel->getOverdueTasksByProject($project_id); + } + + public function openTask($task_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'openTask', $task_id); + return $this->taskStatusModel->open($task_id); + } + + public function closeTask($task_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'closeTask', $task_id); + return $this->taskStatusModel->close($task_id); + } + + public function removeTask($task_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeTask', $task_id); + return $this->taskModel->remove($task_id); + } + + public function moveTaskPosition($project_id, $task_id, $column_id, $position, $swimlane_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'moveTaskPosition', $task_id); + $taskProjectId = $this->taskFinderModel->getProjectId($task_id); + + if ($taskProjectId !== (int) $project_id) { + return false; + } + + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'moveTaskPosition', $project_id); + return $this->taskPositionModel->movePosition($project_id, $task_id, $column_id, $position, $swimlane_id, true, false); + } + + public function moveTaskToProject($task_id, $project_id, $swimlane_id = null, $column_id = null, $category_id = null, $owner_id = null) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'moveTaskToProject', $task_id); + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'moveTaskToProject', $project_id); + return $this->taskProjectMoveModel->moveToProject($task_id, $project_id, $swimlane_id, $column_id, $category_id, $owner_id); + } + + public function duplicateTaskToProject($task_id, $project_id, $swimlane_id = null, $column_id = null, $category_id = null, $owner_id = null) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'duplicateTaskToProject', $task_id); + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'duplicateTaskToProject', $project_id); + return $this->taskProjectDuplicationModel->duplicateToProject($task_id, $project_id, $swimlane_id, $column_id, $category_id, $owner_id); + } + + public function createTask( + $title, + $project_id, + $color_id = '', + $column_id = 0, + $owner_id = 0, + $creator_id = 0, + $date_due = '', + $description = '', + $category_id = 0, + $score = 0, + $swimlane_id = null, + $priority = 0, + $recurrence_status = 0, + $recurrence_trigger = 0, + $recurrence_factor = 0, + $recurrence_timeframe = 0, + $recurrence_basedate = 0, + $reference = '', + array $tags = array(), + $date_started = '', + $time_spent = null, + $time_estimated = null + ) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'createTask', $project_id); + + if ($owner_id !== 0 && ! $this->projectPermissionModel->isAssignable($project_id, $owner_id)) { + return false; + } + + if ($creator_id !== 0 && ! $this->projectPermissionModel->isAssignable($project_id, $creator_id)) { + return false; + } + + $values = array( + 'title' => $title, + 'project_id' => $project_id, + 'color_id' => $color_id, + 'column_id' => $column_id, + 'owner_id' => $owner_id, + 'creator_id' => $creator_id, + 'date_due' => $date_due, + 'description' => $description, + 'category_id' => $category_id, + 'score' => $score, + 'swimlane_id' => $swimlane_id, + 'recurrence_status' => $recurrence_status, + 'recurrence_trigger' => $recurrence_trigger, + 'recurrence_factor' => $recurrence_factor, + 'recurrence_timeframe' => $recurrence_timeframe, + 'recurrence_basedate' => $recurrence_basedate, + 'reference' => $reference, + 'priority' => $priority, + 'tags' => $tags, + 'date_started' => $date_started, + 'time_spent' => $time_spent, + 'time_estimated' => $time_estimated, + ); + + list($valid, ) = $this->taskValidator->validateCreation($values); + + return $valid ? $this->taskCreationModel->create($values) : false; + } + + public function updateTask( + $id, + $title = null, + $color_id = null, + $owner_id = null, + $date_due = null, + $description = null, + $category_id = null, + $score = null, + $priority = null, + $recurrence_status = null, + $recurrence_trigger = null, + $recurrence_factor = null, + $recurrence_timeframe = null, + $recurrence_basedate = null, + $reference = null, + $tags = null, + $date_started = null, + $time_spent = null, + $time_estimated = null + ) { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateTask', $id); + $project_id = $this->taskFinderModel->getProjectId($id); + + if ($project_id === 0) { + return false; + } + + if ($owner_id !== null && $owner_id != 0 && ! $this->projectPermissionModel->isAssignable($project_id, $owner_id)) { + return false; + } + + $values = $this->filterValues(array( + 'id' => $id, + 'title' => $title, + 'color_id' => $color_id, + 'owner_id' => $owner_id, + 'date_due' => $date_due, + 'description' => $description, + 'category_id' => $category_id, + 'score' => $score, + 'recurrence_status' => $recurrence_status, + 'recurrence_trigger' => $recurrence_trigger, + 'recurrence_factor' => $recurrence_factor, + 'recurrence_timeframe' => $recurrence_timeframe, + 'recurrence_basedate' => $recurrence_basedate, + 'reference' => $reference, + 'priority' => $priority, + 'tags' => $tags, + 'date_started' => $date_started, + 'time_spent' => $time_spent, + 'time_estimated' => $time_estimated, + )); + + list($valid) = $this->taskValidator->validateApiModification($values); + return $valid && $this->taskModificationModel->update($values); + } +} diff --git a/app/Api/Procedure/TaskTagProcedure.php b/app/Api/Procedure/TaskTagProcedure.php new file mode 100644 index 0000000..8e8607c --- /dev/null +++ b/app/Api/Procedure/TaskTagProcedure.php @@ -0,0 +1,28 @@ +container)->check($this->getClassName(), 'setTaskTags', $project_id); + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'setTaskTags', $task_id); + return $this->taskTagModel->save($project_id, $task_id, $tags); + } + + public function getTaskTags($task_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getTaskTags', $task_id); + return (object) $this->taskTagModel->getList($task_id); + } +} diff --git a/app/Api/Procedure/UserProcedure.php b/app/Api/Procedure/UserProcedure.php new file mode 100644 index 0000000..676f771 --- /dev/null +++ b/app/Api/Procedure/UserProcedure.php @@ -0,0 +1,147 @@ +userModel->getById($user_id); + } + + public function getUserByName($username) + { + return $this->userModel->getByUsername($username); + } + + public function getAllUsers() + { + return $this->userModel->getAll(); + } + + public function removeUser($user_id) + { + return $this->userModel->remove($user_id); + } + + public function disableUser($user_id) + { + return $this->userModel->disable($user_id); + } + + public function enableUser($user_id) + { + return $this->userModel->enable($user_id); + } + + public function isActiveUser($user_id) + { + return $this->userModel->isActive($user_id); + } + + public function createUser($username, $password, $name = '', $email = '', $role = Role::APP_USER) + { + $values = array( + 'username' => $username, + 'password' => $password, + 'confirmation' => $password, + 'name' => $name, + 'email' => $email, + 'role' => $role, + ); + + list($valid, ) = $this->userValidator->validateCreation($values); + + if ($valid) { + $user_id = $this->userModel->create($values); + if ($user_id !== false && $this->configModel->get('notifications_enabled', 0) == 1) { + $this->userNotificationTypeModel->saveSelectedTypes($user_id, [MailNotification::TYPE, WebNotification::TYPE]); + } + return $user_id; + } + + return false; + } + + /** + * Create LDAP user in the database + * + * Only "anonymous" and "proxy" LDAP authentication are supported by this method + * + * User information will be fetched from the LDAP server + * + * @access public + * @param string $username + * @return bool|int + */ + public function createLdapUser($username) + { + if (LDAP_BIND_TYPE === 'user') { + $this->logger->error('LDAP authentication "user" is not supported by this API call'); + return false; + } + + try { + + $ldap = LdapClient::connect(); + $ldap->setLogger($this->logger); + $user = LdapUser::getUser($ldap, $username); + + if ($user === null) { + $this->logger->info('User not found in LDAP server'); + return false; + } + + if ($user->getUsername() === '') { + throw new LogicException('Username not found in LDAP profile, check the parameter LDAP_USER_ATTRIBUTE_USERNAME'); + } + + $values = array( + 'username' => $user->getUsername(), + 'name' => $user->getName(), + 'email' => $user->getEmail(), + 'role' => $user->getRole() ?: Role::APP_USER, + 'is_ldap_user' => 1, + ); + + $user_id = $this->userModel->create($values); + + if ($user_id !== false && $this->configModel->get('notifications_enabled', 0) == 1) { + $this->userNotificationTypeModel->saveSelectedTypes($user_id, [MailNotification::TYPE, WebNotification::TYPE]); + } + + return $user_id; + } catch (LdapException $e) { + $this->logger->error($e->getMessage()); + return false; + } + } + + public function updateUser($id, $username = null, $name = null, $email = null, $role = null) + { + $values = $this->filterValues(array( + 'id' => $id, + 'username' => $username, + 'name' => $name, + 'email' => $email, + 'role' => $role, + )); + + list($valid, ) = $this->userValidator->validateApiModification($values); + return $valid && $this->userModel->update($values); + } +} diff --git a/app/Auth/ApiAccessTokenAuth.php b/app/Auth/ApiAccessTokenAuth.php new file mode 100644 index 0000000..88e1686 --- /dev/null +++ b/app/Auth/ApiAccessTokenAuth.php @@ -0,0 +1,118 @@ +db + ->table(UserModel::TABLE) + ->columns('id', 'password') + ->eq('username', $this->username) + ->eq('api_access_token', $this->password) + ->notNull('api_access_token') + ->eq('is_active', 1) + ->findOne(); + + if (! empty($user)) { + $this->userInfo = $user; + return true; + } + + return false; + } + + /** + * Get user object + * + * @access public + * @return \Kanboard\User\DatabaseUserProvider + */ + public function getUser() + { + if (empty($this->userInfo)) { + return null; + } + + return new DatabaseUserProvider($this->userInfo); + } + + /** + * Set username + * + * @access public + * @param string $username + */ + public function setUsername($username) + { + $this->username = $username; + } + + /** + * Set password + * + * @access public + * @param string $password + */ + public function setPassword($password) + { + $this->password = $password; + } +} diff --git a/app/Auth/DatabaseAuth.php b/app/Auth/DatabaseAuth.php new file mode 100644 index 0000000..1982f57 --- /dev/null +++ b/app/Auth/DatabaseAuth.php @@ -0,0 +1,126 @@ +db + ->table(UserModel::TABLE) + ->columns('id', 'password') + ->eq('username', $this->username) + ->eq('disable_login_form', 0) + ->eq('is_ldap_user', 0) + ->eq('is_active', 1) + ->findOne(); + + if (! empty($user) && password_verify($this->password, $user['password'])) { + $this->userInfo = $user; + return true; + } + + return false; + } + + /** + * Check if the user session is valid + * + * @access public + * @return boolean + */ + public function isValidSession() + { + return $this->userModel->isValidSession($this->userSession->getId(), $this->userSession->getRole()); + } + + /** + * Get user object + * + * @access public + * @return \Kanboard\User\DatabaseUserProvider + */ + public function getUser() + { + if (empty($this->userInfo)) { + return null; + } + + return new DatabaseUserProvider($this->userInfo); + } + + /** + * Set username + * + * @access public + * @param string $username + */ + public function setUsername($username) + { + $this->username = $username; + } + + /** + * Set password + * + * @access public + * @param string $password + */ + public function setPassword($password) + { + $this->password = $password; + } +} diff --git a/app/Auth/LdapAuth.php b/app/Auth/LdapAuth.php new file mode 100644 index 0000000..05ffbeb --- /dev/null +++ b/app/Auth/LdapAuth.php @@ -0,0 +1,176 @@ +getLdapUsername(), $this->getLdapPassword()); + $client->setLogger($this->logger); + + $user = LdapUser::getUser($client, $this->username); + + if ($user === null) { + $this->logger->info('User ('.$this->username.') not found in LDAP server'); + return false; + } + + if ($user->getUsername() === '') { + throw new LogicException('Username not found in LDAP profile, check the parameter LDAP_USER_ATTRIBUTE_USERNAME'); + } + + $this->logger->info('Authenticate this user: '.$user->getDn()); + + if ($client->authenticate($user->getDn(), $this->password)) { + $this->userInfo = $user; + return true; + } + + } catch (LdapException $e) { + $this->logger->error($e->getMessage()); + } + + return false; + } + + /** + * Get user object + * + * @access public + * @return \Kanboard\User\LdapUserProvider + */ + public function getUser() + { + return $this->userInfo; + } + + /** + * Set username + * + * @access public + * @param string $username + */ + public function setUsername($username) + { + $this->username = $username; + } + + /** + * Set password + * + * @access public + * @param string $password + */ + public function setPassword($password) + { + $this->password = $password; + } + + /** + * Get LDAP username (proxy auth) + * + * @access public + * @return string + */ + public function getLdapUsername() + { + switch ($this->getLdapBindType()) { + case 'proxy': + return LDAP_USERNAME; + case 'user': + return sprintf(LDAP_USERNAME, $this->username); + default: + return null; + } + } + + /** + * Get LDAP password (proxy auth) + * + * @access public + * @return string + */ + public function getLdapPassword() + { + switch ($this->getLdapBindType()) { + case 'proxy': + return LDAP_PASSWORD; + case 'user': + return $this->password; + default: + return null; + } + } + + /** + * Get LDAP bind type + * + * @access public + * @return integer + */ + public function getLdapBindType() + { + if (LDAP_BIND_TYPE !== 'user' && LDAP_BIND_TYPE !== 'proxy' && LDAP_BIND_TYPE !== 'anonymous') { + throw new LogicException('Wrong value for the parameter LDAP_BIND_TYPE'); + } + + return LDAP_BIND_TYPE; + } +} diff --git a/app/Auth/RememberMeAuth.php b/app/Auth/RememberMeAuth.php new file mode 100644 index 0000000..e0f4ceb --- /dev/null +++ b/app/Auth/RememberMeAuth.php @@ -0,0 +1,79 @@ +rememberMeCookie->read(); + + if ($credentials !== false) { + $session = $this->rememberMeSessionModel->find($credentials['token'], $credentials['sequence']); + + if (! empty($session)) { + $this->rememberMeCookie->write( + $session['token'], + $this->rememberMeSessionModel->updateSequence($session['token']), + $session['expiration'] + ); + + $this->userInfo = $this->userModel->getById($session['user_id']); + + return true; + } + } + + return false; + } + + /** + * Get user object + * + * @access public + * @return DatabaseUserProvider + */ + public function getUser() + { + if (empty($this->userInfo)) { + return null; + } + + return new DatabaseUserProvider($this->userInfo); + } +} diff --git a/app/Auth/ReverseProxyAuth.php b/app/Auth/ReverseProxyAuth.php new file mode 100644 index 0000000..edbb4c7 --- /dev/null +++ b/app/Auth/ReverseProxyAuth.php @@ -0,0 +1,91 @@ +request->getRemoteUser(); + $email = $this->request->getRemoteEmail(); + $fullname = $this->request->getRemoteName(); + + if (! empty($username)) { + $userProfile = $this->userCacheDecorator->getByUsername($username); + $this->userInfo = new ReverseProxyUserProvider($username, $email, $fullname, $userProfile ?: array()); + return true; + } + + return false; + } + + /** + * Check if the user session is valid + * + * @access public + * @return boolean + */ + public function isValidSession() + { + return $this->request->getRemoteUser() === $this->userSession->getUsername(); + } + + /** + * Get user object + * + * @access public + * @return ReverseProxyUserProvider + */ + public function getUser() + { + return $this->userInfo; + } + + /** + * Check if the authentication provider should be used + * + * @access public + * @return boolean + */ + public function isEnabled() + { + return ! empty($this->request->getRemoteUser()); + } +} diff --git a/app/Auth/TotpAuth.php b/app/Auth/TotpAuth.php new file mode 100644 index 0000000..abfb216 --- /dev/null +++ b/app/Auth/TotpAuth.php @@ -0,0 +1,146 @@ +checkTotp(Base32::decode($this->secret), $this->code); + } + + /** + * Called before to prompt the user + * + * @access public + */ + public function beforeCode() + { + + } + + /** + * Set validation code + * + * @access public + * @param string $code + */ + public function setCode($code) + { + $this->code = $code; + } + + /** + * Generate secret + * + * @access public + * @return string + */ + public function generateSecret() + { + $this->secret = GoogleAuthenticator::generateRandom(); + return $this->secret; + } + + /** + * Set secret token + * + * @access public + * @param string $secret + */ + public function setSecret($secret) + { + $this->secret = $secret; + } + + /** + * Get secret token + * + * @access public + * @return string + */ + public function getSecret() + { + return $this->secret; + } + + /** + * Get QR code url + * + * @access public + * @param string $label + * @return string + */ + public function getQrCodeUrl($label) + { + if (empty($this->secret)) { + return ''; + } + + $options = array('issuer' => TOTP_ISSUER); + return GoogleAuthenticator::getQrCodeUrl('totp', $label, $this->secret, null, $options); + } + + /** + * Get key url (empty if no url can be provided) + * + * @access public + * @param string $label + * @return string + */ + public function getKeyUrl($label) + { + if (empty($this->secret)) { + return ''; + } + + $options = array('issuer' => TOTP_ISSUER); + return GoogleAuthenticator::getKeyUri('totp', $label, $this->secret, null, $options); + } +} diff --git a/app/Console/BaseCommand.php b/app/Console/BaseCommand.php new file mode 100644 index 0000000..8ea67ae --- /dev/null +++ b/app/Console/BaseCommand.php @@ -0,0 +1,69 @@ +container = $container; + } + + /** + * Load automatically models + * + * @access public + * @param string $name Model name + * @return mixed + */ + public function __get($name) + { + return $this->container[$name]; + } +} diff --git a/app/Console/CronjobCommand.php b/app/Console/CronjobCommand.php new file mode 100644 index 0000000..4eabbec --- /dev/null +++ b/app/Console/CronjobCommand.php @@ -0,0 +1,33 @@ +setName('cronjob') + ->setDescription('Execute daily cronjob'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + foreach ($this->commands as $command) { + $job = $this->getApplication()->find($command); + $job->run(new ArrayInput(array('command' => $command)), new NullOutput()); + } + return 0; + } +} diff --git a/app/Console/CssCommand.php b/app/Console/CssCommand.php new file mode 100644 index 0000000..30ad1f1 --- /dev/null +++ b/app/Console/CssCommand.php @@ -0,0 +1,131 @@ +setName('css') + ->setDescription('Minify CSS files') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->minifyFiles(self::CSS_SRC_PATH, array_merge(['themes'.DIRECTORY_SEPARATOR.'light.css'], $this->appFiles), 'light.min.css'); + $this->minifyFiles(self::CSS_SRC_PATH, array_merge(['themes'.DIRECTORY_SEPARATOR.'dark.css'], $this->appFiles), 'dark.min.css'); + $this->minifyFiles(self::CSS_SRC_PATH, array_merge(['themes'.DIRECTORY_SEPARATOR.'auto.css'], $this->appFiles), 'auto.min.css'); + $this->minifyFiles(self::CSS_SRC_PATH, $this->printFiles, 'print.min.css'); + + $vendorBundle = concat_files($this->vendorFiles); + file_put_contents('assets/css/vendor.min.css', $vendorBundle); + return 0; + } + + private function minifyFiles($folder, array $files, $destination) + { + $minifier = new Minify\CSS(); + + foreach ($files as $file) { + $filename = $folder.$file; + if (! file_exists($filename)) { + die("$filename not found\n"); + } + $minifier->add($filename); + } + + $minifier->minify(self::CSS_DIST_PATH . $destination); + } +} diff --git a/app/Console/DatabaseMigrationCommand.php b/app/Console/DatabaseMigrationCommand.php new file mode 100644 index 0000000..d5c3209 --- /dev/null +++ b/app/Console/DatabaseMigrationCommand.php @@ -0,0 +1,24 @@ +setName('db:migrate') + ->setDescription('Execute SQL migrations'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + parent::execute($input, $output); + DatabaseProvider::runMigrations($this->container['db']); + return 0; + } +} diff --git a/app/Console/DatabaseVersionCommand.php b/app/Console/DatabaseVersionCommand.php new file mode 100644 index 0000000..32ac180 --- /dev/null +++ b/app/Console/DatabaseVersionCommand.php @@ -0,0 +1,24 @@ +setName('db:version') + ->setDescription('Show database schema version'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $output->writeln('Current version: '.DatabaseProvider::getSchemaVersion($this->container['db']).''); + $output->writeln('Last version: '.\Schema\VERSION.''); + return 0; + } +} diff --git a/app/Console/JobCommand.php b/app/Console/JobCommand.php new file mode 100644 index 0000000..ed043d3 --- /dev/null +++ b/app/Console/JobCommand.php @@ -0,0 +1,36 @@ +setName('job') + ->setDescription('Execute individual job (read payload from stdin)') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $payload = fgets(STDIN); + + $job = new Job(); + $job->unserialize($payload); + + JobHandler::getInstance($this->container)->executeJob($job); + return 0; + } +} diff --git a/app/Console/JsCommand.php b/app/Console/JsCommand.php new file mode 100644 index 0000000..cac3c66 --- /dev/null +++ b/app/Console/JsCommand.php @@ -0,0 +1,91 @@ +setName('js') + ->setDescription('Minify Javascript files') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $appBundle = concat_files($this->appFiles); + $vendorBundle = concat_files($this->vendorFiles); + + $minifier = new Minify\JS($appBundle); + + file_put_contents('assets/js/app.min.js', $minifier->minify()); + file_put_contents('assets/js/vendor.min.js', $vendorBundle); + return 0; + } +} diff --git a/app/Console/LocaleComparatorCommand.php b/app/Console/LocaleComparatorCommand.php new file mode 100644 index 0000000..e206f36 --- /dev/null +++ b/app/Console/LocaleComparatorCommand.php @@ -0,0 +1,82 @@ +setName('locale:compare') + ->setDescription('Compare application translations with the '.self::REF_LOCALE.' locale'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $strings = array(); + $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(APP_DIR)); + $it->rewind(); + + while ($it->valid()) { + if (! $it->isDot() && substr($it->key(), -4) === '.php') { + $strings = array_merge($strings, $this->search($it->key())); + } + + $it->next(); + } + + $this->compare(array_unique($strings)); + return 0; + } + + public function show(array $strings) + { + foreach ($strings as $string) { + echo " '".str_replace("'", "\'", $string)."' => '',".PHP_EOL; + } + } + + public function compare(array $strings) + { + $reference_file = APP_DIR.DIRECTORY_SEPARATOR.'Locale'.DIRECTORY_SEPARATOR.self::REF_LOCALE.DIRECTORY_SEPARATOR.'translations.php'; + $reference = include $reference_file; + + echo str_repeat('#', 70).PHP_EOL; + echo 'MISSING STRINGS'.PHP_EOL; + echo str_repeat('#', 70).PHP_EOL; + $this->show(array_diff($strings, array_keys($reference))); + + echo str_repeat('#', 70).PHP_EOL; + echo 'USELESS STRINGS'.PHP_EOL; + echo str_repeat('#', 70).PHP_EOL; + $this->show(array_diff(array_keys($reference), $strings)); + } + + public function search($filename) + { + $content = file_get_contents($filename); + $strings = array(); + + if (preg_match_all('/\b[et]\s*\(\s*(\'\K.*?\')\s*[\)\,]/', $content, $matches) && isset($matches[1])) { + $strings = $matches[1]; + } + + if (preg_match_all('/\bdt\s*\(\s*(\'\K.*?\')\s*[\)\,]/', $content, $matches) && isset($matches[1])) { + $strings = array_merge($strings, $matches[1]); + } + + array_walk($strings, function (&$value) { + $value = trim($value, "'"); + $value = str_replace("\'", "'", $value); + }); + + return $strings; + } +} diff --git a/app/Console/LocaleSyncCommand.php b/app/Console/LocaleSyncCommand.php new file mode 100644 index 0000000..7283b5e --- /dev/null +++ b/app/Console/LocaleSyncCommand.php @@ -0,0 +1,55 @@ +setName('locale:sync') + ->setDescription('Synchronize all translations based on the '.self::REF_LOCALE.' locale'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $reference_file = APP_DIR.DIRECTORY_SEPARATOR.'Locale'.DIRECTORY_SEPARATOR.self::REF_LOCALE.DIRECTORY_SEPARATOR.'translations.php'; + $reference = include $reference_file; + + foreach (new DirectoryIterator(APP_DIR.DIRECTORY_SEPARATOR.'Locale') as $fileInfo) { + if (! $fileInfo->isDot() && $fileInfo->isDir() && $fileInfo->getFilename() !== self::REF_LOCALE) { + $filename = APP_DIR.DIRECTORY_SEPARATOR.'Locale'.DIRECTORY_SEPARATOR.$fileInfo->getFilename().DIRECTORY_SEPARATOR.'translations.php'; + echo $fileInfo->getFilename().' ('.$filename.')'.PHP_EOL; + + file_put_contents($filename, $this->updateFile($reference, $filename)); + } + } + return 0; + } + + public function updateFile(array $reference, $outdated_file) + { + $outdated = include $outdated_file; + + $output = ' $value) { + $escapedKey = str_replace("'", "\'", $key); + if (! empty($outdated[$key])) { + $output .= " '".$escapedKey."' => '".str_replace("'", "\'", $outdated[$key])."',\n"; + } else { + $output .= " // '".$escapedKey."' => '',\n"; + } + } + + $output .= "];\n"; + return $output; + } +} diff --git a/app/Console/PluginInstallCommand.php b/app/Console/PluginInstallCommand.php new file mode 100644 index 0000000..f4bb698 --- /dev/null +++ b/app/Console/PluginInstallCommand.php @@ -0,0 +1,38 @@ +setName('plugin:install') + ->setDescription('Install a plugin from a remote Zip archive') + ->addArgument('url', InputArgument::REQUIRED, 'Archive URL'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + if (!Installer::isConfigured()) { + throw new LogicException('Kanboard is not configured to install plugins itself'); + } + + try { + $installer = new Installer($this->container); + $installer->install($input->getArgument('url')); + $output->writeln('Plugin installed successfully'); + return 0; + } catch (PluginInstallerException $e) { + $output->writeln(''.$e->getMessage().''); + return 1; + } + } +} diff --git a/app/Console/PluginUninstallCommand.php b/app/Console/PluginUninstallCommand.php new file mode 100644 index 0000000..13a6faf --- /dev/null +++ b/app/Console/PluginUninstallCommand.php @@ -0,0 +1,38 @@ +setName('plugin:uninstall') + ->setDescription('Remove a plugin') + ->addArgument('pluginId', InputArgument::REQUIRED, 'Plugin directory name'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + if (!Installer::isConfigured()) { + throw new LogicException('Kanboard is not configured to install plugins itself'); + } + + try { + $installer = new Installer($this->container); + $installer->uninstall($input->getArgument('pluginId')); + $output->writeln('Plugin removed successfully'); + return 0; + } catch (PluginInstallerException $e) { + $output->writeln(''.$e->getMessage().''); + return 1; + } + } +} diff --git a/app/Console/PluginUpgradeCommand.php b/app/Console/PluginUpgradeCommand.php new file mode 100644 index 0000000..9a8558b --- /dev/null +++ b/app/Console/PluginUpgradeCommand.php @@ -0,0 +1,56 @@ +setName('plugin:upgrade') + ->setDescription('Update all installed plugins') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + if (!Installer::isConfigured()) { + throw new LogicException('Kanboard is not configured to install plugins itself'); + } + + $installer = new Installer($this->container); + $availablePlugins = Directory::getInstance($this->container)->getAvailablePlugins(); + + foreach ($this->pluginLoader->getPlugins() as $installedPlugin) { + $pluginDetails = $this->getPluginDetails($availablePlugins, $installedPlugin); + + if ($pluginDetails === null) { + $output->writeln('* Plugin not available in the directory: '.$installedPlugin->getPluginName().''); + } elseif ($pluginDetails['version'] > $installedPlugin->getPluginVersion()) { + $output->writeln('* Updating plugin: '.$installedPlugin->getPluginName().''); + $installer->update($pluginDetails['download']); + } else { + $output->writeln('* Plugin up to date: '.$installedPlugin->getPluginName().''); + } + } + return 0; + } + + protected function getPluginDetails(array $availablePlugins, BasePlugin $installedPlugin) + { + foreach ($availablePlugins as $availablePluginName => $availablePlugin) { + if ($availablePluginName === $installedPlugin->getPluginName()) { + return $availablePlugin; + } + } + + return null; + } +} diff --git a/app/Console/ProjectActivityArchiveCommand.php b/app/Console/ProjectActivityArchiveCommand.php new file mode 100644 index 0000000..9dd6b61 --- /dev/null +++ b/app/Console/ProjectActivityArchiveCommand.php @@ -0,0 +1,22 @@ +setName('projects:archive-activities') + ->setDescription('Remove project activities after one year'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->projectActivityModel->cleanup(strtotime('-1 year')); + return 0; + } +} diff --git a/app/Console/ProjectArchiveCommand.php b/app/Console/ProjectArchiveCommand.php new file mode 100644 index 0000000..c11be1f --- /dev/null +++ b/app/Console/ProjectArchiveCommand.php @@ -0,0 +1,31 @@ +setName('projects:archive') + ->setDescription('Disable projects not touched during one year'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $projects = $this->db->table(ProjectModel::TABLE) + ->eq('is_active', 1) + ->lt('last_modified', strtotime('-1 year')) + ->findAll(); + + foreach ($projects as $project) { + $output->writeln('Deactivating project: #'.$project['id'].' - '.$project['name']); + $this->projectModel->disable($project['id']); + } + return 0; + } +} diff --git a/app/Console/ProjectDailyColumnStatsExportCommand.php b/app/Console/ProjectDailyColumnStatsExportCommand.php new file mode 100644 index 0000000..635addc --- /dev/null +++ b/app/Console/ProjectDailyColumnStatsExportCommand.php @@ -0,0 +1,35 @@ +setName('export:daily-project-column-stats') + ->setDescription('Daily project column stats CSV export (number of tasks per column and per day)') + ->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): int + { + $data = $this->projectDailyColumnStatsModel->getAggregatedMetrics( + $input->getArgument('project_id'), + $input->getArgument('start_date'), + $input->getArgument('end_date') + ); + + if (is_array($data)) { + Csv::output($data); + } + return 0; + } +} diff --git a/app/Console/ProjectDailyStatsCalculationCommand.php b/app/Console/ProjectDailyStatsCalculationCommand.php new file mode 100644 index 0000000..77170ca --- /dev/null +++ b/app/Console/ProjectDailyStatsCalculationCommand.php @@ -0,0 +1,29 @@ +setName('projects:daily-stats') + ->setDescription('Calculate daily statistics for all projects'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $projects = $this->projectModel->getAllByStatus(ProjectModel::ACTIVE); + + foreach ($projects as $project) { + $output->writeln('Run calculation for '.$project['name']); + $this->projectDailyColumnStatsModel->updateTotals($project['id'], date('Y-m-d')); + $this->projectDailyStatsModel->updateTotals($project['id'], date('Y-m-d')); + } + return 0; + } +} diff --git a/app/Console/ResetPasswordCommand.php b/app/Console/ResetPasswordCommand.php new file mode 100644 index 0000000..f66818c --- /dev/null +++ b/app/Console/ResetPasswordCommand.php @@ -0,0 +1,80 @@ +setName('user:reset-password') + ->setDescription('Change user password') + ->addArgument('username', InputArgument::REQUIRED, 'Username') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $helper = $this->getHelper('question'); + $username = $input->getArgument('username'); + + $passwordQuestion = new Question('What is the new password for '.$username.'? (characters are not printed)'.PHP_EOL); + $passwordQuestion->setHidden(true); + $passwordQuestion->setHiddenFallback(false); + + $password = $helper->ask($input, $output, $passwordQuestion); + + $confirmationQuestion = new Question('Confirmation:'.PHP_EOL); + $confirmationQuestion->setHidden(true); + $confirmationQuestion->setHiddenFallback(false); + + $confirmation = $helper->ask($input, $output, $confirmationQuestion); + + if ($this->validatePassword($output, $password, $confirmation)) { + $this->resetPassword($output, $username, $password); + } + return 0; + } + + private function validatePassword(OutputInterface $output, $password, $confirmation) + { + list($valid, $errors) = $this->passwordResetValidator->validateModification(array( + 'password' => $password, + 'confirmation' => $confirmation, + )); + + if (!$valid) { + foreach ($errors as $error_list) { + foreach ($error_list as $error) { + $output->writeln(''.$error.''); + } + } + } + + return $valid; + } + + private function resetPassword(OutputInterface $output, $username, $password) + { + $userId = $this->userModel->getIdByUsername($username); + + if (empty($userId)) { + $output->writeln('User not found'); + return false; + } + + if (!$this->userModel->update(array('id' => $userId, 'password' => $password))) { + $output->writeln('Unable to update password'); + return false; + } + + $output->writeln('Password updated successfully'); + + return true; + } +} diff --git a/app/Console/ResetTwoFactorCommand.php b/app/Console/ResetTwoFactorCommand.php new file mode 100644 index 0000000..0ab9854 --- /dev/null +++ b/app/Console/ResetTwoFactorCommand.php @@ -0,0 +1,37 @@ +setName('user:reset-2fa') + ->setDescription('Remove two-factor authentication for a user') + ->addArgument('username', InputArgument::REQUIRED, 'Username'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $username = $input->getArgument('username'); + $userId = $this->userModel->getIdByUsername($username); + + if (empty($userId)) { + $output->writeln('User not found'); + return 1; + } + + if (!$this->userModel->update(array('id' => $userId, 'twofactor_activated' => 0, 'twofactor_secret' => ''))) { + $output->writeln('Unable to update user profile'); + return 1; + } + + $output->writeln('Two-factor authentication disabled'); + return 0; + } +} diff --git a/app/Console/SubtaskExportCommand.php b/app/Console/SubtaskExportCommand.php new file mode 100644 index 0000000..c855c22 --- /dev/null +++ b/app/Console/SubtaskExportCommand.php @@ -0,0 +1,35 @@ +setName('export:subtasks') + ->setDescription('Subtasks 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): int + { + $data = $this->subtaskExport->export( + $input->getArgument('project_id'), + $input->getArgument('start_date'), + $input->getArgument('end_date') + ); + + if (is_array($data)) { + Csv::output($data); + } + return 0; + } +} diff --git a/app/Console/TaskExportCommand.php b/app/Console/TaskExportCommand.php new file mode 100644 index 0000000..bdfcff7 --- /dev/null +++ b/app/Console/TaskExportCommand.php @@ -0,0 +1,35 @@ +setName('export:tasks') + ->setDescription('Tasks 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): int + { + $data = $this->taskExport->export( + $input->getArgument('project_id'), + $input->getArgument('start_date'), + $input->getArgument('end_date') + ); + + if (is_array($data)) { + Csv::output($data); + } + return 0; + } +} diff --git a/app/Console/TaskOverdueNotificationCommand.php b/app/Console/TaskOverdueNotificationCommand.php new file mode 100644 index 0000000..3326b0e --- /dev/null +++ b/app/Console/TaskOverdueNotificationCommand.php @@ -0,0 +1,206 @@ +setName('notification:overdue-tasks') + ->setDescription('Send notifications for overdue tasks') + ->addOption('show', null, InputOption::VALUE_NONE, 'Show sent overdue tasks') + ->addOption('group', null, InputOption::VALUE_NONE, 'Group all overdue tasks for one user (from all projects) in one email') + ->addOption('manager', null, InputOption::VALUE_NONE, 'Send all overdue tasks to project manager(s) in one email') + ->addOption('project', 'p', InputOption::VALUE_REQUIRED, 'Send notifications only the given project') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + if ($input->getOption('project')) { + $tasks = $this->taskFinderModel->getOverdueTasksQuery() + ->beginOr() + ->eq(TaskModel::TABLE.'.project_id', $input->getOption('project')) + ->eq(ProjectModel::TABLE.'.identifier', $input->getOption('project')) + ->closeOr() + ->findAll(); + } else { + $tasks = $this->taskFinderModel->getOverdueTasks(); + } + + if ($input->getOption('group')) { + $tasks = $this->sendGroupOverdueTaskNotifications($tasks); + } elseif ($input->getOption('manager')) { + $tasks = $this->sendOverdueTaskNotificationsToManagers($tasks); + } else { + $tasks = $this->sendOverdueTaskNotifications($tasks); + } + + if ($input->getOption('show')) { + $this->showTable($output, $tasks); + } + return 0; + } + + public function showTable(OutputInterface $output, array $tasks) + { + $rows = array(); + + foreach ($tasks as $task) { + $rows[] = array( + $task['id'], + $task['title'], + date('Y-m-d H:i', $task['date_due']), + $task['project_id'], + $task['project_name'], + $task['assignee_name'] ?: $task['assignee_username'], + ); + } + + $table = new Table($output); + $table + ->setHeaders(array('Id', 'Title', 'Due date', 'Project Id', 'Project name', 'Assignee')) + ->setRows($rows) + ->render(); + } + + /** + * Send all overdue tasks for one user in one email + * + * @access public + * @param array $tasks + * @return array + */ + public function sendGroupOverdueTaskNotifications(array $tasks) + { + foreach ($this->groupByColumn($tasks, 'owner_id') as $user_tasks) { + $users = $this->userNotificationModel->getUsersWithNotificationEnabled($user_tasks[0]['project_id']); + + foreach ($users as $user) { + $this->sendUserOverdueTaskNotifications($user, $user_tasks); + } + } + + return $tasks; + } + + /** + * Send all overdue tasks in one email to project manager(s) + * + * @access public + * @param array $tasks + * @return array + */ + public function sendOverdueTaskNotificationsToManagers(array $tasks) + { + foreach ($this->groupByColumn($tasks, 'project_id') as $project_id => $project_tasks) { + $users = $this->userNotificationModel->getUsersWithNotificationEnabled($project_id); + $managers = array(); + + foreach ($users as $user) { + $role = $this->projectUserRoleModel->getUserRole($project_id, $user['id']); + if ($role == Role::PROJECT_MANAGER) { + $managers[] = $user; + } + } + + foreach ($managers as $manager) { + $this->sendUserOverdueTaskNotificationsToManagers($manager, $project_tasks); + } + } + + return $tasks; + } + + /** + * Send overdue tasks + * + * @access public + * @param array $tasks + * @return array + */ + public function sendOverdueTaskNotifications(array $tasks) + { + foreach ($this->groupByColumn($tasks, 'project_id') as $project_id => $project_tasks) { + $users = $this->userNotificationModel->getUsersWithNotificationEnabled($project_id); + + foreach ($users as $user) { + $this->sendUserOverdueTaskNotifications($user, $project_tasks); + } + } + + return $tasks; + } + + /** + * Send overdue tasks for a given user + * + * @access public + * @param array $user + * @param array $tasks + */ + public function sendUserOverdueTaskNotifications(array $user, array $tasks) + { + $user_tasks = array(); + $project_names = array(); + + foreach ($tasks as $task) { + if ($this->userNotificationFilterModel->shouldReceiveNotification($user, array('task' => $task))) { + $user_tasks[] = $task; + $project_names[$task['project_id']] = $task['project_name']; + } + } + + if (! empty($user_tasks)) { + $this->userNotificationModel->sendUserNotification( + $user, + TaskModel::EVENT_OVERDUE, + array('tasks' => $user_tasks, 'project_name' => implode(', ', $project_names)) + ); + } + } + + /** + * Send overdue tasks for a project manager(s) + * + * @access public + * @param array $manager + * @param array $tasks + */ + public function sendUserOverdueTaskNotificationsToManagers(array $manager, array $tasks) + { + $this->userNotificationModel->sendUserNotification( + $manager, + TaskModel::EVENT_OVERDUE, + array('tasks' => $tasks, 'project_name' => $tasks[0]['project_name']) + ); + } + + /** + * Group a collection of records by a column + * + * @access public + * @param array $collection + * @param string $column + * @return array + */ + public function groupByColumn(array $collection, $column) + { + $result = array(); + + foreach ($collection as $item) { + $result[$item[$column]][] = $item; + } + + return $result; + } +} diff --git a/app/Console/TaskTriggerCommand.php b/app/Console/TaskTriggerCommand.php new file mode 100644 index 0000000..ac1af7f --- /dev/null +++ b/app/Console/TaskTriggerCommand.php @@ -0,0 +1,52 @@ +setName('trigger:tasks') + ->setDescription('Trigger scheduler event for all tasks'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + foreach ($this->getProjectIds() as $project_id) { + $tasks = $this->taskFinderModel->getAll($project_id); + $nb_tasks = count($tasks); + + if ($nb_tasks > 0) { + $output->writeln('Trigger task event: project_id='.$project_id.', nb_tasks='.$nb_tasks); + $this->sendEvent($tasks, $project_id); + } + } + return 0; + } + + private function getProjectIds() + { + $listeners = $this->dispatcher->getListeners(TaskModel::EVENT_DAILY_CRONJOB); + $project_ids = array(); + + foreach ($listeners as $listener) { + $project_ids[] = $listener[0]->getProjectId(); + } + + return array_unique($project_ids); + } + + private function sendEvent(array &$tasks, $project_id) + { + $event = new TaskListEvent(array('project_id' => $project_id)); + $event->setTasks($tasks); + + $this->dispatcher->dispatch($event, TaskModel::EVENT_DAILY_CRONJOB); + } +} diff --git a/app/Console/TransitionExportCommand.php b/app/Console/TransitionExportCommand.php new file mode 100644 index 0000000..16fa5c8 --- /dev/null +++ b/app/Console/TransitionExportCommand.php @@ -0,0 +1,35 @@ +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): int + { + $data = $this->transitionExport->export( + $input->getArgument('project_id'), + $input->getArgument('start_date'), + $input->getArgument('end_date') + ); + + if (is_array($data)) { + Csv::output($data); + } + return 0; + } +} diff --git a/app/Console/VersionCommand.php b/app/Console/VersionCommand.php new file mode 100644 index 0000000..76332c9 --- /dev/null +++ b/app/Console/VersionCommand.php @@ -0,0 +1,29 @@ +setName('version') + ->setDescription('Display Kanboard version') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $output->writeln(APP_VERSION); + return 0; + } +} diff --git a/app/Console/WorkerCommand.php b/app/Console/WorkerCommand.php new file mode 100644 index 0000000..6214d97 --- /dev/null +++ b/app/Console/WorkerCommand.php @@ -0,0 +1,29 @@ +setName('worker') + ->setDescription('Execute queue worker') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->queueManager->listen(); + return 0; + } +} diff --git a/app/Controller/ActionController.php b/app/Controller/ActionController.php new file mode 100644 index 0000000..43acf59 --- /dev/null +++ b/app/Controller/ActionController.php @@ -0,0 +1,79 @@ +getProject(); + $actions = $this->actionModel->getAllByProject($project['id']); + + $this->response->html($this->helper->layout->project('action/index', array( + 'values' => array('project_id' => $project['id']), + 'project' => $project, + 'actions' => $actions, + 'available_actions' => $this->actionManager->getAvailableActions(), + 'available_events' => $this->eventManager->getAll(), + 'available_params' => $this->actionManager->getAvailableParameters($actions), + 'columns_list' => $this->columnModel->getList($project['id']), + 'users_list' => $this->projectUserRoleModel->getAssignableUsersList($project['id']), + 'projects_list' => $this->projectUserRoleModel->getProjectsByUser($this->userSession->getId()), + 'colors_list' => $this->colorModel->getList(), + 'categories_list' => $this->categoryModel->getList($project['id']), + 'links_list' => $this->linkModel->getList(0, false), + 'swimlane_list' => $this->swimlaneModel->getList($project['id']), + 'title' => t('Automatic actions') + ))); + } + + /** + * Confirmation dialog before removing an action + * + * @access public + */ + public function confirm() + { + $project = $this->getProject(); + $action = $this->getAction($project); + + $this->response->html($this->helper->layout->project('action/remove', array( + 'action' => $action, + 'available_events' => $this->eventManager->getAll(), + 'available_actions' => $this->actionManager->getAvailableActions(), + 'project' => $project, + 'title' => t('Remove an action') + ))); + } + + /** + * Remove an action + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $project = $this->getProject(); + $action = $this->getAction($project); + + if (! empty($action) && $this->actionModel->remove($action['id'])) { + $this->flash->success(t('Action removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this action.')); + } + + $this->response->redirect($this->helper->url->to('ActionController', 'index', array('project_id' => $project['id']))); + } +} diff --git a/app/Controller/ActionCreationController.php b/app/Controller/ActionCreationController.php new file mode 100644 index 0000000..2a38c74 --- /dev/null +++ b/app/Controller/ActionCreationController.php @@ -0,0 +1,127 @@ +getProject(); + + $this->response->html($this->template->render('action_creation/create', array( + 'project' => $project, + 'values' => array('project_id' => $project['id']), + 'available_actions' => $this->actionManager->getAvailableActions(), + ))); + } + + /** + * Choose the event according to the action (step 2) + * + * @access public + */ + public function event() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + $values['project_id'] = $project['id']; + + if (empty($values['action_name'])) { + return $this->create(); + } + + return $this->response->html($this->template->render('action_creation/event', array( + 'values' => $values, + 'project' => $project, + 'available_actions' => $this->actionManager->getAvailableActions(), + 'events' => $this->actionManager->getCompatibleEvents($values['action_name']), + ))); + } + + /** + * Define action parameters (step 3) + * + * @access public + */ + public function params() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + $values['project_id'] = $project['id']; + + if (empty($values['action_name']) || empty($values['event_name'])) { + $this->create(); + return; + } + + $action = $this->actionManager->getAction($values['action_name']); + $action_params = $action->getActionRequiredParameters(); + + if (empty($action_params)) { + $this->doCreation($project, $values + array('params' => array())); + } + + $projects_list = $this->projectUserRoleModel->getActiveProjectsByUser($this->userSession->getId()); + unset($projects_list[$project['id']]); + + $this->response->html($this->template->render('action_creation/params', array( + 'values' => $values, + 'action_params' => $action_params, + 'columns_list' => $this->columnModel->getList($project['id']), + 'users_list' => $this->projectUserRoleModel->getAssignableUsersList($project['id']), + 'projects_list' => $projects_list, + 'colors_list' => $this->colorModel->getList(), + 'categories_list' => $this->categoryModel->getList($project['id']), + 'links_list' => $this->linkModel->getList(0, false), + 'priorities_list' => $this->projectTaskPriorityModel->getPriorities($project), + 'project' => $project, + 'available_actions' => $this->actionManager->getAvailableActions(), + 'swimlane_list' => $this->swimlaneModel->getList($project['id']), + 'events' => $this->actionManager->getCompatibleEvents($values['action_name']), + ))); + } + + /** + * Save the action (last step) + * + * @access public + */ + public function save() + { + $this->doCreation($this->getProject(), $this->request->getValues()); + } + + /** + * Common method to save the action + * + * @access private + * @param array $project Project properties + * @param array $values Form values + */ + private function doCreation(array $project, array $values) + { + $values['project_id'] = $project['id']; + list($valid, ) = $this->actionValidator->validateCreation($values); + + if ($valid) { + if ($this->actionModel->create($values) !== false) { + $this->flash->success(t('Your automatic action has been created successfully.')); + } else { + $this->flash->failure(t('Unable to create your automatic action.')); + } + } + + $this->response->redirect($this->helper->url->to('ActionController', 'index', array('project_id' => $project['id']))); + } +} diff --git a/app/Controller/ActivityController.php b/app/Controller/ActivityController.php new file mode 100644 index 0000000..50f1e0b --- /dev/null +++ b/app/Controller/ActivityController.php @@ -0,0 +1,62 @@ +getUser(); + + $this->response->html($this->helper->layout->dashboard('activity/user', array( + 'title' => t('Activity stream for %s', $this->helper->user->getFullname($user)), + 'events' => $this->helper->projectActivity->getProjectsEvents($this->projectPermissionModel->getActiveProjectIds($user['id']), 100), + 'user' => $user, + ))); + } + + /** + * Activity page for a project + * + * @access public + */ + public function project() + { + $project = $this->getProject(); + + $this->response->html($this->helper->layout->app('activity/project', array( + 'title' => t('%s\'s activity', $project['name']), + 'events' => $this->helper->projectActivity->getProjectEvents($project['id']), + 'project' => $project, + ))); + } + + /** + * Display task activities + * + * @access public + */ + public function task() + { + $task = $this->getTask(); + + $this->response->html($this->helper->layout->task('activity/task', array( + 'title' => $task['title'], + 'task' => $task, + 'project' => $this->projectModel->getById($task['project_id']), + 'events' => $this->helper->projectActivity->getTaskEvents($task['id']), + 'tags' => $this->taskTagModel->getTagsByTask($task['id']), + ))); + } +} diff --git a/app/Controller/AnalyticController.php b/app/Controller/AnalyticController.php new file mode 100644 index 0000000..656e673 --- /dev/null +++ b/app/Controller/AnalyticController.php @@ -0,0 +1,192 @@ +getProject(); + list($from, $to) = $this->getDates(); + + $this->response->html($this->helper->layout->analytic('analytic/lead_cycle_time', array( + 'values' => array( + 'from' => $from, + 'to' => $to, + ), + 'project' => $project, + 'average' => $this->averageLeadCycleTimeAnalytic->build($project['id']), + 'metrics' => $this->projectDailyStatsModel->getRawMetrics($project['id'], $from, $to), + 'title' => t('Lead and cycle time'), + ))); + } + + /** + * Show comparison between actual and estimated hours chart + * + * @access public + */ + public function timeComparison() + { + $project = $this->getProject(); + + $paginator = $this->paginator + ->setUrl('AnalyticController', 'timeComparison', array('project_id' => $project['id'])) + ->setMax(30) + ->setOrder(TaskModel::TABLE.'.id') + ->setQuery( + $this->taskQuery + ->withFilter(new TaskProjectFilter($project['id'])) + ->getQuery() + ) + ->calculate(); + + $this->response->html($this->helper->layout->analytic('analytic/time_comparison', array( + 'project' => $project, + 'paginator' => $paginator, + 'metrics' => $this->estimatedTimeComparisonAnalytic->build($project['id']), + 'title' => t('Estimated vs actual time'), + ))); + } + + /** + * Show average time spent by column + * + * @access public + */ + public function averageTimeByColumn() + { + $project = $this->getProject(); + + $this->response->html($this->helper->layout->analytic('analytic/avg_time_columns', array( + 'project' => $project, + 'metrics' => $this->averageTimeSpentColumnAnalytic->build($project['id']), + 'title' => t('Average time into each column'), + ))); + } + + /** + * Show tasks distribution graph + * + * @access public + */ + public function taskDistribution() + { + $project = $this->getProject(); + + $this->response->html($this->helper->layout->analytic('analytic/task_distribution', array( + 'project' => $project, + 'metrics' => $this->taskDistributionAnalytic->build($project['id']), + 'title' => t('Task distribution'), + ))); + } + + /** + * Show users repartition + * + * @access public + */ + public function userDistribution() + { + $project = $this->getProject(); + + $this->response->html($this->helper->layout->analytic('analytic/user_distribution', array( + 'project' => $project, + 'metrics' => $this->userDistributionAnalytic->build($project['id']), + 'title' => t('User repartition'), + ))); + } + + /** + * Show cumulative flow diagram + * + * @access public + */ + public function cfd() + { + $this->commonAggregateMetrics('analytic/cfd', 'total', t('Cumulative flow diagram')); + } + + /** + * Show burndown chart + * + * @access public + */ + public function burndown() + { + $this->commonAggregateMetrics('analytic/burndown', 'score', t('Burndown chart')); + } + + /** + * Estimated vs actual time per column + * + * @access public + */ + public function estimatedVsActualByColumn() + { + $project = $this->getProject(); + + $this->response->html($this->helper->layout->analytic('analytic/estimated_actual_column', array( + 'project' => $project, + 'metrics' => $this->estimatedActualColumnAnalytic->build($project['id']), + 'title' => t('Estimated vs actual time per column'), + ))); + } + + /** + * Common method for CFD and Burdown chart + * + * @access private + * @param string $template + * @param string $column + * @param string $title + */ + private function commonAggregateMetrics($template, $column, $title) + { + $project = $this->getProject(); + list($from, $to) = $this->getDates(); + + $displayGraph = $this->projectDailyColumnStatsModel->countDays($project['id'], $from, $to) >= 2; + $metrics = $displayGraph ? $this->projectDailyColumnStatsModel->getAggregatedMetrics($project['id'], $from, $to, $column) : array(); + + $this->response->html($this->helper->layout->analytic($template, array( + 'values' => array( + 'from' => $from, + 'to' => $to, + ), + 'display_graph' => $displayGraph, + 'metrics' => $metrics, + 'project' => $project, + 'title' => $title, + ))); + } + + private function getDates() + { + $values = $this->request->getValues(); + + $from = $this->request->getStringParam('from', date('Y-m-d', strtotime('-1week'))); + $to = $this->request->getStringParam('to', date('Y-m-d')); + + if (! empty($values)) { + $from = $this->dateParser->getIsoDate($values['from']); + $to = $this->dateParser->getIsoDate($values['to']); + } + + return array($from, $to); + } +} diff --git a/app/Controller/AppController.php b/app/Controller/AppController.php new file mode 100644 index 0000000..645cae7 --- /dev/null +++ b/app/Controller/AppController.php @@ -0,0 +1,47 @@ +request->isAjax()) { + $this->response->json(array('message' => $message ?: t('Access Forbidden')), 403); + } else { + $this->response->html($this->helper->layout->app('app/forbidden', array( + 'title' => t('Access Forbidden'), + 'no_layout' => $withoutLayout, + )), 403); + } + } + + /** + * Page not found + * + * @access public + * @param boolean $withoutLayout + */ + public function notFound($withoutLayout = false) + { + $this->response->html($this->helper->layout->app('app/notfound', array( + 'title' => t('Page not found'), + 'no_layout' => $withoutLayout, + ))); + } +} diff --git a/app/Controller/AuthController.php b/app/Controller/AuthController.php new file mode 100644 index 0000000..83c93bb --- /dev/null +++ b/app/Controller/AuthController.php @@ -0,0 +1,78 @@ +userSession->isLogged()) { + $this->response->redirect($this->helper->url->to('DashboardController', 'show')); + } else { + $showCaptcha = false; + if (! empty($values['username']) && $this->userLockingModel->hasCaptcha($values['username'])) { + $showCaptcha = true; + } elseif ($this->captchaModel->isLocked($this->request->getIpAddress())) { + $showCaptcha = true; + } + $this->response->html($this->helper->layout->app('auth/index', array( + 'captcha' => $showCaptcha, + 'errors' => $errors, + 'values' => $values, + 'no_layout' => true, + 'title' => t('Login') + ))); + } + } + + /** + * Check credentials + * + * @access public + */ + public function check() + { + $values = $this->request->getValues(); + + if (REMEMBER_ME_AUTH) { + session_set('hasRememberMe', ! empty($values['remember_me'])); + } + + list($valid, $errors) = $this->authValidator->validateForm($values); + + if ($valid) { + $this->redirectAfterLogin(); + } else { + $this->login($values, $errors); + } + } + + /** + * Logout and destroy session + * + * @access public + */ + public function logout() + { + if (! DISABLE_LOGOUT) { + $this->checkCSRFParam(); + $this->sessionManager->close(); + $this->response->redirect($this->helper->url->to('AuthController', 'login')); + } else { + $this->response->redirect($this->helper->url->to('DashboardController', 'show')); + } + } +} diff --git a/app/Controller/AvatarFileController.php b/app/Controller/AvatarFileController.php new file mode 100644 index 0000000..de5267c --- /dev/null +++ b/app/Controller/AvatarFileController.php @@ -0,0 +1,122 @@ +getUser(); + + $this->response->html($this->helper->layout->user('avatar_file/show', array( + 'user' => $user, + ))); + } + + /** + * Upload Avatar + */ + public function upload() + { + $this->checkCSRFParam(); + $user = $this->getUser(); + + if (! $this->request->getFileInfo('avatar')['name']) { + $this->flash->failure(t('You must select a file to upload as your avatar!')); + } elseif (! $this->avatarFileModel->isAvatarImage($this->request->getFileInfo('avatar')['name'])) { + $this->flash->failure(t('The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)')); + } else { + if (! $this->avatarFileModel->uploadImageFile($user['id'], $this->request->getFileInfo('avatar'))) { + $this->flash->failure(t('Unable to upload files, check the permissions of your data folder.')); + } + } + + $this->renderResponse($user['id']); + } + + /** + * Remove Avatar image + */ + public function remove() + { + $this->checkCSRFParam(); + $user = $this->getUser(); + $this->avatarFileModel->remove($user['id']); + $this->userSession->refresh($user['id']); + $this->renderResponse($user['id']); + } + + /** + * Show Avatar image (public) + */ + public function image() + { + $user_id = $this->request->getIntegerParam('user_id'); + $size = $this->request->getStringParam('size', 48); + $hash = $this->request->getStringParam('hash'); + + if ($size > 100) { + $this->response->status(400); + return; + } + + $filename = $this->avatarFileModel->getFilename($user_id); + $etag = md5($filename.$size); + + if ($hash !== $etag) { + $this->response->status(404); + return; + } + + $this->response->withCache(365 * 86400, $etag); + $this->response->withContentType('image/png'); + + if ($this->request->getHeader('If-None-Match') !== '"'.$etag.'"') { + $this->response->send(); + $this->render($filename, $size); + } else { + $this->response->status(304); + } + } + + /** + * Render thumbnail from object storage + * + * @access private + * @param string $filename + * @param integer $size + */ + private function render($filename, $size) + { + try { + $blob = $this->objectStorage->get($filename); + + Thumbnail::createFromString($blob) + ->resize($size, $size) + ->toOutput(); + } catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); + } + } + + protected function renderResponse($userId) + { + if ($this->request->isAjax()) { + $this->show(); + } else { + $this->response->redirect($this->helper->url->to('AvatarFileController', 'show', array('user_id' => $userId))); + } + } +} diff --git a/app/Controller/BaseController.php b/app/Controller/BaseController.php new file mode 100644 index 0000000..e364025 --- /dev/null +++ b/app/Controller/BaseController.php @@ -0,0 +1,338 @@ +token->validateCSRFToken($this->request->getStringParam('csrf_token'))) { + throw new AccessForbiddenException(); + } + } + + protected function checkReusableCSRFParam() + { + if (! $this->token->validateReusableCSRFToken($this->request->getRawValue('csrf_token'))) { + throw new AccessForbiddenException(); + } + } + + protected function checkReusableGETCSRFParam() + { + if (! $this->token->validateReusableCSRFToken($this->request->getStringParam('csrf_token'))) { + throw new AccessForbiddenException(); + } + } + + protected function checkCSRFForm() + { + if (! $this->token->validateCSRFToken($this->request->getRawValue('csrf_token'))) { + throw new AccessForbiddenException(); + } + } + + /** + * Check webhook token + * + * @access protected + */ + protected function checkWebhookToken() + { + if (! hash_equals($this->configModel->get('webhook_token'), $this->request->getStringParam('token'))) { + throw AccessForbiddenException::getInstance()->withoutLayout(); + } + } + + /** + * Common method to get a task for task views + * + * @access protected + * @return array + * @throws PageNotFoundException + * @throws AccessForbiddenException + */ + protected function getTask() + { + $project_id = $this->request->getIntegerParam('project_id'); + $task = $this->taskFinderModel->getDetails($this->request->getIntegerParam('task_id')); + + if (empty($task)) { + throw new PageNotFoundException(); + } + + if ($project_id !== 0 && $project_id != $task['project_id']) { + throw new AccessForbiddenException(); + } + + return $task; + } + + /** + * Get Task or Project file + * + * @access protected + * @return array + * @throws PageNotFoundException + */ + protected function getFile() + { + $project_id = $this->request->getIntegerParam('project_id'); + $task_id = $this->request->getIntegerParam('task_id'); + $file_id = $this->request->getIntegerParam('file_id'); + $model = 'projectFileModel'; + + if ($task_id > 0) { + $model = 'taskFileModel'; + } + + $file = $this->$model->getById($file_id); + + if (empty($file)) { + throw new PageNotFoundException(); + } + + if (isset($file['task_id']) && $file['task_id'] != $task_id) { + throw new PageNotFoundException(); + } + + if (isset($file['project_id']) && $file['project_id'] != $project_id) { + throw new PageNotFoundException(); + } + + $file['model'] = $model; + return $file; + } + + /** + * Common method to get a project + * + * @access protected + * @param integer $project_id Default project id + * @return array + * @throws PageNotFoundException + */ + protected function getProject($project_id = 0) + { + $project_id = $this->request->getIntegerParam('project_id', $project_id); + $project = $this->projectModel->getByIdWithOwnerAndTaskCount($project_id); + + if (empty($project)) { + throw new PageNotFoundException(); + } + + return $project; + } + + /** + * Common method to get the user + * + * @access protected + * @return array + * @throws PageNotFoundException + * @throws AccessForbiddenException + */ + protected function getUser() + { + $user = $this->userModel->getById($this->request->getIntegerParam('user_id', $this->userSession->getId())); + + if (empty($user)) { + throw new PageNotFoundException(); + } + + if (! $this->userSession->isAdmin() && $this->userSession->getId() != $user['id']) { + // Always returns a 404 otherwise people might guess which user exist. + throw new PageNotFoundException(); + } + + return $user; + } + + protected function getSubtask(array $task) + { + $subtask = $this->subtaskModel->getById($this->request->getIntegerParam('subtask_id')); + + if (empty($subtask)) { + throw new PageNotFoundException(); + } + + if ($subtask['task_id'] != $task['id']) { + throw new AccessForbiddenException(); + } + + return $subtask; + } + + protected function getComment(array $task) + { + $comment = $this->commentModel->getById($this->request->getIntegerParam('comment_id')); + + if (empty($comment)) { + throw new PageNotFoundException(); + } + + if (! $this->userSession->isAdmin() && $comment['user_id'] != $this->userSession->getId()) { + throw new AccessForbiddenException(); + } + + if ($comment['task_id'] != $task['id']) { + throw new AccessForbiddenException(); + } + + return $comment; + } + + protected function getExternalTaskLink(array $task) + { + $link = $this->taskExternalLinkModel->getById($this->request->getIntegerParam('link_id')); + + if (empty($link)) { + throw new PageNotFoundException(); + } + + if ($link['task_id'] != $task['id']) { + throw new AccessForbiddenException(); + } + + return $link; + } + + protected function getInternalTaskLink(array $task) + { + $link = $this->taskLinkModel->getById($this->request->getIntegerParam('link_id')); + + if (empty($link)) { + throw new PageNotFoundException(); + } + + if ($link['task_id'] != $task['id']) { + throw new AccessForbiddenException(); + } + + return $link; + } + + protected function getColumn(array $project) + { + $column = $this->columnModel->getById($this->request->getIntegerParam('column_id')); + + if (empty($column)) { + throw new PageNotFoundException(); + } + + if ($column['project_id'] != $project['id']) { + throw new AccessForbiddenException(); + } + + return $column; + } + + protected function getSwimlane(array $project) + { + $swimlane = $this->swimlaneModel->getById($this->request->getIntegerParam('swimlane_id')); + + if (empty($swimlane)) { + throw new PageNotFoundException(); + } + + if ($swimlane['project_id'] != $project['id']) { + throw new AccessForbiddenException(); + } + + return $swimlane; + } + + protected function getCategory(array $project) + { + $category = $this->categoryModel->getById($this->request->getIntegerParam('category_id')); + + if (empty($category)) { + throw new PageNotFoundException(); + } + + if ($category['project_id'] != $project['id']) { + throw new AccessForbiddenException(); + } + + return $category; + } + + protected function getProjectTag(array $project) + { + $tag = $this->tagModel->getById($this->request->getIntegerParam('tag_id')); + + if (empty($tag)) { + throw new PageNotFoundException(); + } + + if ($tag['project_id'] != $project['id']) { + throw new AccessForbiddenException(); + } + + return $tag; + } + + protected function getAction(array $project) + { + $action = $this->actionModel->getById($this->request->getIntegerParam('action_id')); + + if (empty($action)) { + throw new PageNotFoundException(); + } + + if ($action['project_id'] != $project['id']) { + throw new AccessForbiddenException(); + } + + return $action; + } + + protected function getCustomFilter(array $project) + { + $filter = $this->customFilterModel->getById($this->request->getIntegerParam('filter_id')); + + if (empty($filter)) { + throw new PageNotFoundException(); + } + + if ($filter['project_id'] != $project['id']) { + throw new AccessForbiddenException(); + } + + return $filter; + } + + /** + * Redirect the user after the authentication + * + * @access protected + */ + protected function redirectAfterLogin() + { + $redirectUri = $this->helper->url->to('DashboardController', 'show'); + + if (session_exists('redirectAfterLogin')) { + if ($this->request->isSafeRedirectUri(session_get('redirectAfterLogin'))) { + $redirectUri = session_get('redirectAfterLogin'); + } + session_remove('redirectAfterLogin'); + } + + $this->response->redirect($redirectUri); + } +} diff --git a/app/Controller/BoardAjaxController.php b/app/Controller/BoardAjaxController.php new file mode 100644 index 0000000..3f726b6 --- /dev/null +++ b/app/Controller/BoardAjaxController.php @@ -0,0 +1,150 @@ +checkReusableGETCSRFParam(); + $project_id = $this->request->getIntegerParam('project_id'); + + if (! $project_id || ! $this->request->isAjax()) { + throw new AccessForbiddenException(); + } + + $values = $this->request->getJson(); + + if (! $this->helper->projectRole->canMoveTask($project_id, $values['src_column_id'], $values['dst_column_id'])) { + throw new AccessForbiddenException(e("You don't have the permission to move this task")); + } + + try { + $result =$this->taskPositionModel->movePosition( + $project_id, + $values['task_id'], + $values['dst_column_id'], + $values['position'], + $values['swimlane_id'] + ); + + if (! $result) { + $this->response->status(400); + } else { + $this->response->html($this->renderBoard($project_id), 201); + } + } catch (Exception $e) { + $this->response->html('
'.$e->getMessage().'
'); + } + } + + /** + * Check if the board have been changed + * + * @access public + */ + public function check() + { + $project_id = $this->request->getIntegerParam('project_id'); + $timestamp = $this->request->getIntegerParam('timestamp'); + + if (! $project_id || ! $this->request->isAjax()) { + throw new AccessForbiddenException(); + } elseif (! $this->projectModel->isModifiedSince($project_id, $timestamp)) { + $this->response->status(304); + } else { + $this->response->html($this->renderBoard($project_id)); + } + } + + /** + * Reload the board with new filters + * + * @access public + */ + public function reload() + { + $project_id = $this->request->getIntegerParam('project_id'); + + if (! $project_id || ! $this->request->isAjax()) { + throw new AccessForbiddenException(); + } + + $values = $this->request->getJson(); + $this->userSession->setFilters($project_id, empty($values['search']) ? '' : $values['search']); + + $this->response->html($this->renderBoard($project_id)); + } + + /** + * Enable collapsed mode + * + * @access public + */ + public function collapse() + { + $this->changeDisplayMode(1); + } + + /** + * Enable expanded mode + * + * @access public + */ + public function expand() + { + $this->changeDisplayMode(0); + } + + /** + * Change display mode + * + * @access private + * @param int $mode + */ + private function changeDisplayMode($mode) + { + $project_id = $this->request->getIntegerParam('project_id'); + $this->userMetadataCacheDecorator->set(UserMetadataModel::KEY_BOARD_COLLAPSED.$project_id, $mode); + + if ($this->request->isAjax()) { + $this->response->html($this->renderBoard($project_id)); + } else { + $this->response->redirect($this->helper->url->to('BoardViewController', 'show', array('project_id' => $project_id))); + } + } + + /** + * Render board + * + * @access protected + * @param integer $project_id + * @return string + */ + protected function renderBoard($project_id) + { + return $this->template->render('board/table_container', array( + 'project' => $this->projectModel->getById($project_id), + 'board_private_refresh_interval' => $this->configModel->get('board_private_refresh_interval'), + 'board_highlight_period' => $this->configModel->get('board_highlight_period'), + 'swimlanes' => $this->taskLexer + ->build($this->userSession->getFilters($project_id)) + ->format($this->boardFormatter->withProjectId($project_id)) + )); + } +} diff --git a/app/Controller/BoardPopoverController.php b/app/Controller/BoardPopoverController.php new file mode 100644 index 0000000..bbbe815 --- /dev/null +++ b/app/Controller/BoardPopoverController.php @@ -0,0 +1,47 @@ +getProject(); + $column_id = $this->request->getIntegerParam('column_id'); + $swimlane_id = $this->request->getIntegerParam('swimlane_id'); + + $this->response->html($this->template->render('board_popover/close_all_tasks_column', array( + 'project' => $project, + 'nb_tasks' => $this->taskFinderModel->countByColumnAndSwimlaneId($project['id'], $column_id, $swimlane_id), + 'column' => $this->columnModel->getColumnTitleById($column_id), + 'swimlane' => $this->swimlaneModel->getNameById($swimlane_id), + 'values' => array('column_id' => $column_id, 'swimlane_id' => $swimlane_id), + ))); + } + + /** + * Close all column tasks + * + * @access public + */ + public function closeColumnTasks() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + + $this->taskStatusModel->closeTasksBySwimlaneAndColumn($values['swimlane_id'], $values['column_id']); + $this->flash->success(t('All tasks of the column "%s" and the swimlane "%s" have been closed successfully.', $this->columnModel->getColumnTitleById($values['column_id']), $this->swimlaneModel->getNameById($values['swimlane_id']))); + $this->response->redirect($this->helper->url->to('BoardViewController', 'show', array('project_id' => $project['id']))); + } +} diff --git a/app/Controller/BoardTooltipController.php b/app/Controller/BoardTooltipController.php new file mode 100644 index 0000000..8824bf9 --- /dev/null +++ b/app/Controller/BoardTooltipController.php @@ -0,0 +1,112 @@ +getTask(); + $this->response->html($this->template->render('board/tooltip_tasklinks', array( + 'links' => $this->taskLinkModel->getAllGroupedByLabel($task['id']), + 'task' => $task, + ))); + } + + /** + * Get links on mouseover + * + * @access public + */ + public function externallinks() + { + $task = $this->getTask(); + $this->response->html($this->template->render('board/tooltip_external_links', array( + 'links' => $this->taskExternalLinkModel->getAll($task['id']), + 'task' => $task, + ))); + } + + /** + * Get subtasks on mouseover + * + * @access public + */ + public function subtasks() + { + $task = $this->getTask(); + $this->response->html($this->template->render('board/tooltip_subtasks', array( + 'subtasks' => $this->subtaskModel->getAll($task['id']), + 'task' => $task, + ))); + } + + /** + * Display all attachments during the task mouseover + * + * @access public + */ + public function attachments() + { + $task = $this->getTask(); + + $this->response->html($this->template->render('board/tooltip_files', array( + 'files' => $this->taskFileModel->getAll($task['id']), + 'task' => $task, + ))); + } + + /** + * Display task description + * + * @access public + */ + public function description() + { + $task = $this->getTask(); + + $this->response->html($this->template->render('board/tooltip_description', array( + 'task' => $task + ))); + } + + /** + * Get recurrence information on mouseover + * + * @access public + */ + public function recurrence() + { + $task = $this->getTask(); + + $this->response->html($this->template->render('task_recurrence/info', array( + 'task' => $task, + 'recurrence_trigger_list' => $this->taskRecurrenceModel->getRecurrenceTriggerList(), + 'recurrence_timeframe_list' => $this->taskRecurrenceModel->getRecurrenceTimeframeList(), + 'recurrence_basedate_list' => $this->taskRecurrenceModel->getRecurrenceBasedateList(), + ))); + } + + /** + * Display swimlane description in tooltip + * + * @access public + */ + public function swimlane() + { + $this->getProject(); + $swimlane = $this->swimlaneModel->getById($this->request->getIntegerParam('swimlane_id')); + $this->response->html($this->template->render('board/tooltip_description', array('task' => $swimlane))); + } +} diff --git a/app/Controller/BoardViewController.php b/app/Controller/BoardViewController.php new file mode 100644 index 0000000..98dd348 --- /dev/null +++ b/app/Controller/BoardViewController.php @@ -0,0 +1,72 @@ +request->getStringParam('token'); + $project = $this->projectModel->getByToken($token); + + if (empty($project)) { + throw AccessForbiddenException::getInstance()->withoutLayout(); + } + + $query = $this->taskFinderModel + ->getExtendedQuery() + ->eq(TaskModel::TABLE.'.is_active', TaskModel::STATUS_OPEN); + + $this->response->html($this->helper->layout->app('board/view_public', array( + 'project' => $project, + 'swimlanes' => $this->boardFormatter + ->withProjectId($project['id']) + ->withQuery($query) + ->format(), + 'title' => $project['name'], + 'description' => $project['description'], + 'no_layout' => true, + 'not_editable' => true, + 'board_public_refresh_interval' => $this->configModel->get('board_public_refresh_interval'), + 'board_private_refresh_interval' => $this->configModel->get('board_private_refresh_interval'), + 'board_highlight_period' => $this->configModel->get('board_highlight_period'), + ))); + } + + /** + * Show a board for a given project + * + * @access public + */ + public function show() + { + $project = $this->getProject(); + $search = $this->helper->projectHeader->getSearchQuery($project); + + $this->response->html($this->helper->layout->app('board/view_private', array( + 'project' => $project, + 'title' => $project['name'], + 'description' => $this->helper->projectHeader->getDescription($project), + 'board_private_refresh_interval' => $this->configModel->get('board_private_refresh_interval'), + 'board_highlight_period' => $this->configModel->get('board_highlight_period'), + 'swimlanes' => $this->taskLexer + ->build($search) + ->format($this->boardFormatter->withProjectId($project['id'])) + ))); + } +} diff --git a/app/Controller/CaptchaController.php b/app/Controller/CaptchaController.php new file mode 100644 index 0000000..5b4ea61 --- /dev/null +++ b/app/Controller/CaptchaController.php @@ -0,0 +1,29 @@ +response->withContentType('image/jpeg')->send(); + + $builder = new CaptchaBuilder; + $builder->build(); + session_set('captcha', $builder->getPhrase()); + $builder->output(); + } +} diff --git a/app/Controller/CategoryController.php b/app/Controller/CategoryController.php new file mode 100644 index 0000000..c27533b --- /dev/null +++ b/app/Controller/CategoryController.php @@ -0,0 +1,161 @@ +getProject(); + + $this->response->html($this->helper->layout->project('category/index', array( + 'categories' => $this->categoryModel->getAll($project['id']), + 'project' => $project, + 'colors' => $this->colorModel->getList(), + 'title' => t('Categories'), + ))); + } + + /** + * Show form to create new category + * + * @param array $values + * @param array $errors + */ + public function create(array $values = array(), array $errors = array()) + { + $project = $this->getProject(); + + $this->response->html($this->template->render('category/create', array( + 'values' => $values + array('project_id' => $project['id']), + 'colors' => $this->colorModel->getList(), + 'errors' => $errors, + 'project' => $project, + ))); + } + + /** + * Validate and save a new category + * + * @access public + */ + public function save() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + $values['project_id'] = $project['id']; + + list($valid, $errors) = $this->categoryValidator->validateCreation($values); + + if ($valid) { + if ($this->categoryModel->create($values) !== false) { + $this->flash->success(t('Your category has been created successfully.')); + $this->response->redirect($this->helper->url->to('CategoryController', 'index', array('project_id' => $project['id'])), true); + return; + } else { + $errors = array('name' => array(t('Another category with the same name exists in this project'))); + } + } + + $this->create($values, $errors); + } + + /** + * Edit a category (display the form) + * + * @access public + * @param array $values + * @param array $errors + * @throws PageNotFoundException + */ + public function edit(array $values = array(), array $errors = array()) + { + $project = $this->getProject(); + $category = $this->getCategory($project); + + $this->response->html($this->template->render('category/edit', array( + 'values' => empty($values) ? $category : $values, + 'colors' => $this->colorModel->getList(), + 'errors' => $errors, + 'project' => $project, + ))); + } + + /** + * Edit a category (validate the form and update the database) + * + * @access public + */ + public function update() + { + $project = $this->getProject(); + $category = $this->getCategory($project); + + $values = $this->request->getValues(); + $values['project_id'] = $project['id']; + $values['id'] = $category['id']; + + list($valid, $errors) = $this->categoryValidator->validateModification($values); + + if ($valid) { + if ($this->categoryModel->update($values)) { + $this->flash->success(t('This category has been updated successfully.')); + return $this->response->redirect($this->helper->url->to('CategoryController', 'index', array('project_id' => $project['id']))); + } else { + $this->flash->failure(t('Unable to update this category.')); + } + } + + return $this->edit($values, $errors); + } + + /** + * Confirmation dialog before removing a category + * + * @access public + */ + public function confirm() + { + $project = $this->getProject(); + $category = $this->getCategory($project); + + $this->response->html($this->helper->layout->project('category/remove', array( + 'project' => $project, + 'category' => $category, + ))); + } + + /** + * Remove a category + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $project = $this->getProject(); + $category = $this->getCategory($project); + + if ($this->categoryModel->remove($category['id'])) { + $this->flash->success(t('Category removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this category.')); + } + + $this->response->redirect($this->helper->url->to('CategoryController', 'index', array('project_id' => $project['id']))); + } +} diff --git a/app/Controller/ColumnController.php b/app/Controller/ColumnController.php new file mode 100644 index 0000000..73a4667 --- /dev/null +++ b/app/Controller/ColumnController.php @@ -0,0 +1,200 @@ +getProject(); + $columns = $this->columnModel->getAllWithTaskCount($project['id']); + + $this->response->html($this->helper->layout->project('column/index', array( + 'columns' => $columns, + 'project' => $project, + 'title' => t('Edit columns') + ))); + } + + /** + * Show form to create a new column + * + * @access public + * @param array $values + * @param array $errors + * @throws \Kanboard\Core\Controller\PageNotFoundException + */ + public function create(array $values = array(), array $errors = array()) + { + $project = $this->getProject(); + + if (empty($values)) { + $values = array('project_id' => $project['id']); + } + + $this->response->html($this->template->render('column/create', array( + 'values' => $values, + 'errors' => $errors, + 'project' => $project, + ))); + } + + /** + * Validate and add a new column + * + * @access public + */ + public function save() + { + $project = $this->getProject(); + $values = $this->request->getValues() + array('hide_in_dashboard' => 0); + $values['project_id'] = $project['id']; + + list($valid, $errors) = $this->columnValidator->validateCreation($values); + + if ($valid) { + $result = $this->columnModel->create( + $project['id'], + $values['title'], + $values['task_limit'], + $values['description'], + $values['hide_in_dashboard'] + ); + + if ($result !== false) { + $this->flash->success(t('Column created successfully.')); + $this->response->redirect($this->helper->url->to('ColumnController', 'index', array('project_id' => $project['id'])), true); + return; + } else { + $errors['title'] = array(t('Another column with the same name exists in the project')); + } + } + + $this->create($values, $errors); + } + + /** + * Display a form to edit a column + * + * @access public + * @param array $values + * @param array $errors + */ + public function edit(array $values = array(), array $errors = array()) + { + $project = $this->getProject(); + $column = $this->getColumn($project); + + $this->response->html($this->helper->layout->project('column/edit', array( + 'errors' => $errors, + 'values' => $values ?: $column, + 'project' => $project, + 'column' => $column, + ))); + } + + /** + * Validate and update a column + * + * @access public + */ + public function update() + { + $project = $this->getProject(); + $column = $this->getColumn($project); + + $values = $this->request->getValues() + array('hide_in_dashboard' => 0); + $values['project_id'] = $project['id']; + $values['id'] = $column['id']; + + list($valid, $errors) = $this->columnValidator->validateModification($values); + + if ($valid) { + $result = $this->columnModel->update( + $values['id'], + $values['title'], + $values['task_limit'], + $values['description'], + $values['hide_in_dashboard'] + ); + + if ($result) { + $this->flash->success(t('Board updated successfully.')); + $this->response->redirect($this->helper->url->to('ColumnController', 'index', array('project_id' => $project['id'])), true); + return; + } else { + $this->flash->failure(t('Unable to update this board.')); + } + } + + $this->edit($values, $errors); + } + + /** + * Move column position + * + * @access public + */ + public function move() + { + $this->checkReusableGETCSRFParam(); + $project = $this->getProject(); + $values = $this->request->getJson(); + + if (! empty($values) && isset($values['column_id']) && isset($values['position'])) { + $result = $this->columnModel->changePosition($project['id'], $values['column_id'], $values['position']); + $this->response->json(array('result' => $result)); + } else { + throw new AccessForbiddenException(); + } + } + + /** + * Confirm column suppression + * + * @access public + */ + public function confirm() + { + $project = $this->getProject(); + $column = $this->getColumn($project); + + $this->response->html($this->helper->layout->project('column/remove', array( + 'column' => $column, + 'project' => $project, + ))); + } + + /** + * Remove a column + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $project = $this->getProject(); + $column = $this->getColumn($project); + + if ($this->columnModel->remove($column['id'])) { + $this->flash->success(t('Column removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this column.')); + } + + $this->response->redirect($this->helper->url->to('ColumnController', 'index', array('project_id' => $project['id']))); + } +} diff --git a/app/Controller/ColumnMoveRestrictionController.php b/app/Controller/ColumnMoveRestrictionController.php new file mode 100644 index 0000000..9a75bf7 --- /dev/null +++ b/app/Controller/ColumnMoveRestrictionController.php @@ -0,0 +1,103 @@ +getProject(); + $role_id = $this->request->getIntegerParam('role_id'); + $role = $this->projectRoleModel->getById($project['id'], $role_id); + + $this->response->html($this->template->render('column_move_restriction/create', array( + 'project' => $project, + 'role' => $role, + 'columns' => $this->columnModel->getList($project['id']), + 'values' => $values + array('project_id' => $project['id'], 'role_id' => $role['role_id']), + 'errors' => $errors, + ))); + } + + /** + * Save new column restriction + */ + public function save() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + + list($valid, $errors) = $this->columnMoveRestrictionValidator->validateCreation($values); + + if ($valid) { + $restriction_id = $this->columnMoveRestrictionModel->create( + $project['id'], + $values['role_id'], + $values['src_column_id'], + $values['dst_column_id'], + isset($values['only_assigned']) && $values['only_assigned'] == 1 + ); + + if ($restriction_id !== false) { + $this->flash->success(t('The column restriction has been created successfully.')); + } else { + $this->flash->failure(t('Unable to create this column restriction.')); + } + + $this->response->redirect($this->helper->url->to('ProjectRoleController', 'show', array('project_id' => $project['id']))); + } else { + $this->create($values, $errors); + } + } + + /** + * Confirm suppression + * + * @access public + */ + public function confirm() + { + $project = $this->getProject(); + $restriction_id = $this->request->getIntegerParam('restriction_id'); + + $this->response->html($this->helper->layout->project('column_move_restriction/remove', array( + 'project' => $project, + 'restriction' => $this->columnMoveRestrictionModel->getById($project['id'], $restriction_id), + ))); + } + + /** + * Remove a restriction + * + * @access public + */ + public function remove() + { + $project = $this->getProject(); + $this->checkCSRFParam(); + $restriction_id = $this->request->getIntegerParam('restriction_id'); + + if ($this->columnMoveRestrictionModel->remove($restriction_id)) { + $this->flash->success(t('Column restriction removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this restriction.')); + } + + $this->response->redirect($this->helper->url->to('ProjectRoleController', 'show', array('project_id' => $project['id']))); + } +} diff --git a/app/Controller/ColumnRestrictionController.php b/app/Controller/ColumnRestrictionController.php new file mode 100644 index 0000000..ce2a1ca --- /dev/null +++ b/app/Controller/ColumnRestrictionController.php @@ -0,0 +1,103 @@ +getProject(); + $role_id = $this->request->getIntegerParam('role_id'); + $role = $this->projectRoleModel->getById($project['id'], $role_id); + + $this->response->html($this->template->render('column_restriction/create', array( + 'project' => $project, + 'role' => $role, + 'rules' => $this->columnRestrictionModel->getRules(), + 'columns' => $this->columnModel->getList($project['id']), + 'values' => $values + array('project_id' => $project['id'], 'role_id' => $role['role_id']), + 'errors' => $errors, + ))); + } + + /** + * Save new column restriction + */ + public function save() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + + list($valid, $errors) = $this->columnRestrictionValidator->validateCreation($values); + + if ($valid) { + $restriction_id = $this->columnRestrictionModel->create( + $project['id'], + $values['role_id'], + $values['column_id'], + $values['rule'] + ); + + if ($restriction_id !== false) { + $this->flash->success(t('The column restriction has been created successfully.')); + } else { + $this->flash->failure(t('Unable to create this column restriction.')); + } + + $this->response->redirect($this->helper->url->to('ProjectRoleController', 'show', array('project_id' => $project['id']))); + } else { + $this->create($values, $errors); + } + } + + /** + * Confirm suppression + * + * @access public + */ + public function confirm() + { + $project = $this->getProject(); + $restriction_id = $this->request->getIntegerParam('restriction_id'); + + $this->response->html($this->helper->layout->project('column_restriction/remove', array( + 'project' => $project, + 'restriction' => $this->columnRestrictionModel->getById($project['id'], $restriction_id), + ))); + } + + /** + * Remove a restriction + * + * @access public + */ + public function remove() + { + $project = $this->getProject(); + $this->checkCSRFParam(); + $restriction_id = $this->request->getIntegerParam('restriction_id'); + + if ($this->columnRestrictionModel->remove($restriction_id)) { + $this->flash->success(t('Column restriction removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this restriction.')); + } + + $this->response->redirect($this->helper->url->to('ProjectRoleController', 'show', array('project_id' => $project['id']))); + } +} diff --git a/app/Controller/CommentController.php b/app/Controller/CommentController.php new file mode 100644 index 0000000..fb6af21 --- /dev/null +++ b/app/Controller/CommentController.php @@ -0,0 +1,178 @@ +getTask(); + $values['project_id'] = $task['project_id']; + + $this->response->html($this->helper->layout->task('comment/create', array( + 'values' => $values, + 'errors' => $errors, + 'task' => $task + ))); + } + + /** + * Add a comment + * + * @access public + */ + public function save() + { + $task = $this->getTask(); + $values = $this->request->getValues(); + $values['task_id'] = $task['id']; + $values['user_id'] = $this->userSession->getId(); + + list($valid, $errors) = $this->commentValidator->validateCreation($values); + + if ($valid) { + if ($this->commentModel->create($values) !== false) { + $this->flash->success(t('Comment added successfully.')); + } else { + $this->flash->failure(t('Unable to create your comment.')); + } + + $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id']), 'comments'), true); + } else { + $this->create($values, $errors); + } + } + + /** + * Edit a comment + * + * @access public + * @param array $values + * @param array $errors + * @throws AccessForbiddenException + * @throws PageNotFoundException + */ + public function edit(array $values = array(), array $errors = array()) + { + $task = $this->getTask(); + $comment = $this->getComment($task); + + if (empty($values)) { + $values = $comment; + } + + $values['project_id'] = $task['project_id']; + + $this->response->html($this->template->render('comment/edit', array( + 'values' => $values, + 'errors' => $errors, + 'comment' => $comment, + 'task' => $task, + ))); + } + + /** + * Update and validate a comment + * + * @access public + */ + public function update() + { + $task = $this->getTask(); + $comment = $this->getComment($task); + + $values = $this->request->getValues(); + $values['id'] = $comment['id']; + $values['task_id'] = $task['id']; + $values['user_id'] = $comment['user_id']; + + list($valid, $errors) = $this->commentValidator->validateModification($values); + + if ($valid) { + if ($this->commentModel->update($values)) { + $this->flash->success(t('Comment updated successfully.')); + } else { + $this->flash->failure(t('Unable to update your comment.')); + } + + $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'])), true); + return; + } + + $this->edit($values, $errors); + } + + /** + * Confirmation dialog before removing a comment + * + * @access public + */ + public function confirm() + { + $task = $this->getTask(); + $comment = $this->getComment($task); + + $this->response->html($this->template->render('comment/remove', array( + 'comment' => $comment, + 'task' => $task, + 'title' => t('Remove a comment') + ))); + } + + /** + * Remove a comment + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $task = $this->getTask(); + $comment = $this->getComment($task); + + if ($this->commentModel->remove($comment['id'])) { + $this->flash->success(t('Comment removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this comment.')); + } + + $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id']), 'comments'), true); + } + + /** + * Toggle comment sorting + * + * @access public + */ + public function toggleSorting() + { + $this->checkReusableGETCSRFParam(); + $task = $this->getTask(); + $this->helper->comment->toggleSorting(); + + $this->response->redirect($this->helper->url->to( + 'TaskViewController', + 'show', + array('task_id' => $task['id']), + 'comments' + )); + } +} diff --git a/app/Controller/CommentListController.php b/app/Controller/CommentListController.php new file mode 100644 index 0000000..f9e66df --- /dev/null +++ b/app/Controller/CommentListController.php @@ -0,0 +1,48 @@ +getTask(); + $commentSortingDirection = $this->userMetadataCacheDecorator->get(UserMetadataModel::KEY_COMMENT_SORTING_DIRECTION, 'ASC'); + + $this->response->html($this->template->render('comment_list/show', array( + 'task' => $task, + 'comments' => $this->commentModel->getAll($task['id'], $commentSortingDirection), + 'editable' => $this->helper->user->hasProjectAccess('CommentController', 'edit', $task['project_id']), + ))); + } + + public function save() + { + $task = $this->getTask(); + $values = $this->request->getValues(); + $values['task_id'] = $task['id']; + $values['user_id'] = $this->userSession->getId(); + + list($valid, ) = $this->commentValidator->validateCreation($values); + + if ($valid && $this->commentModel->create($values) !== false) { + $this->flash->success(t('Comment added successfully.')); + } + + $this->show(); + } + + public function toggleSorting() + { + $this->helper->comment->toggleSorting(); + $this->show(); + } +} diff --git a/app/Controller/CommentMailController.php b/app/Controller/CommentMailController.php new file mode 100644 index 0000000..575b850 --- /dev/null +++ b/app/Controller/CommentMailController.php @@ -0,0 +1,74 @@ +getTask(); + + $this->response->html($this->helper->layout->task('comment_mail/create', array( + 'values' => $values, + 'errors' => $errors, + 'task' => $task, + 'members' => $this->projectPermissionModel->getMembersWithEmail($task['project_id']), + ))); + } + + public function save() + { + $task = $this->getTask(); + $values = $this->request->getValues(); + $values['task_id'] = $task['id']; + $values['user_id'] = $this->userSession->getId(); + + list($valid, $errors) = $this->commentValidator->validateEmailCreation($values); + + if ($valid) { + $this->sendByEmail($values, $task); + $values = $this->prepareComment($values); + + if ($this->commentModel->create($values) !== false) { + $this->flash->success(t('Comment sent by email successfully.')); + } else { + $this->flash->failure(t('Unable to create your comment.')); + } + + $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id']), 'comments'), true); + } else { + $this->create($values, $errors); + } + } + + protected function sendByEmail(array $values, array $task) + { + $html = $this->template->render('comment_mail/email', array('email' => $values, 'task' => $task)); + $emails = explode_csv_field($values['emails']); + + foreach ($emails as $email) { + $this->emailClient->send( + $email, + $email, + $values['subject'], + $html + ); + } + } + + protected function prepareComment(array $values) + { + $values['comment'] .= "\n\n_".t('Sent by email to "%s" (%s)', $values['emails'], $values['subject']).'_'; + + unset($values['subject']); + unset($values['emails']); + + return $values; + } +} diff --git a/app/Controller/ConfigController.php b/app/Controller/ConfigController.php new file mode 100644 index 0000000..227b3d4 --- /dev/null +++ b/app/Controller/ConfigController.php @@ -0,0 +1,254 @@ +response->html($this->helper->layout->config('config/about', array( + 'db_size' => $this->configModel->getDatabaseSize(), + 'db_version' => $this->db->getDriver()->getDatabaseVersion(), + 'db_options' => $this->configModel->getDatabaseOptions(), + 'user_agent' => $this->request->getServerVariable('HTTP_USER_AGENT'), + 'title' => t('Settings').' > '.t('About'), + ))); + } + + /** + * Save settings + * + */ + public function save() + { + $values = $this->request->getValues(); + $redirect = $this->request->getStringParam('redirect', 'application'); + + switch ($redirect) { + case 'application': + $values += array('password_reset' => 0, 'notifications_enabled' => 0); + break; + case 'project': + $values += array( + 'subtask_restriction' => 0, + 'subtask_time_tracking' => 0, + 'cfd_include_closed_tasks' => 0, + 'disable_private_project' => 0, + ); + break; + } + + list($valid, $errors) = $this->configValidator->validate($values); + + if (!$valid) { + switch ($redirect) { + case 'email': + return $this->email($values, $errors); + case 'project': + return $this->project($values, $errors); + case 'board': + return $this->board($values, $errors); + default: + return $this->application($values, $errors); + } + } + + if ($this->configModel->save($values)) { + $this->languageModel->loadCurrentLanguage(); + $this->flash->success(t('Settings saved successfully.')); + } else { + $this->flash->failure(t('Unable to save your settings.')); + } + + $this->response->redirect($this->helper->url->to('ConfigController', $redirect)); + } + + /** + * Display the application settings page + * + * @access public + */ + public function application(array $values = [], array $errors = []) + { + $this->response->html($this->helper->layout->config('config/application', array( + 'mail_transports' => $this->emailClient->getAvailableTransports(), + 'languages' => $this->languageModel->getLanguages(), + 'timezones' => $this->timezoneModel->getTimezones(), + 'date_formats' => $this->dateParser->getAvailableFormats($this->dateParser->getDateFormats(true)), + 'time_formats' => $this->dateParser->getAvailableFormats($this->dateParser->getTimeFormats()), + 'title' => t('Settings').' > '.t('Application settings'), + 'errors' => $errors, + 'values' => $values, + ))); + } + + /** + * Display the email settings page + * + * @access public + */ + public function email(array $values = [], array $errors = []) + { + if (empty($values)) { + $values = $this->configModel->getAll(); + } + + if (empty($values['mail_transport'])) { + $values['mail_transport'] = MAIL_TRANSPORT; + } + + $this->response->html($this->helper->layout->config('config/email', array( + 'values' => $values, + 'mail_transports' => $this->emailClient->getAvailableTransports(), + 'title' => t('Settings').' > '.t('Email settings'), + 'errors' => $errors, + ))); + } + + /** + * Display the project settings page + * + * @access public + */ + public function project(array $values = [], array $errors = []) + { + $this->response->html($this->helper->layout->config('config/project', array( + 'colors' => $this->colorModel->getList(), + 'default_columns' => implode(', ', $this->boardModel->getDefaultColumns()), + 'title' => t('Settings').' > '.t('Project settings'), + 'errors' => $errors, + 'values' => $values, + ))); + } + + /** + * Display the board settings page + * + * @access public + */ + public function board(array $values = [], array $errors = []) + { + $this->response->html($this->helper->layout->config('config/board', array( + 'title' => t('Settings').' > '.t('Board settings'), + 'errors' => $errors, + 'values' => $values, + ))); + } + + /** + * Display the integration settings page + * + * @access public + */ + public function integrations() + { + $this->response->html($this->helper->layout->config('config/integrations', array( + 'title' => t('Settings').' > '.t('Integrations'), + ))); + } + + /** + * Display the webhook settings page + * + * @access public + */ + public function webhook() + { + $this->response->html($this->helper->layout->config('config/webhook', array( + 'title' => t('Settings').' > '.t('Webhook settings'), + ))); + } + + /** + * Display the api settings page + * + * @access public + */ + public function api() + { + $this->response->html($this->helper->layout->config('config/api', array( + 'title' => t('Settings').' > '.t('API'), + ))); + } + + /** + * Download the Sqlite database + * + * @access public + */ + public function downloadDb() + { + $this->checkCSRFParam(); + $this->response->withFileDownload('db.sqlite.gz'); + $this->response->binary($this->configModel->downloadDatabase()); + } + + /** + * Optimize the Sqlite database + * + * @access public + */ + public function optimizeDb() + { + $this->checkCSRFParam(); + $this->configModel->optimizeDatabase(); + $this->flash->success(t('Database optimization done.')); + $this->response->redirect($this->helper->url->to('ConfigController', 'index')); + } + + /** + * Display the Sqlite database upload page + * + * @access public + */ + public function uploadDb() + { + $this->response->html($this->template->render('config/upload_db')); + } + + /** + * Replace current Sqlite db with uploaded file + * + * @access public + */ + public function saveUploadedDb() + { + $this->checkCSRFParam(); + $filename = $this->request->getFilePath('file'); + + if (!file_exists($filename) || !$this->configModel->uploadDatabase($filename)) { + $this->flash->failure(t('Unable to read uploaded file.')); + } else { + $this->flash->success(t('Database uploaded successfully.')); + } + + $this->response->redirect($this->helper->url->to('ConfigController', 'index')); + } + + /** + * Regenerate webhook token + * + * @access public + */ + public function token() + { + $type = $this->request->getStringParam('type'); + + $this->checkCSRFParam(); + $this->configModel->regenerateToken($type.'_token'); + + $this->flash->success(t('Token regenerated.')); + $this->response->redirect($this->helper->url->to('ConfigController', $type)); + } +} diff --git a/app/Controller/CronjobController.php b/app/Controller/CronjobController.php new file mode 100644 index 0000000..f5cb02a --- /dev/null +++ b/app/Controller/CronjobController.php @@ -0,0 +1,32 @@ +checkWebhookToken(); + + $input = new ArrayInput(array( + 'command' => 'cronjob', + )); + $output = new NullOutput(); + + $this->cli->setAutoExit(false); + $this->cli->run($input, $output); + + $this->response->html('Cronjob executed'); + } +} diff --git a/app/Controller/CurrencyController.php b/app/Controller/CurrencyController.php new file mode 100644 index 0000000..513dd98 --- /dev/null +++ b/app/Controller/CurrencyController.php @@ -0,0 +1,104 @@ +response->html($this->helper->layout->config('currency/show', array( + 'application_currency' => $this->configModel->get('application_currency'), + 'rates' => $this->currencyModel->getAll(), + 'currencies' => $this->currencyModel->getCurrencies(), + 'title' => t('Settings') . ' > ' . t('Currency rates'), + ))); + } + + /** + * Add or change currency rate + * + * @access public + * @param array $values + * @param array $errors + */ + public function create(array $values = array(), array $errors = array()) + { + $this->response->html($this->template->render('currency/create', array( + 'values' => $values, + 'errors' => $errors, + 'currencies' => $this->currencyModel->getCurrencies(), + ))); + } + + /** + * Validate and save a new currency rate + * + * @access public + */ + public function save() + { + $values = $this->request->getValues(); + list($valid, $errors) = $this->currencyValidator->validateCreation($values); + + if ($valid) { + if ($this->currencyModel->create($values['currency'], $values['rate'])) { + $this->flash->success(t('The currency rate has been added successfully.')); + $this->response->redirect($this->helper->url->to('CurrencyController', 'show'), true); + return; + } else { + $this->flash->failure(t('Unable to add this currency rate.')); + } + } + + $this->create($values, $errors); + } + + /** + * Change reference currency + * + * @access public + * @param array $values + * @param array $errors + */ + public function change(array $values = array(), array $errors = array()) + { + if (empty($values)) { + $values['application_currency'] = $this->configModel->get('application_currency'); + } + + $this->response->html($this->template->render('currency/change', array( + 'values' => $values, + 'errors' => $errors, + 'currencies' => $this->currencyModel->getCurrencies(), + ))); + } + + /** + * Save reference currency + * + * @access public + */ + public function update() + { + $values = $this->request->getValues(); + + if ($this->configModel->save($values)) { + $this->flash->success(t('Settings saved successfully.')); + } else { + $this->flash->failure(t('Unable to save your settings.')); + } + + $this->response->redirect($this->helper->url->to('CurrencyController', 'show'), true); + } +} diff --git a/app/Controller/CustomFilterController.php b/app/Controller/CustomFilterController.php new file mode 100644 index 0000000..a08d777 --- /dev/null +++ b/app/Controller/CustomFilterController.php @@ -0,0 +1,193 @@ +getProject(); + + $this->response->html($this->helper->layout->project('custom_filter/index', array( + 'project' => $project, + 'custom_filters' => $this->customFilterModel->getAll($project['id'], $this->userSession->getId()), + 'title' => t('Custom filters'), + ))); + } + + /** + * Show creation form for custom filters + * + * @access public + * @param array $values + * @param array $errors + */ + public function create(array $values = array(), array $errors = array()) + { + $project = $this->getProject(); + + $this->response->html($this->template->render('custom_filter/create', array( + 'values' => $values + array('project_id' => $project['id']), + 'errors' => $errors, + 'project' => $project, + ))); + } + + /** + * Save a new custom filter + * + * @access public + */ + public function save() + { + $project = $this->getProject(); + + $values = $this->request->getValues(); + $values['project_id'] = $project['id']; + $values['user_id'] = $this->userSession->getId(); + + list($valid, $errors) = $this->customFilterValidator->validateCreation($values); + + if ($valid) { + if ($this->customFilterModel->create($values) !== false) { + $this->flash->success(t('Your custom filter has been created successfully.')); + $this->response->redirect($this->helper->url->to('CustomFilterController', 'index', array('project_id' => $project['id'])), true); + return; + } else { + $this->flash->failure(t('Unable to create your custom filter.')); + } + } + + $this->create($values, $errors); + } + + /** + * Confirmation dialog before removing a custom filter + * + * @access public + */ + public function confirm() + { + $project = $this->getProject(); + $filter = $this->getCustomFilter($project); + + $this->response->html($this->helper->layout->project('custom_filter/remove', array( + 'project' => $project, + 'filter' => $filter, + 'title' => t('Remove a custom filter') + ))); + } + + /** + * Remove a custom filter + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $project = $this->getProject(); + $filter = $this->getCustomFilter($project); + + $this->checkPermission($project, $filter); + + if ($this->customFilterModel->remove($filter['id'])) { + $this->flash->success(t('Custom filter removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this custom filter.')); + } + + $this->response->redirect($this->helper->url->to('CustomFilterController', 'index', array('project_id' => $project['id']))); + } + + /** + * Edit a custom filter (display the form) + * + * @access public + * @param array $values + * @param array $errors + * @throws AccessForbiddenException + * @throws \Kanboard\Core\Controller\PageNotFoundException + */ + public function edit(array $values = array(), array $errors = array()) + { + $project = $this->getProject(); + $filter = $this->customFilterModel->getById($this->request->getIntegerParam('filter_id')); + + $this->checkPermission($project, $filter); + + $this->response->html($this->helper->layout->project('custom_filter/edit', array( + 'values' => empty($values) ? $filter : $values, + 'errors' => $errors, + 'project' => $project, + 'filter' => $filter, + 'title' => t('Edit custom filter') + ))); + } + + /** + * Edit a custom filter (validate the form and update the database) + * + * @access public + */ + public function update() + { + $project = $this->getProject(); + $filter = $this->customFilterModel->getById($this->request->getIntegerParam('filter_id')); + + $this->checkPermission($project, $filter); + + $values = $this->request->getValues(); + $values['id'] = $filter['id']; + $values['project_id'] = $project['id']; + + if (! isset($values['is_shared'])) { + $values += array('is_shared' => 0); + } + + if (! isset($values['append'])) { + $values += array('append' => 0); + } + + list($valid, $errors) = $this->customFilterValidator->validateModification($values); + + if ($valid) { + if ($this->customFilterModel->update($values)) { + $this->flash->success(t('Your custom filter has been updated successfully.')); + $this->response->redirect($this->helper->url->to('CustomFilterController', 'index', array('project_id' => $project['id'])), true); + return; + } else { + $this->flash->failure(t('Unable to update custom filter.')); + } + } + + $this->edit($values, $errors); + } + + private function checkPermission(array $project, array $filter) + { + $userID = $this->userSession->getId(); + + if ($filter['user_id'] != $userID) { + if ($this->projectUserRoleModel->getUserRole($project['id'], $userID) !== Role::PROJECT_MANAGER && ! $this->userSession->isAdmin()) { + throw new AccessForbiddenException(); + } + } + } +} diff --git a/app/Controller/DashboardController.php b/app/Controller/DashboardController.php new file mode 100644 index 0000000..f1d4047 --- /dev/null +++ b/app/Controller/DashboardController.php @@ -0,0 +1,78 @@ +getUser(); + + $this->response->html($this->helper->layout->dashboard('dashboard/overview', array( + 'title' => t('Dashboard for %s', $this->helper->user->getFullname($user)), + 'user' => $user, + 'overview_paginator' => $this->dashboardPagination->getOverview($user['id']), + 'project_paginator' => $this->projectPagination->getDashboardPaginator($user['id'], 'show', DASHBOARD_MAX_PROJECTS), + ))); + } + + /** + * My tasks + * + * @access public + */ + public function tasks() + { + $user = $this->getUser(); + + $this->response->html($this->helper->layout->dashboard('dashboard/tasks', array( + 'title' => t('Tasks overview for %s', $this->helper->user->getFullname($user)), + 'paginator' => $this->taskPagination->getDashboardPaginator($user['id'], 'tasks', 50), + 'user' => $user, + ))); + } + + /** + * My subtasks + * + * @access public + */ + public function subtasks() + { + $user = $this->getUser(); + + $this->response->html($this->helper->layout->dashboard('dashboard/subtasks', array( + 'title' => t('Subtasks overview for %s', $this->helper->user->getFullname($user)), + 'paginator' => $this->subtaskPagination->getDashboardPaginator($user['id']), + 'user' => $user, + 'nb_subtasks' => $this->subtaskModel->countByAssigneeAndTaskStatus($user['id']), + ))); + } + + /** + * My projects + * + * @access public + */ + public function projects() + { + $user = $this->getUser(); + + $this->response->html($this->helper->layout->dashboard('dashboard/projects', array( + 'title' => t('Projects overview for %s', $this->helper->user->getFullname($user)), + 'paginator' => $this->projectPagination->getDashboardPaginator($user['id'], 'projects', 25), + 'user' => $user, + ))); + } +} diff --git a/app/Controller/DocumentationController.php b/app/Controller/DocumentationController.php new file mode 100644 index 0000000..1d17d41 --- /dev/null +++ b/app/Controller/DocumentationController.php @@ -0,0 +1,19 @@ +response->html($this->template->render('config/keyboard_shortcuts')); + } +} diff --git a/app/Controller/ExportController.php b/app/Controller/ExportController.php new file mode 100644 index 0000000..a405a46 --- /dev/null +++ b/app/Controller/ExportController.php @@ -0,0 +1,114 @@ +getProject(); + + if ($this->request->isPost()) { + $from = $this->request->getRawValue('from'); + $to = $this->request->getRawValue('to'); + + if ($from && $to) { + $data = $this->$model->$method($project['id'], $from, $to); + $this->response->withFileDownload($filename.'.csv'); + $this->response->csv($data, $this->request->getRawValue('bom') === '1'); + } + } else { + $this->response->html($this->template->render('export/'.$action, array( + 'values' => array( + 'project_id' => $project['id'], + 'from' => '', + 'to' => '', + ), + 'errors' => array(), + 'project' => $project, + 'title' => $page_title, + ))); + } + } + + /** + * Task export + * + * @access public + */ + public function tasks() + { + $this->common('taskExport', 'export', t('Tasks'), 'tasks', t('Tasks Export')); + } + + /** + * Subtask export + * + * @access public + */ + public function subtasks() + { + $this->common('subtaskExport', 'export', t('Subtasks'), 'subtasks', t('Subtasks Export')); + } + + /** + * Daily project summary export + * + * @access public + */ + public function summary() + { + $project = $this->getProject(); + + if ($this->request->isPost()) { + $from = $this->request->getRawValue('from'); + $to = $this->request->getRawValue('to'); + + if ($from && $to) { + $from = $this->dateParser->getIsoDate($from); + $to = $this->dateParser->getIsoDate($to); + $data = $this->projectDailyColumnStatsModel->getAggregatedMetrics($project['id'], $from, $to); + $this->response->withFileDownload(t('Summary').'.csv'); + $this->response->csv($data, $this->request->getRawValue('bom') === '1'); + } + } else { + $this->response->html($this->template->render('export/summary', array( + 'values' => array( + 'project_id' => $project['id'], + 'from' => '', + 'to' => '', + ), + 'errors' => array(), + 'project' => $project, + 'title' => t('Daily project summary export'), + ))); + } + } + + /** + * Transition export + * + * @access public + */ + public function transitions() + { + $this->common('transitionExport', 'export', t('Transitions'), 'transitions', t('Task transitions export')); + } +} diff --git a/app/Controller/ExternalTaskCreationController.php b/app/Controller/ExternalTaskCreationController.php new file mode 100644 index 0000000..23ee853 --- /dev/null +++ b/app/Controller/ExternalTaskCreationController.php @@ -0,0 +1,97 @@ +getProject(); + $providerName = $this->request->getStringParam('provider_name'); + $taskProvider = $this->externalTaskManager->getProvider($providerName); + + if (empty($values)) { + $values = array( + 'swimlane_id' => $this->request->getIntegerParam('swimlane_id'), + 'column_id' => $this->request->getIntegerParam('column_id'), + ); + } + + $this->response->html($this->template->render('external_task_creation/step1', array( + 'project' => $project, + 'values' => $values, + 'error_message' => $errorMessage, + 'provider_name' => $providerName, + 'template' => $taskProvider->getImportFormTemplate(), + ))); + } + + public function step2(array $values = array(), array $errors = array()) + { + $project = $this->getProject(); + $providerName = $this->request->getStringParam('provider_name'); + + try { + $taskProvider = $this->externalTaskManager->getProvider($providerName); + + if (empty($values)) { + $values = $this->request->getValues(); + $externalTask = $taskProvider->fetch($taskProvider->buildTaskUri($values), $project['id']); + + $values = $externalTask->getFormValues() + array( + 'external_uri' => $externalTask->getUri(), + 'external_provider' => $providerName, + 'project_id' => $project['id'], + 'swimlane_id' => $values['swimlane_id'], + 'column_id' => $values['column_id'], + 'color_id' => $this->colorModel->getDefaultColor(), + 'owner_id' => $this->userSession->getId(), + ); + } else { + $externalTask = $taskProvider->fetch($values['external_uri'], $project['id']); + } + + $this->response->html($this->template->render('external_task_creation/step2', array( + 'project' => $project, + 'external_task' => $externalTask, + 'provider_name' => $providerName, + 'values' => $values, + 'errors' => $errors, + 'template' => $taskProvider->getCreationFormTemplate(), + 'columns_list' => $this->columnModel->getList($project['id']), + 'users_list' => $this->projectUserRoleModel->getAssignableUsersList($project['id'], true, false, $project['is_private'] == 1), + 'categories_list' => $this->categoryModel->getList($project['id']), + 'swimlanes_list' => $this->swimlaneModel->getList($project['id'], false, true), + ))); + } catch (ExternalTaskException $e) { + $this->step1($values, $e->getMessage()); + } + } + + public function step3() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + + list($valid, $errors) = $this->taskValidator->validateCreation($values); + + if (! $valid) { + $this->step2($values, $errors); + } elseif (! $this->helper->projectRole->canCreateTaskInColumn($project['id'], $values['column_id'])) { + $this->flash->failure(t('You cannot create tasks in this column.')); + $this->response->redirect($this->helper->url->to('BoardViewController', 'show', array('project_id' => $project['id'])), true); + } else { + $taskId = $this->taskCreationModel->create($values); + $this->flash->success(t('Task created successfully.')); + $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $taskId)), true); + } + } +} diff --git a/app/Controller/ExternalTaskViewController.php b/app/Controller/ExternalTaskViewController.php new file mode 100644 index 0000000..6719b65 --- /dev/null +++ b/app/Controller/ExternalTaskViewController.php @@ -0,0 +1,30 @@ +getTask(); + $taskProvider = $this->externalTaskManager->getProvider($task['external_provider']); + $externalTask = $taskProvider->fetch($task['external_uri'], $task['project_id']); + + $this->response->html($this->template->render($taskProvider->getViewTemplate(), array( + 'task' => $task, + 'external_task' => $externalTask, + ))); + } catch (ExternalTaskException $e) { + $this->response->html('
'.$e->getMessage().'
'); + } + } +} diff --git a/app/Controller/FeedController.php b/app/Controller/FeedController.php new file mode 100644 index 0000000..f494ab1 --- /dev/null +++ b/app/Controller/FeedController.php @@ -0,0 +1,58 @@ +request->getStringParam('token'); + $user = $this->userModel->getByToken($token); + + if (empty($user)) { + throw AccessForbiddenException::getInstance()->withoutLayout(); + } + + $events = $this->helper->projectActivity->getProjectsEvents($this->projectPermissionModel->getActiveProjectIds($user['id'])); + + $this->response->xml($this->template->render('feed/user', [ + 'user' => $user, + 'events' => $events, + ])); + } + + /** + * RSS feed for a project + * + * @access public + */ + public function project() + { + $token = $this->request->getStringParam('token'); + $project = $this->projectModel->getByToken($token); + + if (empty($project)) { + throw AccessForbiddenException::getInstance()->withoutLayout(); + } + + $events = $this->helper->projectActivity->getProjectEvents($project['id']); + + $this->response->xml($this->template->render('feed/project', [ + 'project' => $project, + 'events' => $events, + ])); + } +} diff --git a/app/Controller/FileViewerController.php b/app/Controller/FileViewerController.php new file mode 100644 index 0000000..a9a5f89 --- /dev/null +++ b/app/Controller/FileViewerController.php @@ -0,0 +1,163 @@ +objectStorage->get($file['path']); + } + } catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); + } + + return $content; + } + + /** + * Output file with cache + * + * @param array $file + * @param $mimetype + */ + protected function renderFileWithCache(array $file, $mimetype) + { + if ($this->request->getHeader('If-None-Match') === '"'.$file['etag'].'"') { + $this->response->status(304); + } else { + try { + $this->response->withContentType($mimetype); + $this->response->withCache(5 * 86400, $file['etag']); + $this->response->send(); + $this->objectStorage->output($file['path']); + } catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); + } + } + } + + /** + * Show file content in a popover + * + * @access public + */ + public function show() + { + $file = $this->getFile(); + $type = $this->helper->file->getPreviewType($file['name']); + $params = ['file_id' => $file['id']]; + + if (array_key_exists('etag', $file)) { + $params['etag'] = $file['etag']; + } + + $project_id = $this->request->getIntegerParam('project_id'); + if ($project_id !== 0) { + $params['project_id'] = $project_id; + } + + if ($file['model'] === 'taskFileModel') { + $params['task_id'] = $file['task_id']; + } + + $this->response->html($this->template->render('file_viewer/show', array( + 'file' => $file, + 'params' => $params, + 'type' => $type, + 'content' => $this->getFileContent($file), + ))); + } + + /** + * Display image + * + * @access public + */ + public function image() + { + $file = $this->getFile(); + $this->renderFileWithCache($file, $this->helper->file->getImageMimeType($file['name'])); + } + + /** + * Display file in browser + * + * @access public + */ + public function browser() + { + $file = $this->getFile(); + $this->renderFileWithCache($file, $this->helper->file->getBrowserViewType($file['name'])); + } + + /** + * Display image thumbnail + * + * @access public + */ + public function thumbnail() + { + $file = $this->getFile(); + $model = $file['model']; + $filename = $this->$model->getThumbnailPath($file['path']); + + $this->response->withCache(5 * 86400, $file['etag']); + $this->response->withContentType('image/png'); + + if ($this->request->getHeader('If-None-Match') === '"'.$file['etag'].'"') { + $this->response->status(304); + } else { + + $this->response->send(); + + try { + + $this->objectStorage->output($filename); + } catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); + + // Try to generate thumbnail on the fly for images uploaded before Kanboard < 1.0.19 + $data = $this->objectStorage->get($file['path']); + $this->$model->generateThumbnailFromData($file['path'], $data); + $this->objectStorage->output($this->$model->getThumbnailPath($file['path'])); + } + } + } + + /** + * File download + * + * @access public + */ + public function download() + { + try { + $file = $this->getFile(); + $this->response->withFileDownload($file['name']); + $this->response->send(); + $this->objectStorage->output($file['path']); + } catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); + } + } +} diff --git a/app/Controller/GroupAjaxController.php b/app/Controller/GroupAjaxController.php new file mode 100644 index 0000000..308bba9 --- /dev/null +++ b/app/Controller/GroupAjaxController.php @@ -0,0 +1,24 @@ +request->getStringParam('term'); + $groups = $this->groupManager->find($search); + $this->response->json($this->groupAutoCompleteFormatter->withGroups($groups)->format()); + } +} diff --git a/app/Controller/GroupCreationController.php b/app/Controller/GroupCreationController.php new file mode 100644 index 0000000..b297b19 --- /dev/null +++ b/app/Controller/GroupCreationController.php @@ -0,0 +1,49 @@ +response->html($this->template->render('group_creation/show', array( + 'errors' => $errors, + 'values' => $values, + ))); + } + + /** + * Validate and save a new group + * + * @access public + */ + public function save() + { + $values = $this->request->getValues(); + list($valid, $errors) = $this->groupValidator->validateCreation($values); + + if ($valid) { + if ($this->groupModel->create($values['name']) !== false) { + $this->flash->success(t('Group created successfully.')); + return $this->response->redirect($this->helper->url->to('GroupListController', 'index'), true); + } else { + $this->flash->failure(t('Unable to create your group.')); + } + } + + return $this->show($values, $errors); + } +} diff --git a/app/Controller/GroupListController.php b/app/Controller/GroupListController.php new file mode 100644 index 0000000..f56e63a --- /dev/null +++ b/app/Controller/GroupListController.php @@ -0,0 +1,186 @@ +request->getStringParam('search'); + $query = $this->groupModel->getQuery(); + + if ($search !== '') { + $query->ilike('groups.name', '%'.$search.'%'); + } + + $paginator = $this->paginator + ->setUrl('GroupListController', 'index') + ->setMax(30) + ->setOrder(GroupModel::TABLE.'.name') + ->setQuery($query) + ->calculate(); + + $this->response->html($this->helper->layout->app('group/index', array( + 'title' => t('Groups').' ('.$paginator->getTotal().')', + 'paginator' => $paginator, + 'values' => array( + 'search' => $search + ), + ))); + } + + /** + * List all users + * + * @access public + */ + public function users() + { + $group_id = $this->request->getIntegerParam('group_id'); + $group = $this->groupModel->getById($group_id); + + $paginator = $this->paginator + ->setUrl('GroupListController', 'users', array('group_id' => $group_id)) + ->setMax(30) + ->setOrder(UserModel::TABLE.'.username') + ->setQuery($this->groupMemberModel->getQuery($group_id)) + ->calculate(); + + $this->response->html($this->helper->layout->app('group/users', array( + 'title' => t('Members of %s', $group['name']).' ('.$paginator->getTotal().')', + 'paginator' => $paginator, + 'group' => $group, + ))); + } + + /** + * Form to associate a user to a group + * + * @access public + * @param array $values + * @param array $errors + */ + public function associate(array $values = array(), array $errors = array()) + { + $group_id = $this->request->getIntegerParam('group_id'); + $group = $this->groupModel->getById($group_id); + + if (empty($values)) { + $values['group_id'] = $group_id; + } + + $this->response->html($this->template->render('group/associate', array( + 'users' => $this->userModel->prepareList($this->groupMemberModel->getNotMembers($group_id)), + 'group' => $group, + 'errors' => $errors, + 'values' => $values, + ))); + } + + /** + * Add user to a group + * + * @access public + */ + public function addUser() + { + $values = $this->request->getValues(); + + if (isset($values['group_id']) && isset($values['user_id'])) { + if ($this->groupMemberModel->addUser($values['group_id'], $values['user_id'])) { + $this->flash->success(t('Group member added successfully.')); + return $this->response->redirect($this->helper->url->to('GroupListController', 'users', array('group_id' => $values['group_id'])), true); + } else { + $this->flash->failure(t('Unable to add group member.')); + } + } + + return $this->associate($values); + } + + /** + * Confirmation dialog to remove a user from a group + * + * @access public + */ + public function dissociate() + { + $group_id = $this->request->getIntegerParam('group_id'); + $user_id = $this->request->getIntegerParam('user_id'); + $group = $this->groupModel->getById($group_id); + $user = $this->userModel->getById($user_id); + + $this->response->html($this->template->render('group/dissociate', array( + 'group' => $group, + 'user' => $user, + ))); + } + + /** + * Remove a user from a group + * + * @access public + */ + public function removeUser() + { + $this->checkCSRFParam(); + $group_id = $this->request->getIntegerParam('group_id'); + $user_id = $this->request->getIntegerParam('user_id'); + + if ($this->groupMemberModel->removeUser($group_id, $user_id)) { + $this->flash->success(t('User removed successfully from this group.')); + } else { + $this->flash->failure(t('Unable to remove this user from the group.')); + } + + $this->response->redirect($this->helper->url->to('GroupListController', 'users', array('group_id' => $group_id)), true); + } + + /** + * Confirmation dialog to remove a group + * + * @access public + */ + public function confirm() + { + $group_id = $this->request->getIntegerParam('group_id'); + $group = $this->groupModel->getById($group_id); + + $this->response->html($this->template->render('group/remove', array( + 'group' => $group, + ))); + } + + /** + * Remove a group + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $group_id = $this->request->getIntegerParam('group_id'); + + if ($this->groupModel->remove($group_id)) { + $this->flash->success(t('Group removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this group.')); + } + + $this->response->redirect($this->helper->url->to('GroupListController', 'index'), true); + } +} diff --git a/app/Controller/GroupModificationController.php b/app/Controller/GroupModificationController.php new file mode 100644 index 0000000..bd181b7 --- /dev/null +++ b/app/Controller/GroupModificationController.php @@ -0,0 +1,53 @@ +groupModel->getById($this->request->getIntegerParam('group_id')); + } + + $this->response->html($this->template->render('group_modification/show', array( + 'errors' => $errors, + 'values' => $values, + ))); + } + + /** + * Validate and save a group + * + * @access public + */ + public function save() + { + $values = $this->request->getValues(); + list($valid, $errors) = $this->groupValidator->validateModification($values); + + if ($valid) { + if ($this->groupModel->update($values) !== false) { + $this->flash->success(t('Group updated successfully.')); + return $this->response->redirect($this->helper->url->to('GroupListController', 'index'), true); + } else { + $this->flash->failure(t('Unable to update your group.')); + } + } + + return $this->show($values, $errors); + } +} diff --git a/app/Controller/ICalendarController.php b/app/Controller/ICalendarController.php new file mode 100644 index 0000000..fbbd63a --- /dev/null +++ b/app/Controller/ICalendarController.php @@ -0,0 +1,118 @@ +request->getStringParam('token'); + $user = $this->userModel->getByToken($token); + + if (empty($user)) { + throw AccessForbiddenException::getInstance()->withoutLayout(); + } + + $startRange = strtotime('-2 months'); + $endRange = strtotime('+6 months'); + + $startColumn = $this->configModel->get('calendar_user_tasks', 'date_started'); + + $calendar = new iCalendar('Kanboard'); + $calendar->setName($user['name'] ?: $user['username']); + $calendar->setDescription($user['name'] ?: $user['username']); + $calendar->setPublishedTTL('PT1H'); + + $queryDueDateOnly = QueryBuilder::create() + ->withQuery($this->taskFinderModel->getICalQuery()) + ->withFilter(new TaskStatusFilter(TaskModel::STATUS_OPEN)) + ->withFilter(new TaskDueDateRangeFilter(array($startRange, $endRange))) + ->withFilter(new TaskAssigneeFilter($user['id'])) + ->getQuery(); + + $queryStartAndDueDate = QueryBuilder::create() + ->withQuery($this->taskFinderModel->getICalQuery()) + ->withFilter(new TaskStatusFilter(TaskModel::STATUS_OPEN)) + ->withFilter(new TaskAssigneeFilter($user['id'])) + ->getQuery() + ->addCondition($this->getConditionForTasksWithStartAndDueDate($startRange, $endRange, $startColumn, 'date_due')); + + $this->response->ical($this->taskICalFormatter + ->setCalendar($calendar) + ->addTasksWithDueDateOnly($queryDueDateOnly) + ->addTasksWithStartAndDueDate($queryStartAndDueDate, $startColumn, 'date_due') + ->format()); + } + + public function project() + { + $token = $this->request->getStringParam('token'); + $project = $this->projectModel->getByToken($token); + + if (empty($project)) { + throw AccessForbiddenException::getInstance()->withoutLayout(); + } + + $startRange = strtotime('-2 months'); + $endRange = strtotime('+6 months'); + + $startColumn = $this->configModel->get('calendar_project_tasks', 'date_started'); + + $calendar = new iCalendar('Kanboard'); + $calendar->setName($project['name']); + $calendar->setDescription($project['name']); + $calendar->setPublishedTTL('PT1H'); + + $queryDueDateOnly = QueryBuilder::create() + ->withQuery($this->taskFinderModel->getICalQuery()) + ->withFilter(new TaskStatusFilter(TaskModel::STATUS_OPEN)) + ->withFilter(new TaskProjectFilter($project['id'])) + ->withFilter(new TaskDueDateRangeFilter(array($startRange, $endRange))) + ->getQuery(); + + $queryStartAndDueDate = QueryBuilder::create() + ->withQuery($this->taskFinderModel->getICalQuery()) + ->withFilter(new TaskStatusFilter(TaskModel::STATUS_OPEN)) + ->withFilter(new TaskProjectFilter($project['id'])) + ->getQuery() + ->addCondition($this->getConditionForTasksWithStartAndDueDate($startRange, $endRange, $startColumn, 'date_due')); + + $this->response->ical($this->taskICalFormatter + ->setCalendar($calendar) + ->addTasksWithDueDateOnly($queryDueDateOnly) + ->addTasksWithStartAndDueDate($queryStartAndDueDate, $startColumn, 'date_due') + ->format()); + } + + protected function getConditionForTasksWithStartAndDueDate($start_time, $end_time, $start_column, $end_column) + { + $start_time = (int) $start_time; + $end_time = (int) $end_time; + + $start_column = $this->db->escapeIdentifier($start_column); + $end_column = $this->db->escapeIdentifier($end_column); + + $conditions = array( + "($start_column >= '$start_time' AND $start_column <= '$end_time')", + "($start_column <= '$start_time' AND $end_column >= '$start_time')", + "($start_column <= '$start_time' AND ($end_column = '0' OR $end_column IS NULL))", + ); + + return $start_column.' IS NOT NULL AND '.$start_column.' > 0 AND ('.implode(' OR ', $conditions).')'; + } +} diff --git a/app/Controller/LinkController.php b/app/Controller/LinkController.php new file mode 100644 index 0000000..2ad8a2b --- /dev/null +++ b/app/Controller/LinkController.php @@ -0,0 +1,162 @@ +linkModel->getById($this->request->getIntegerParam('link_id')); + + if (empty($link)) { + throw new PageNotFoundException(); + } + + return $link; + } + + /** + * List of labels + * + * @access public + */ + public function show() + { + $this->response->html($this->helper->layout->config('link/show', array( + 'links' => $this->linkModel->getMergedList(), + 'title' => t('Settings').' > '.t('Link labels'), + ))); + } + + /** + * Add new link label + * + * @access public + * @param array $values + * @param array $errors + */ + public function create(array $values = array(), array $errors = array()) + { + $this->response->html($this->template->render('link/create', array( + 'links' => $this->linkModel->getMergedList(), + 'values' => $values, + 'errors' => $errors, + ))); + } + + /** + * Validate and save a new link + * + * @access public + */ + public function save() + { + $values = $this->request->getValues(); + list($valid, $errors) = $this->linkValidator->validateCreation($values); + + if ($valid) { + if ($this->linkModel->create($values['label'], $values['opposite_label']) !== false) { + $this->flash->success(t('Link added successfully.')); + $this->response->redirect($this->helper->url->to('LinkController', 'show'), true); + return; + } else { + $this->flash->failure(t('Unable to create your link.')); + } + } + + $this->create($values, $errors); + } + + /** + * Edit form + * + * @access public + * @param array $values + * @param array $errors + * @throws PageNotFoundException + */ + public function edit(array $values = array(), array $errors = array()) + { + $link = $this->getLink(); + $link['label'] = t($link['label']); + + $this->response->html($this->template->render('link/edit', array( + 'values' => $values ?: $link, + 'errors' => $errors, + 'labels' => $this->linkModel->getList($link['id']), + 'link' => $link, + ))); + } + + /** + * Edit a link (validate the form and update the database) + * + * @access public + */ + public function update() + { + $values = $this->request->getValues(); + list($valid, $errors) = $this->linkValidator->validateModification($values); + + if ($valid) { + if ($this->linkModel->update($values)) { + $this->flash->success(t('Link updated successfully.')); + $this->response->redirect($this->helper->url->to('LinkController', 'show'), true); + return; + } else { + $this->flash->failure(t('Unable to update your link.')); + } + } + + $this->edit($values, $errors); + } + + /** + * Confirmation dialog before removing a link + * + * @access public + */ + public function confirm() + { + $link = $this->getLink(); + + $this->response->html($this->template->render('link/remove', array( + 'link' => $link, + ))); + } + + /** + * Remove a link + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $link = $this->getLink(); + + if ($this->linkModel->remove($link['id'])) { + $this->flash->success(t('Link removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this link.')); + } + + $this->response->redirect($this->helper->url->to('LinkController', 'show'), true); + } +} diff --git a/app/Controller/OAuthController.php b/app/Controller/OAuthController.php new file mode 100644 index 0000000..abd9b2e --- /dev/null +++ b/app/Controller/OAuthController.php @@ -0,0 +1,130 @@ +request->getStringParam('code'); + $state = $this->request->getStringParam('state'); + + if (! empty($code)) { + $this->step2($provider, $code, $state); + } else { + $this->response->redirect($this->authenticationManager->getProvider($provider)->getService()->getAuthorizationUrl()); + } + } + + /** + * Link or authenticate the user + * + * @access protected + * @param string $providerName + * @param string $code + * @param string $state + */ + protected function step2($providerName, $code, $state) + { + $provider = $this->authenticationManager->getProvider($providerName); + $provider->setCode($code); + $hasValidState = $provider->getService()->isValidateState($state); + + if ($this->userSession->isLogged()) { + if ($hasValidState) { + $this->link($provider); + } else { + $this->flash->failure(t('The OAuth2 state parameter is invalid')); + $this->response->redirect($this->helper->url->to('UserViewController', 'external', array('user_id' => $this->userSession->getId()))); + } + } else { + if ($hasValidState) { + $this->authenticate($providerName); + } else { + $this->authenticationFailure(t('The OAuth2 state parameter is invalid')); + } + } + } + + /** + * Link the account + * + * @access protected + * @param OAuthAuthenticationProviderInterface $provider + */ + protected function link(OAuthAuthenticationProviderInterface $provider) + { + if (! $provider->authenticate()) { + $this->flash->failure(t('External authentication failed')); + } else { + $this->userProfile->assign($this->userSession->getId(), $provider->getUser()); + $this->flash->success(t('Your external account is linked to your profile successfully.')); + } + + $this->response->redirect($this->helper->url->to('UserViewController', 'external', array('user_id' => $this->userSession->getId()))); + } + + /** + * Unlink external account + * + * @access public + */ + public function unlink() + { + $backend = $this->request->getStringParam('backend'); + $this->checkCSRFParam(); + + if ($this->authenticationManager->getProvider($backend)->unlink($this->userSession->getId())) { + $this->flash->success(t('Your external account is not linked anymore to your profile.')); + } else { + $this->flash->failure(t('Unable to unlink your external account.')); + } + + $this->response->redirect($this->helper->url->to('UserViewController', 'external', array('user_id' => $this->userSession->getId()))); + } + + /** + * Authenticate the account + * + * @access protected + * @param string $providerName + */ + protected function authenticate($providerName) + { + if ($this->authenticationManager->oauthAuthentication($providerName)) { + $this->redirectAfterLogin(); + } else { + $this->authenticationFailure(t('External authentication failed')); + } + } + + /** + * Show login failure page + * + * @access protected + * @param string $message + */ + protected function authenticationFailure($message) + { + $this->response->html($this->helper->layout->app('auth/index', array( + 'errors' => array('login' => $message), + 'values' => array(), + 'no_layout' => true, + 'title' => t('Login') + ))); + } +} diff --git a/app/Controller/PasswordResetController.php b/app/Controller/PasswordResetController.php new file mode 100644 index 0000000..07edd6c --- /dev/null +++ b/app/Controller/PasswordResetController.php @@ -0,0 +1,132 @@ +checkActivation(); + + $this->response->html($this->helper->layout->app('password_reset/create', array( + 'errors' => $errors, + 'values' => $values, + 'no_layout' => true, + ))); + } + + /** + * Validate and send the email + */ + public function save() + { + $this->checkActivation(); + + $values = $this->request->getValues(); + list($valid, $errors) = $this->passwordResetValidator->validateCreation($values); + + if ($valid) { + $this->sendEmail($values['username']); + $this->response->redirect($this->helper->url->to('AuthController', 'login')); + } else { + $this->create($values, $errors); + } + } + + /** + * Show the form to set a new password + * + * @param array $values + * @param array $errors + * @throws \Kanboard\Core\Controller\BaseException + */ + public function change(array $values = array(), array $errors = array()) + { + $this->checkActivation(); + + $token = $this->request->getStringParam('token'); + $user_id = $this->passwordResetModel->getUserIdByToken($token); + + if ($user_id !== false) { + $this->response->html($this->helper->layout->app('password_reset/change', array( + 'token' => $token, + 'errors' => $errors, + 'values' => $values, + 'no_layout' => true, + ))); + } else { + $this->response->redirect($this->helper->url->to('AuthController', 'login')); + } + } + + /** + * Set the new password + */ + public function update() + { + $this->checkActivation(); + + $token = $this->request->getStringParam('token'); + $values = $this->request->getValues(); + list($valid, $errors) = $this->passwordResetValidator->validateModification($values); + + if ($valid) { + $user_id = $this->passwordResetModel->getUserIdByToken($token); + + if ($user_id !== false) { + $this->userModel->update(array('id' => $user_id, 'password' => $values['password'])); + $this->passwordResetModel->disable($user_id); + } + + return $this->response->redirect($this->helper->url->to('AuthController', 'login')); + } + + return $this->change($values, $errors); + } + + /** + * Send the email + * + * @param string $username + */ + protected function sendEmail($username) + { + $token = $this->passwordResetModel->create($username); + + if ($token !== false) { + $user = $this->userCacheDecorator->getByUsername($username); + + $this->emailClient->send( + $user['email'], + $user['name'] ?: $user['username'], + t('Password Reset for Kanboard'), + $this->template->render('password_reset/email', array('token' => $token)) + ); + } + } + + /** + * Check feature availability + */ + protected function checkActivation() + { + if ($this->configModel->get('password_reset', 0) == 0) { + throw AccessForbiddenException::getInstance()->withoutLayout(); + } + } +} diff --git a/app/Controller/PluginController.php b/app/Controller/PluginController.php new file mode 100644 index 0000000..a11ed57 --- /dev/null +++ b/app/Controller/PluginController.php @@ -0,0 +1,153 @@ +response->html($this->helper->layout->plugin('plugin/show', array( + 'plugins' => $this->pluginLoader->getPlugins(), + 'incompatible_plugins' => $this->pluginLoader->getIncompatiblePlugins(), + 'title' => t('Installed Plugins'), + 'is_configured' => Installer::isConfigured(), + ))); + } + + /** + * Display list of available plugins + */ + public function directory() + { + $installedPlugins = []; + $availablePlugins = []; + $isConfigured = Installer::isConfigured(); + + if ($isConfigured) { + foreach ($this->pluginLoader->getPlugins() as $plugin) { + $installedPlugins[$plugin->getPluginName()] = $plugin->getPluginVersion(); + } + + $availablePlugins = Directory::getInstance($this->container)->getAvailablePlugins(); + } + + $this->response->html($this->helper->layout->plugin('plugin/directory', array( + 'installed_plugins' => $installedPlugins, + 'available_plugins' => $availablePlugins, + 'title' => t('Plugin Directory'), + 'is_configured' => $isConfigured, + ))); + } + + /** + * Install plugin from URL + * + * @throws \Kanboard\Core\Controller\AccessForbiddenException + */ + public function install() + { + if (! Installer::isConfigured()) { + throw new AccessForbiddenException(); + } + + $this->checkCSRFParam(); + $pluginArchiveUrl = urldecode($this->request->getStringParam('archive_url')); + + try { + $installer = new Installer($this->container); + $installer->install($pluginArchiveUrl); + $this->flash->success(t('Plugin installed successfully.')); + } catch (PluginInstallerException $e) { + $this->flash->failure($e->getMessage()); + } + + $this->response->redirect($this->helper->url->to('PluginController', 'show')); + } + + /** + * Update plugin from URL + * + * @throws \Kanboard\Core\Controller\AccessForbiddenException + */ + public function update() + { + if (! Installer::isConfigured()) { + throw new AccessForbiddenException(); + } + + $this->checkCSRFParam(); + $pluginArchiveUrl = urldecode($this->request->getStringParam('archive_url')); + + try { + $installer = new Installer($this->container); + $installer->update($pluginArchiveUrl); + $this->flash->success(t('Plugin updated successfully.')); + } catch (PluginInstallerException $e) { + $this->flash->failure($e->getMessage()); + } + + $this->response->redirect($this->helper->url->to('PluginController', 'show')); + } + + /** + * Confirmation before to remove the plugin + */ + public function confirm() + { + if (! Installer::isConfigured()) { + throw new AccessForbiddenException(); + } + + $pluginId = $this->request->getStringParam('pluginId'); + $plugins = array_merge( + $this->pluginLoader->getPlugins(), + $this->pluginLoader->getIncompatiblePlugins() + ); + + $this->response->html($this->template->render('plugin/remove', array( + 'plugin_id' => $pluginId, + 'plugin' => $plugins[$pluginId], + ))); + } + + /** + * Remove a plugin + * + * @throws \Kanboard\Core\Controller\AccessForbiddenException + */ + public function uninstall() + { + if (! Installer::isConfigured()) { + throw new AccessForbiddenException(); + } + + $this->checkCSRFParam(); + $pluginId = $this->request->getStringParam('pluginId'); + + try { + $installer = new Installer($this->container); + $installer->uninstall($pluginId); + $this->flash->success(t('Plugin removed successfully.')); + } catch (PluginInstallerException $e) { + $this->flash->failure($e->getMessage()); + } + + $this->response->redirect($this->helper->url->to('PluginController', 'show')); + } +} diff --git a/app/Controller/PredefinedTaskDescriptionController.php b/app/Controller/PredefinedTaskDescriptionController.php new file mode 100644 index 0000000..2187c30 --- /dev/null +++ b/app/Controller/PredefinedTaskDescriptionController.php @@ -0,0 +1,116 @@ +getProject(); + + $this->response->html($this->template->render('predefined_task_description/create', array( + 'values' => $values, + 'errors' => $errors, + 'project' => $project, + ))); + } + + public function save() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + + list($valid, $errors) = $this->predefinedTaskDescriptionValidator->validate($values); + + if ($valid) { + if ($this->predefinedTaskDescriptionModel->create($project['id'], $values['title'], $values['description']) !== false) { + $this->flash->success(t('Template created successfully.')); + } else { + $this->flash->failure(t('Unable to create this template.')); + } + + $this->response->redirect($this->helper->url->to('ProjectPredefinedContentController', 'show', array('project_id' => $project['id'])), true); + } else { + $this->create($values, $errors); + } + } + + public function edit(array $values = array(), array $errors = array()) + { + $project = $this->getProject(); + $template = $this->predefinedTaskDescriptionModel->getById($project['id'], $this->request->getIntegerParam('id')); + + $this->response->html($this->template->render('predefined_task_description/edit', array( + 'values' => empty($values) ? $template : $values, + 'template' => $template, + 'errors' => $errors, + 'project' => $project, + ))); + } + + public function update() + { + $project = $this->getProject(); + $template = $this->getTemplate($project); + $values = $this->request->getValues(); + + list($valid, $errors) = $this->predefinedTaskDescriptionValidator->validate($values); + + if ($valid) { + if ($this->predefinedTaskDescriptionModel->update($project['id'], $template['id'], $values['title'], $values['description']) !== false) { + $this->flash->success(t('Template updated successfully.')); + } else { + $this->flash->failure(t('Unable to update this template.')); + } + + $this->response->redirect($this->helper->url->to('ProjectPredefinedContentController', 'show', array('project_id' => $project['id'])), true); + } else { + $this->edit($values, $errors); + } + } + + public function confirm() + { + $project = $this->getProject(); + $template = $this->getTemplate($project); + + $this->response->html($this->template->render('predefined_task_description/remove', array( + 'template' => $template, + 'project' => $project, + ))); + } + + public function remove() + { + $this->checkCSRFParam(); + $project = $this->getProject(); + $template = $this->getTemplate($project); + + if ($this->predefinedTaskDescriptionModel->remove($project['id'], $template['id'])) { + $this->flash->success(t('Template removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this template.')); + } + + $this->response->redirect($this->helper->url->to('ProjectPredefinedContentController', 'show', array('project_id' => $project['id'])), true); + } + + protected function getTemplate(array $project) + { + $template = $this->predefinedTaskDescriptionModel->getById($project['id'], $this->request->getIntegerParam('id')); + + if (empty($template)) { + throw new PageNotFoundException(); + } + + return $template; + } +} diff --git a/app/Controller/ProjectActionDuplicationController.php b/app/Controller/ProjectActionDuplicationController.php new file mode 100644 index 0000000..713f5f1 --- /dev/null +++ b/app/Controller/ProjectActionDuplicationController.php @@ -0,0 +1,44 @@ +getProject(); + $projects = $this->projectUserRoleModel->getProjectsByUser($this->userSession->getId()); + unset($projects[$project['id']]); + + $this->response->html($this->template->render('project_action_duplication/show', array( + 'project' => $project, + 'projects_list' => $projects, + ))); + } + + public function save() + { + $project = $this->getProject(); + $src_project_id = $this->request->getValue('src_project_id'); + + if (empty($src_project_id) || ! $this->projectPermissionModel->isUserAllowed($src_project_id, $this->userSession->getId())) { + throw new AccessForbiddenException(); + } + + if ($this->actionModel->duplicate($src_project_id, $project['id'])) { + $this->flash->success(t('Actions duplicated successfully.')); + } else { + $this->flash->failure(t('Unable to duplicate actions.')); + } + + $this->response->redirect($this->helper->url->to('ActionController', 'index', array('project_id' => $project['id']))); + } +} diff --git a/app/Controller/ProjectCreationController.php b/app/Controller/ProjectCreationController.php new file mode 100644 index 0000000..029c43f --- /dev/null +++ b/app/Controller/ProjectCreationController.php @@ -0,0 +1,139 @@ + t('Do not duplicate anything')) + $this->projectUserRoleModel->getActiveProjectsByUser($this->userSession->getId()); + + $this->response->html($this->helper->layout->app('project_creation/create', array( + 'values' => $values, + 'errors' => $errors, + 'is_private' => $is_private, + 'projects_list' => $projects_list, + 'title' => $is_private ? t('New personal project') : t('New project'), + ))); + } + + /** + * Display a form to create a private project + * + * @access public + * @param array $values + * @param array $errors + */ + public function createPrivate(array $values = array(), array $errors = array()) + { + $values['is_private'] = 1; + $this->create($values, $errors); + } + + /** + * Validate and save a new project + * + * @access public + */ + public function save() + { + $values = $this->request->getValues(); + list($valid, $errors) = $this->projectValidator->validateCreation($values); + + if ($valid) { + $project_id = $this->createOrDuplicate($values); + + if ($project_id > 0) { + $this->flash->success(t('Your project has been created successfully.')); + return $this->response->redirect($this->helper->url->to('ProjectViewController', 'show', array('project_id' => $project_id))); + } + + $this->flash->failure(t('Unable to create your project.')); + } + + return $this->create($values, $errors); + } + + /** + * Create or duplicate a project + * + * @access private + * @param array $values + * @return boolean|integer + */ + private function createOrDuplicate(array $values) + { + if (empty($values['src_project_id'])) { + return $this->createNewProject($values); + } + + return $this->duplicateNewProject($values); + } + + /** + * Save a new project + * + * @access private + * @param array $values + * @return boolean|integer + */ + private function createNewProject(array $values) + { + $project = array( + 'name' => $values['name'], + 'is_private' => $values['is_private'], + 'identifier' => $values['identifier'], + 'per_swimlane_task_limits' => array_key_exists('per_swimlane_task_limits', $values) ? $values['per_swimlane_task_limits'] : 0, + 'task_limit' => $values['task_limit'], + ); + + return $this->projectModel->create($project, $this->userSession->getId(), true); + } + + /** + * Create from another project + * + * @access private + * @param array $values + * @return boolean|integer + */ + private function duplicateNewProject(array $values) + { + $selection = array(); + + if (! $this->projectPermissionModel->isUserAllowed($values['src_project_id'], $this->userSession->getId())) { + throw new AccessForbiddenException(); + } + + foreach ($this->projectDuplicationModel->getOptionalSelection() as $item) { + if (isset($values[$item]) && $values[$item] == 1) { + $selection[] = $item; + } + } + + return $this->projectDuplicationModel->duplicate( + $values['src_project_id'], + $selection, + $this->userSession->getId(), + $values['name'], + $values['is_private'] == 1, + $values['identifier'] + ); + } +} diff --git a/app/Controller/ProjectEditController.php b/app/Controller/ProjectEditController.php new file mode 100644 index 0000000..dd53450 --- /dev/null +++ b/app/Controller/ProjectEditController.php @@ -0,0 +1,82 @@ +getProject(); + + $this->response->html($this->helper->layout->project('project_edit/show', array( + 'owners' => $this->projectUserRoleModel->getAssignableUsersList($project['id'], true), + 'values' => empty($values) ? $project : $values, + 'errors' => $errors, + 'project' => $project, + 'title' => t('Edit project') + ))); + } + + /** + * Validate and update a project + * + * @access public + */ + public function update() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + + $values = $this->prepareValues($project, $values); + list($valid, $errors) = $this->projectValidator->validateModification($values); + + if ($valid) { + if ($this->projectModel->update($values)) { + $this->flash->success(t('Project updated successfully.')); + return $this->response->redirect($this->helper->url->to('ProjectEditController', 'show', array('project_id' => $project['id'])), true); + } else { + $this->flash->failure(t('Unable to update this project.')); + } + } + + return $this->show($values, $errors); + } + + /** + * Prepare form values + * + * @access private + * @param array $project + * @param array $values + * @return array + */ + private function prepareValues(array $project, array $values) + { + $values['id'] = $project['id']; + + if (isset($values['is_private'])) { + if (! $this->helper->user->hasProjectAccess('ProjectCreationController', 'create', $project['id'])) { + unset($values['is_private']); + } + } elseif ($project['is_private'] == 1 && ! isset($values['is_private'])) { + if ($this->helper->user->hasProjectAccess('ProjectCreationController', 'create', $project['id'])) { + $values += array('is_private' => 0); + } + } + + return $values; + } +} diff --git a/app/Controller/ProjectFileController.php b/app/Controller/ProjectFileController.php new file mode 100644 index 0000000..e515aa3 --- /dev/null +++ b/app/Controller/ProjectFileController.php @@ -0,0 +1,89 @@ +getProject(); + + $this->response->html($this->template->render('project_file/create', array( + 'project' => $project, + 'max_size' => get_upload_max_size(), + ))); + } + + /** + * Save uploaded files + * + * @access public + */ + public function save() + { + $this->checkReusableCSRFParam(); + $project = $this->getProject(); + $result = $this->projectFileModel->uploadFiles($project['id'], $this->request->getFileInfo('files')); + + if ($this->request->isAjax()) { + if (! $result) { + $this->response->json(array('message' => t('Unable to upload files, check the permissions of your data folder.')), 500); + } else { + $this->response->json(array('message' => 'OK')); + } + } else { + if (! $result) { + $this->flash->failure(t('Unable to upload files, check the permissions of your data folder.')); + } + + $this->response->redirect($this->helper->url->to('ProjectOverviewController', 'show', array('project_id' => $project['id'])), true); + } + } + + /** + * Remove a file + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $project = $this->getProject(); + $file = $this->getFile(); + + if ($this->projectFileModel->remove($file['id'])) { + $this->flash->success(t('File removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this file.')); + } + + $this->response->redirect($this->helper->url->to('ProjectOverviewController', 'show', array('project_id' => $project['id']))); + } + + /** + * Confirmation dialog before removing a file + * + * @access public + */ + public function confirm() + { + $project = $this->getProject(); + $file = $this->getFile(); + + $this->response->html($this->template->render('project_file/remove', array( + 'project' => $project, + 'file' => $file, + ))); + } +} diff --git a/app/Controller/ProjectListController.php b/app/Controller/ProjectListController.php new file mode 100644 index 0000000..490a685 --- /dev/null +++ b/app/Controller/ProjectListController.php @@ -0,0 +1,47 @@ +userSession->isAdmin()) { + $projectIds = $this->projectModel->getAllIds(); + } else { + $projectIds = $this->projectPermissionModel->getProjectIds($this->userSession->getId()); + } + + $query = $this->projectModel->getQueryByProjectIds($projectIds); + $search = $this->request->getStringParam('search'); + + if ($search !== '') { + $query->ilike('projects.name', '%' . $search . '%'); + } + + $paginator = $this->paginator + ->setUrl('ProjectListController', 'show') + ->setMax(20) + ->setOrder('name') + ->setQuery($query) + ->calculate(); + + $this->response->html($this->helper->layout->dashboard('project_list/listing', array( + 'paginator' => $paginator, + 'title' => t('Projects') . ' (' . $paginator->getTotal() . ')', + 'values' => array('search' => $search), + 'user' => $this->getUser(), + ))); + } +} diff --git a/app/Controller/ProjectOverviewController.php b/app/Controller/ProjectOverviewController.php new file mode 100644 index 0000000..477c1dd --- /dev/null +++ b/app/Controller/ProjectOverviewController.php @@ -0,0 +1,33 @@ +getProject(); + $columns = $this->columnModel->getAllWithTaskCount($project['id']); + + $this->response->html($this->helper->layout->app('project_overview/show', array( + 'project' => $project, + 'columns' => $columns, + 'title' => $project['name'], + 'description' => $this->helper->projectHeader->getDescription($project), + 'users' => $this->projectUserRoleModel->getAllUsersGroupedByRole($project['id']), + 'roles' => $this->projectRoleModel->getList($project['id']), + 'events' => $this->helper->projectActivity->getProjectEvents($project['id'], 10), + 'images' => $this->projectFileModel->getAllImages($project['id']), + 'files' => $this->projectFileModel->getAllDocuments($project['id']), + ))); + } +} diff --git a/app/Controller/ProjectPermissionController.php b/app/Controller/ProjectPermissionController.php new file mode 100644 index 0000000..f12a3f2 --- /dev/null +++ b/app/Controller/ProjectPermissionController.php @@ -0,0 +1,221 @@ +getProject(); + + if (empty($values)) { + $values['role'] = Role::PROJECT_MEMBER; + } + + $this->response->html($this->helper->layout->project('project_permission/index', array( + 'project' => $project, + 'users' => $this->projectUserRoleModel->getUsers($project['id']), + 'groups' => $this->projectGroupRoleModel->getGroups($project['id']), + 'roles' => $this->projectRoleModel->getList($project['id']), + 'values' => $values, + 'errors' => $errors, + 'title' => t('Project Permissions'), + ))); + } + + /** + * Add user to the project + * + * @access public + */ + public function addUser() + { + $this->checkCSRFForm(); + + $project = $this->getProject(); + $values = $this->request->getValues(); + + if (empty($values['user_id']) && ! empty($values['external_id']) && ! empty($values['external_id_column'])) { + $values['user_id'] = $this->userModel->getOrCreateExternalUserId($values['username'], $values['name'], $values['external_id_column'], $values['external_id']); + } + + if (empty($values['user_id'])) { + $this->flash->failure(t('User not found.')); + } elseif ($this->projectUserRoleModel->addUser($project['id'], $values['user_id'], $values['role'])) { + $this->flash->success(t('Project updated successfully.')); + } else { + $this->flash->failure(t('Unable to update this project.')); + } + + $this->response->redirect($this->helper->url->to('ProjectPermissionController', 'index', array('project_id' => $project['id']))); + } + + /** + * Revoke user access + * + * @access public + */ + public function removeUser() + { + $this->checkCSRFParam(); + $project = $this->getProject(); + $user_id = $this->request->getIntegerParam('user_id'); + + if ($this->projectUserRoleModel->removeUser($project['id'], $user_id)) { + $this->flash->success(t('Project updated successfully.')); + } else { + $this->flash->failure(t('Unable to update this project.')); + } + + $this->response->redirect($this->helper->url->to('ProjectPermissionController', 'index', array('project_id' => $project['id']))); + } + + /** + * Change user role + * + * @access public + */ + public function changeUserRole() + { + $this->checkReusableGETCSRFParam(); + $project = $this->getProject(); + + if (! $this->request->isAjax()) { + $this->response->json(array('status' => 'error'), 400); + return; + } + + $values = $this->request->getJson(); + + if (empty($project) || + empty($values) + ) { + $this->response->json(array('status' => 'error'), 500); + return; + } + + $userRole = $this->projectUserRoleModel->getUserRole($project['id'], $values['id']); + $usersGroupedByRole = $this->projectUserRoleModel->getAllUsersGroupedByRole($project['id']); + + if ($userRole === 'project-manager' && + $values['role'] !== 'project-manager' && + count($usersGroupedByRole['project-manager']) <= 1 + ) { + $this->response->json(array('status' => 'error'), 500); + return; + } + + $this->projectUserRoleModel->changeUserRole($project['id'], $values['id'], $values['role']); + + $this->response->json(array('status' => 'ok')); + } + + /** + * Add group to the project + * + * @access public + */ + public function addGroup() + { + $this->checkCSRFForm(); + + $project = $this->getProject(); + $values = $this->request->getValues(); + + if (empty($values['group_id']) && ! empty($values['external_id'])) { + $values['group_id'] = $this->groupModel->getOrCreateExternalGroupId($values['name'], $values['external_id']); + } + + if (empty($values['group_id'])) { + $this->flash->failure(t('Unable to find this group.')); + } else { + if ($this->projectGroupRoleModel->addGroup($project['id'], $values['group_id'], $values['role'])) { + $this->flash->success(t('Project updated successfully.')); + } else { + $this->flash->failure(t('Unable to update this project.')); + } + } + + $this->response->redirect($this->helper->url->to('ProjectPermissionController', 'index', array('project_id' => $project['id']))); + } + + /** + * Revoke group access + * + * @access public + */ + public function removeGroup() + { + $this->checkCSRFParam(); + $project = $this->getProject(); + $group_id = $this->request->getIntegerParam('group_id'); + + if ($this->projectGroupRoleModel->removeGroup($project['id'], $group_id)) { + $this->flash->success(t('Project updated successfully.')); + } else { + $this->flash->failure(t('Unable to update this project.')); + } + + $this->response->redirect($this->helper->url->to('ProjectPermissionController', 'index', array('project_id' => $project['id']))); + } + + /** + * Change group role + * + * @access public + */ + public function changeGroupRole() + { + $this->checkReusableGETCSRFParam(); + $project = $this->getProject(); + + if (! $this->request->isAjax()) { + $this->response->json(array('status' => 'error'), 400); + return; + } + + $values = $this->request->getJson(); + + if (! empty($project) && ! empty($values) && $this->projectGroupRoleModel->changeGroupRole($project['id'], $values['id'], $values['role'])) { + $this->response->json(array('status' => 'ok')); + } else { + $this->response->json(array('status' => 'error')); + } + } +} diff --git a/app/Controller/ProjectPredefinedContentController.php b/app/Controller/ProjectPredefinedContentController.php new file mode 100644 index 0000000..709d6b7 --- /dev/null +++ b/app/Controller/ProjectPredefinedContentController.php @@ -0,0 +1,50 @@ +getProject(); + + $this->response->html($this->helper->layout->project('project_predefined_content/show', array( + 'values' => empty($values) ? $project : $values, + 'errors' => $errors, + 'project' => $project, + 'predefined_task_descriptions' => $this->predefinedTaskDescriptionModel->getAll($project['id']), + 'title' => t('Predefined Contents'), + ))); + } + + public function update() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + + $values = array( + 'id' => $project['id'], + 'name' => $project['name'], + 'predefined_email_subjects' => isset($values['predefined_email_subjects']) ? $values['predefined_email_subjects'] : '', + ); + + list($valid, $errors) = $this->projectValidator->validateModification($values); + + if ($valid) { + if ($this->projectModel->update($values)) { + $this->flash->success(t('Project updated successfully.')); + return $this->response->redirect($this->helper->url->to('ProjectPredefinedContentController', 'show', array('project_id' => $project['id'])), true); + } else { + $this->flash->failure(t('Unable to update this project.')); + } + } + + return $this->show($values, $errors); + } +} diff --git a/app/Controller/ProjectRoleController.php b/app/Controller/ProjectRoleController.php new file mode 100644 index 0000000..9550375 --- /dev/null +++ b/app/Controller/ProjectRoleController.php @@ -0,0 +1,162 @@ +getProject(); + + $this->response->html($this->helper->layout->project('project_role/show', array( + 'project' => $project, + 'roles' => $this->projectRoleModel->getAllWithRestrictions($project['id']), + 'title' => t('Custom Project Roles'), + ))); + } + + /** + * Show form to create new role + * + * @param array $values + * @param array $errors + * @throws AccessForbiddenException + */ + public function create(array $values = array(), array $errors = array()) + { + $project = $this->getProject(); + + $this->response->html($this->template->render('project_role/create', array( + 'project' => $project, + 'values' => $values + array('project_id' => $project['id']), + 'errors' => $errors, + ))); + } + + /** + * Save new role + */ + public function save() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + + list($valid, $errors) = $this->projectRoleValidator->validateCreation($values); + + if ($valid) { + $role_id = $this->projectRoleModel->create($project['id'], $values['role']); + + if ($role_id !== false) { + $this->flash->success(t('Your custom project role has been created successfully.')); + } else { + $this->flash->failure(t('Unable to create custom project role.')); + } + + $this->response->redirect($this->helper->url->to('ProjectRoleController', 'show', array('project_id' => $project['id']))); + } else { + $this->create($values, $errors); + } + } + + /** + * Show form to change existing role + * + * @param array $values + * @param array $errors + * @throws AccessForbiddenException + */ + public function edit(array $values = array(), array $errors = array()) + { + $project = $this->getProject(); + $role = $this->getRole($project['id']); + + if (empty($values)) { + $values = $role; + } + + $this->response->html($this->template->render('project_role/edit', array( + 'role' => $role, + 'project' => $project, + 'values' => $values, + 'errors' => $errors, + ))); + } + + /** + * Update role + */ + public function update() + { + $project = $this->getProject(); + $role = $this->getRole($project['id']); + + $values = $this->request->getValues(); + + list($valid, $errors) = $this->projectRoleValidator->validateModification($values); + + if ($valid) { + if ($this->projectRoleModel->update($role['role_id'], $project['id'], $values['role'])) { + $this->flash->success(t('Your custom project role has been updated successfully.')); + } else { + $this->flash->failure(t('Unable to update custom project role.')); + } + + $this->response->redirect($this->helper->url->to('ProjectRoleController', 'show', array('project_id' => $project['id']))); + } else { + $this->edit($values, $errors); + } + } + + /** + * Confirm suppression + * + * @access public + */ + public function confirm() + { + $project = $this->getProject(); + $role = $this->getRole($project['id']); + + $this->response->html($this->helper->layout->project('project_role/remove', array( + 'project' => $project, + 'role' => $role, + ))); + } + + /** + * Remove a custom role + * + * @access public + */ + public function remove() + { + $project = $this->getProject(); + $this->checkCSRFParam(); + $role_id = $this->request->getIntegerParam('role_id'); + + if ($this->projectRoleModel->remove($project['id'], $role_id)) { + $this->flash->success(t('Custom project role removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this project role.')); + } + + $this->response->redirect($this->helper->url->to('ProjectRoleController', 'show', array('project_id' => $project['id']))); + } + + protected function getRole($project_id) + { + $role_id = $this->request->getIntegerParam('role_id'); + return $this->projectRoleModel->getById($project_id, $role_id); + } +} diff --git a/app/Controller/ProjectRoleRestrictionController.php b/app/Controller/ProjectRoleRestrictionController.php new file mode 100644 index 0000000..4fa9b13 --- /dev/null +++ b/app/Controller/ProjectRoleRestrictionController.php @@ -0,0 +1,96 @@ +getProject(); + $role_id = $this->request->getIntegerParam('role_id'); + $role = $this->projectRoleModel->getById($project['id'], $role_id); + + $this->response->html($this->template->render('project_role_restriction/create', array( + 'project' => $project, + 'role' => $role, + 'values' => $values + array('project_id' => $project['id'], 'role_id' => $role['role_id']), + 'errors' => $errors, + 'restrictions' => $this->projectRoleRestrictionModel->getRules(), + ))); + } + + /** + * Save new restriction + */ + public function save() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + + $restriction_id = $this->projectRoleRestrictionModel->create( + $project['id'], + $values['role_id'], + $values['rule'] + ); + + if ($restriction_id !== false) { + $this->flash->success(t('The project restriction has been created successfully.')); + } else { + $this->flash->failure(t('Unable to create this project restriction.')); + } + + $this->response->redirect($this->helper->url->to('ProjectRoleController', 'show', array('project_id' => $project['id']))); + } + + /** + * Confirm suppression + * + * @access public + */ + public function confirm() + { + $project = $this->getProject(); + $restriction_id = $this->request->getIntegerParam('restriction_id'); + + $this->response->html($this->helper->layout->project('project_role_restriction/remove', array( + 'project' => $project, + 'restriction' => $this->projectRoleRestrictionModel->getById($project['id'], $restriction_id), + 'restrictions' => $this->projectRoleRestrictionModel->getRules(), + ))); + } + + /** + * Remove a restriction + * + * @access public + */ + public function remove() + { + $project = $this->getProject(); + $this->checkCSRFParam(); + $restriction_id = $this->request->getIntegerParam('restriction_id'); + + if ($this->projectRoleRestrictionModel->remove($restriction_id)) { + $this->flash->success(t('Project restriction removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this restriction.')); + } + + $this->response->redirect($this->helper->url->to('ProjectRoleController', 'show', array('project_id' => $project['id']))); + } +} diff --git a/app/Controller/ProjectStatusController.php b/app/Controller/ProjectStatusController.php new file mode 100644 index 0000000..4fdbb6e --- /dev/null +++ b/app/Controller/ProjectStatusController.php @@ -0,0 +1,100 @@ +getProject(); + + $this->response->html($this->template->render('project_status/enable', array( + 'project' => $project, + ))); + } + + /** + * Enable the project + */ + public function enable() + { + $project = $this->getProject(); + $this->checkCSRFParam(); + + if ($this->projectModel->enable($project['id'])) { + $this->flash->success(t('Project activated successfully.')); + } else { + $this->flash->failure(t('Unable to activate this project.')); + } + + $this->response->redirect($this->helper->url->to('ProjectViewController', 'show', array('project_id' => $project['id'])), true); + } + + /** + * Disable a project (confirmation dialog box) + */ + public function confirmDisable() + { + $project = $this->getProject(); + + $this->response->html($this->template->render('project_status/disable', array( + 'project' => $project, + ))); + } + + /** + * Disable a project + */ + public function disable() + { + $project = $this->getProject(); + $this->checkCSRFParam(); + + if ($this->projectModel->disable($project['id'])) { + $this->flash->success(t('Project disabled successfully.')); + } else { + $this->flash->failure(t('Unable to disable this project.')); + } + + $this->response->redirect($this->helper->url->to('ProjectViewController', 'show', array('project_id' => $project['id'])), true); + } + + /** + * Remove a project (confirmation dialog box) + */ + public function confirmRemove() + { + $project = $this->getProject(); + + $this->response->html($this->template->render('project_status/remove', array( + 'project' => $project, + 'title' => t('Remove project') + ))); + } + + /** + * Remove a project + */ + public function remove() + { + $project = $this->getProject(); + $this->checkCSRFParam(); + + if ($this->projectModel->remove($project['id'])) { + $this->flash->success(t('Project removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this project.')); + } + + $this->response->redirect($this->helper->url->to('ProjectListController', 'show')); + } +} diff --git a/app/Controller/ProjectTagController.php b/app/Controller/ProjectTagController.php new file mode 100644 index 0000000..8907bcf --- /dev/null +++ b/app/Controller/ProjectTagController.php @@ -0,0 +1,182 @@ +getProject(); + + $this->response->html($this->helper->layout->project('project_tag/index', array( + 'project' => $project, + 'tags' => $this->tagModel->getAllByProject($project['id']), + 'colors' => $this->colorModel->getList(), + 'title' => t('Project tags management'), + ))); + } + + public function create(array $values = array(), array $errors = array()) + { + $project = $this->getProject(); + + $this->response->html($this->template->render('project_tag/create', array( + 'project' => $project, + 'values' => $values, + 'colors' => $this->colorModel->getList(), + 'errors' => $errors, + ))); + } + + public function save() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + $values['project_id'] = $project['id']; + + list($valid, $errors) = $this->tagValidator->validateCreation($values); + + if ($valid) { + if ($this->tagModel->create($project['id'], $values['name'], $values['color_id']) > 0) { + $this->flash->success(t('Tag created successfully.')); + } else { + $this->flash->failure(t('Unable to create this tag.')); + } + + $this->response->redirect($this->helper->url->to('ProjectTagController', 'index', array('project_id' => $project['id']))); + } else { + $this->create($values, $errors); + } + } + + public function edit(array $values = array(), array $errors = array()) + { + $project = $this->getProject(); + $tag = $this->getProjectTag($project); + + if (empty($values)) { + $values = $tag; + } + + $this->response->html($this->template->render('project_tag/edit', array( + 'project' => $project, + 'tag' => $tag, + 'values' => $values, + 'colors' => $this->colorModel->getList(), + 'errors' => $errors, + ))); + } + + public function update() + { + $project = $this->getProject(); + $tag = $this->getProjectTag($project); + $values = $this->request->getValues(); + $values['project_id'] = $project['id']; + $values['id'] = $tag['id']; + + list($valid, $errors) = $this->tagValidator->validateModification($values); + + if ($valid) { + if ($this->tagModel->update($values['id'], $values['name'], $values['color_id'])) { + $this->flash->success(t('Tag updated successfully.')); + } else { + $this->flash->failure(t('Unable to update this tag.')); + } + + $this->response->redirect($this->helper->url->to('ProjectTagController', 'index', array('project_id' => $project['id']))); + } else { + $this->edit($values, $errors); + } + } + + public function confirm() + { + $project = $this->getProject(); + $tag = $this->getProjectTag($project); + + $this->response->html($this->template->render('project_tag/remove', array( + 'tag' => $tag, + 'project' => $project, + ))); + } + + public function remove() + { + $this->checkCSRFParam(); + $project = $this->getProject(); + $tag = $this->getProjectTag($project); + + if ($this->tagModel->remove($tag['id'])) { + $this->flash->success(t('Tag removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this tag.')); + } + + $this->response->redirect($this->helper->url->to('ProjectTagController', 'index', array('project_id' => $project['id']))); + } + + /** + * Confirm dialog to make a tag global + * + * @return void + */ + public function confirmMakeGlobalTag() + { + $project = $this->getProject(); + $tag = $this->getProjectTag($project); + + $this->response->html($this->template->render('project_tag/make_global', array( + 'tag' => $tag, + 'project' => $project, + ))); + } + + /** + * Make a tag global and flash result + * + * @return void + */ + public function makeGlobalTag() + { + if ($this->userSession->isAdmin()) { + $project = $this->getProject(); + $tag = $this->getProjectTag($project); + + if ($this->tagModel->update($tag['id'], $tag['name'], $tag['color_id'], 0)) { + $this->flash->success(t('Tag updated successfully.')); + } else { + $this->flash->failure(t('Unable to update this tag.')); + } + + $this->response->redirect($this->helper->url->to('ProjectTagController', 'index', array('project_id' => $project['id']))); + } + } + + /** + * Update project tag settings + * + * @return void + */ + public function updateSettings() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + + $values['enable_global_tags'] = array_key_exists('enable_global_tags', $values) ? $values['enable_global_tags'] : 0; + + if ($this->projectModel->changeGlobalTagUsage($project['id'], $values['enable_global_tags'])) { + $this->flash->success(t('Project updated successfully.')); + return $this->response->redirect($this->helper->url->to('ProjectTagController', 'index', array('project_id' => $project['id']))); + } else { + $this->flash->failure(t('Unable to update this project.')); + } + + } +} diff --git a/app/Controller/ProjectUserOverviewController.php b/app/Controller/ProjectUserOverviewController.php new file mode 100644 index 0000000..5faf579 --- /dev/null +++ b/app/Controller/ProjectUserOverviewController.php @@ -0,0 +1,130 @@ +request->getIntegerParam('user_id', UserModel::EVERYBODY_ID); + + if ($this->userSession->isAdmin()) { + $project_ids = $this->projectModel->getAllIds(); + } else { + $project_ids = $this->projectPermissionModel->getActiveProjectIds($this->userSession->getId()); + } + + return array($user_id, $project_ids, $this->userModel->getActiveUsersList(true)); + } + + private function role($role, $action, $title, $title_user) + { + list($user_id, $project_ids, $users) = $this->common(); + + $query = $this->projectPermissionModel->getQueryByRole($project_ids, $role)->callback(array($this->projectModel, 'applyColumnStats')); + + if ($user_id !== UserModel::EVERYBODY_ID && isset($users[$user_id])) { + $query->eq(UserModel::TABLE.'.id', $user_id); + $title = t($title_user, $users[$user_id]); + } + + $paginator = $this->paginator + ->setUrl('ProjectUserOverviewController', $action, array('user_id' => $user_id)) + ->setMax(30) + ->setOrder('projects.name') + ->setQuery($query) + ->calculate(); + + $this->response->html($this->helper->layout->projectUser('project_user_overview/roles', array( + 'paginator' => $paginator, + 'title' => $title, + 'user_id' => $user_id, + 'users' => $users, + ))); + } + + private function tasks($is_active, $action, $title, $title_user) + { + list($user_id, $project_ids, $users) = $this->common(); + + $query = $this->taskFinderModel->getProjectUserOverviewQuery($project_ids, $is_active); + + if ($user_id !== UserModel::EVERYBODY_ID && isset($users[$user_id])) { + $query->eq(TaskModel::TABLE.'.owner_id', $user_id); + $title = t($title_user, $users[$user_id]); + } + + $paginator = $this->paginator + ->setUrl('ProjectUserOverviewController', $action, array('user_id' => $user_id)) + ->setMax(50) + ->setOrder(TaskModel::TABLE.'.id') + ->setQuery($query) + ->calculate(); + + $this->response->html($this->helper->layout->projectUser('project_user_overview/tasks', array( + 'paginator' => $paginator, + 'title' => $title, + 'user_id' => $user_id, + 'users' => $users, + ))); + } + + /** + * Display the list of project managers + * + */ + public function managers() + { + $this->role(Role::PROJECT_MANAGER, 'managers', t('People who are project managers'), 'Projects where "%s" is manager'); + } + + /** + * Display the list of project members + * + */ + public function members() + { + $this->role(Role::PROJECT_MEMBER, 'members', t('People who are project members'), 'Projects where "%s" is member'); + } + + /** + * Display the list of open taks + * + */ + public function opens() + { + $this->tasks(TaskModel::STATUS_OPEN, 'opens', t('Open tasks'), 'Open tasks assigned to "%s"'); + } + + /** + * Display the list of closed tasks + * + */ + public function closed() + { + $this->tasks(TaskModel::STATUS_CLOSED, 'closed', t('Closed tasks'), 'Closed tasks assigned to "%s"'); + } + + /** + * Users tooltip + */ + public function users() + { + $project = $this->getProject(); + + $this->response->html($this->template->render('project_user_overview/tooltip_users', array( + 'users' => $this->projectUserRoleModel->getAllUsersGroupedByRole($project['id']), + 'roles' => $this->projectRoleModel->getList($project['id']), + ))); + } +} diff --git a/app/Controller/ProjectViewController.php b/app/Controller/ProjectViewController.php new file mode 100644 index 0000000..b448398 --- /dev/null +++ b/app/Controller/ProjectViewController.php @@ -0,0 +1,228 @@ +getProject(); + $columns = $this->columnModel->getAllWithTaskCount($project['id']); + + $this->response->html($this->helper->layout->project('project_view/show', array( + 'project' => $project, + 'columns' => $columns, + 'title' => $project['name'], + ))); + } + + /** + * Public access management + * + * @access public + */ + public function share() + { + $project = $this->getProject(); + + $this->response->html($this->helper->layout->project('project_view/share', array( + 'project' => $project, + 'title' => t('Public access'), + ))); + } + + /** + * Change project sharing + * + * @throws \Kanboard\Core\Controller\AccessForbiddenException + * @throws \Kanboard\Core\Controller\PageNotFoundException + */ + public function updateSharing() + { + $project = $this->getProject(); + $this->checkCSRFParam(); + $switch = $this->request->getStringParam('switch'); + + if ($this->projectModel->{$switch.'PublicAccess'}($project['id'])) { + $this->flash->success(t('Project updated successfully.')); + } else { + $this->flash->failure(t('Unable to update this project.')); + } + + $this->response->redirect($this->helper->url->to('ProjectViewController', 'share', array('project_id' => $project['id']))); + } + + /** + * Integrations page + * + * @access public + */ + public function integrations() + { + $project = $this->getProject(); + + $this->response->html($this->helper->layout->project('project_view/integrations', array( + 'project' => $project, + 'title' => t('Integrations'), + 'webhook_token' => $this->configModel->get('webhook_token'), + 'values' => $this->projectMetadataModel->getAll($project['id']), + 'errors' => array(), + ))); + } + + /** + * Update integrations + * + * @throws \Kanboard\Core\Controller\PageNotFoundException + */ + public function updateIntegrations() + { + $project = $this->getProject(); + + $this->projectMetadataModel->save($project['id'], $this->request->getValues()); + $this->flash->success(t('Project updated successfully.')); + $this->response->redirect($this->helper->url->to('ProjectViewController', 'integrations', array('project_id' => $project['id']))); + } + + /** + * Display project notifications + * + * @access public + */ + public function notifications() + { + $project = $this->getProject(); + + $this->response->html($this->helper->layout->project('project_view/notifications', array( + 'notifications' => $this->projectNotificationModel->readSettings($project['id']), + 'types' => $this->projectNotificationTypeModel->getTypes(), + 'project' => $project, + 'title' => t('Notifications'), + ))); + } + + /** + * Update notifications + * + * @throws \Kanboard\Core\Controller\PageNotFoundException + */ + public function updateNotifications() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + + $this->projectNotificationModel->saveSettings($project['id'], $values); + $this->flash->success(t('Project updated successfully.')); + $this->response->redirect($this->helper->url->to('ProjectViewController', 'notifications', array('project_id' => $project['id']))); + } + + /** + * Duplicate a project + * + * @author Antonio Rabelo + * @author Michael Lüpkes + * @access public + */ + public function duplicate() + { + $project = $this->getProject(); + + $this->response->html($this->helper->layout->project('project_view/duplicate', array( + 'project' => $project, + 'title' => t('Clone this project') + ))); + } + + /** + * Do project duplication + */ + public function doDuplication() + { + $this->checkCSRFForm(); + + $project = $this->getProject(); + $values = $this->request->getRawFormValues(); + + $project_id = $this->projectDuplicationModel->duplicate($project['id'], array_keys($values), $this->userSession->getId()); + + if ($project_id !== false) { + $this->flash->success(t('Project cloned successfully.')); + } else { + $this->flash->failure(t('Unable to clone this project.')); + } + + $this->response->redirect($this->helper->url->to('ProjectViewController', 'show', array('project_id' => $project_id))); + } + + /** + * Import another project's tasks into the currently opened project. + * + * @return void + * @throws \Kanboard\Core\Controller\AccessForbiddenException + * @throws \Kanboard\Core\Controller\PageNotFoundException + */ + public function importTasks() + { + $project = $this->getProject(); + + // Fetch list of projects to copy tasks from. + // Remove current project from the list of the user's projects. + $otherProjects = array_filter( + $this->projectUserRoleModel->getActiveProjectsByUser($this->userSession->getId()), + static function ($projectId) use ($project) { + return (int) $project['id'] !== $projectId; + }, + ARRAY_FILTER_USE_KEY + ); + + $this->response->html($this->helper->layout->project('project_view/importTasks', array( + 'project' => $project, + 'title' => t('Import tasks from another project'), + 'projects' => $otherProjects, + 'values' => [], + 'errors' => [], + ))); + } + + /** + * Handle a form submission to copy tasks of a project. + * + * @return void + * @throws \Kanboard\Core\Controller\AccessForbiddenException + * @throws \Kanboard\Core\Controller\PageNotFoundException + */ + public function doTasksImport() + { + $this->checkCSRFForm(); + + $project = $this->getProject(); + $values = $this->request->getValues(); + $srcProjectId = isset($values['src_project_id']) ? (int) $values['src_project_id'] : 0; + + if (empty($srcProjectId) || !$this->projectPermissionModel->isUserAllowed($srcProjectId, $this->userSession->getId())) { + $this->response->redirect($this->helper->url->to('ProjectViewController', 'importTasks', array('project_id' => $project['id']))); + return; + } + + if ($this->projectTaskDuplicationModel->duplicate($srcProjectId, $project['id'])) { + $this->flash->success(t('Tasks copied successfully.')); + } else { + $this->flash->failure(t('Unable to copy tasks.')); + $this->response->redirect($this->helper->url->to('ProjectViewController', 'importTasks', array('project_id' => $project['id']))); + return; + } + + $this->response->redirect($this->helper->url->to('ProjectViewController', 'show', array('project_id' => $project['id']))); + } +} diff --git a/app/Controller/SearchController.php b/app/Controller/SearchController.php new file mode 100644 index 0000000..2cd6d5d --- /dev/null +++ b/app/Controller/SearchController.php @@ -0,0 +1,70 @@ +projectUserRoleModel->getActiveProjectsByUser($this->userSession->getId()); + $search = urldecode($this->request->getStringParam('search')); + $nb_tasks = 0; + + $paginator = $this->paginator + ->setUrl('SearchController', 'index', array('search' => $search)) + ->setMax(30) + ->setOrder(TaskModel::TABLE.'.id') + ->setDirection('DESC'); + + if ($search !== '' && ! empty($projects)) { + $paginator + ->setFormatter($this->taskListFormatter) + ->setQuery( + $this->taskLexer + ->build($search) + ->withFilter(new TaskProjectsFilter(array_keys($projects))) + ->getQuery() + ) + ->calculate(); + + $nb_tasks = $paginator->getTotal(); + } + + $this->response->html($this->helper->layout->app('search/index', array( + 'values' => array( + 'search' => $search, + 'controller' => 'SearchController', + 'action' => 'index', + ), + 'paginator' => $paginator, + 'title' => t('Search tasks').($nb_tasks > 0 ? ' ('.$nb_tasks.')' : '') + ))); + } + + public function activity() + { + $search = urldecode($this->request->getStringParam('search')); + $events = $this->helper->projectActivity->searchEvents($search); + $nb_events = count($events); + + $this->response->html($this->helper->layout->app('search/activity', array( + 'values' => array( + 'search' => $search, + 'controller' => 'SearchController', + 'action' => 'activity', + ), + 'title' => t('Search in activity stream').($nb_events > 0 ? ' ('.$nb_events.')' : ''), + 'nb_events' => $nb_events, + 'events' => $events, + ))); + } +} diff --git a/app/Controller/SubtaskController.php b/app/Controller/SubtaskController.php new file mode 100644 index 0000000..ffb4516 --- /dev/null +++ b/app/Controller/SubtaskController.php @@ -0,0 +1,222 @@ +getTask(); + + if (empty($values)) { + $values = $this->prepareValues($task); + } + + $this->response->html($this->template->render('subtask/create', array( + 'values' => $values, + 'errors' => $errors, + 'users_list' => $this->projectUserRoleModel->getAssignableUsersList($task['project_id']), + 'task' => $task, + ))); + } + + /** + * Prepare form values + * + * @access protected + * @param array $task + * @return array + */ + protected function prepareValues(array $task) + { + $values = array( + 'task_id' => $task['id'], + 'user_id' => $task['owner_id'], + 'another_subtask' => $this->request->getIntegerParam('another_subtask', 0) + ); + + $values = $this->hook->merge('controller:subtask:form:default', $values, array('default_values' => $values)); + return $values; + } + + /** + * Validation and creation + * + * @access public + */ + public function save() + { + $task = $this->getTask(); + $values = $this->request->getValues(); + $values['task_id'] = $task['id']; + $subtasks = explode("\r\n", isset($values['title']) ? $values['title'] : ''); + $subtasksAdded = 0; + + foreach ($subtasks as $subtask) { + $subtask = trim($subtask); + + if (! empty($subtask)) { + $subtaskValues = $values; + $subtaskValues['title'] = $subtask; + + list($valid, $errors) = $this->subtaskValidator->validateCreation($subtaskValues); + + if (! $valid) { + $this->create($values, $errors); + return false; + } + + if (! $this->subtaskModel->create($subtaskValues)) { + $this->flash->failure(t('Unable to create your sub-task.')); + $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id']), 'subtasks'), true); + return false; + } + + $subtasksAdded++; + } + } + + if (isset($values['another_subtask']) && $values['another_subtask'] == 1) { + return $this->create(array( + 'project_id' => $task['project_id'], + 'task_id' => $task['id'], + 'user_id' => $values['user_id'], + 'another_subtask' => 1, + 'subtasks_added' => $subtasksAdded, + )); + } elseif ($subtasksAdded > 0) { + if ($subtasksAdded === 1) { + $this->flash->success(t('Subtask added successfully.')); + } else { + $this->flash->success(t('%d subtasks added successfully.', $subtasksAdded)); + } + } + + $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id']), 'subtasks'), true); + } + + /** + * Edit form + * + * @access public + * @param array $values + * @param array $errors + * @throws AccessForbiddenException + * @throws PageNotFoundException + */ + public function edit(array $values = array(), array $errors = array()) + { + $task = $this->getTask(); + $subtask = $this->getSubtask($task); + + $this->response->html($this->template->render('subtask/edit', array( + 'values' => empty($values) ? $subtask : $values, + 'errors' => $errors, + 'users_list' => $this->projectUserRoleModel->getAssignableUsersList($task['project_id']), + 'status_list' => $this->subtaskModel->getStatusList(), + 'subtask' => $subtask, + 'task' => $task, + ))); + } + + /** + * Update and validate a subtask + * + * @access public + */ + public function update() + { + $task = $this->getTask(); + $subtask = $this->getSubtask($task); + + $values = $this->request->getValues(); + $values['id'] = $subtask['id']; + $values['task_id'] = $task['id']; + + list($valid, $errors) = $this->subtaskValidator->validateModification($values); + + if ($valid) { + if ($this->subtaskModel->update($values)) { + $this->flash->success(t('Sub-task updated successfully.')); + } else { + $this->flash->failure(t('Unable to update your sub-task.')); + } + + return $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'])), true); + } + + return $this->edit($values, $errors); + } + + /** + * Confirmation dialog before removing a subtask + * + * @access public + */ + public function confirm() + { + $task = $this->getTask(); + $subtask = $this->getSubtask($task); + + $this->response->html($this->template->render('subtask/remove', array( + 'subtask' => $subtask, + 'task' => $task, + ))); + } + + /** + * Remove a subtask + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $task = $this->getTask(); + $subtask = $this->getSubtask($task); + + if ($this->subtaskModel->remove($subtask['id'])) { + $this->flash->success(t('Sub-task removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this sub-task.')); + } + + $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'])), true); + } + + /** + * Move subtask position + * + * @access public + */ + public function movePosition() + { + $task = $this->getTask(); + $values = $this->request->getJson(); + + if (! empty($values) && $this->helper->user->hasProjectAccess('SubtaskController', 'movePosition', $task['project_id'])) { + $result = $this->subtaskPositionModel->changePosition($task['id'], $values['subtask_id'], $values['position']); + $this->response->json(array('result' => $result)); + } else { + throw new AccessForbiddenException(); + } + } +} diff --git a/app/Controller/SubtaskConverterController.php b/app/Controller/SubtaskConverterController.php new file mode 100644 index 0000000..acda221 --- /dev/null +++ b/app/Controller/SubtaskConverterController.php @@ -0,0 +1,45 @@ +getTask(); + $subtask = $this->getSubtask($task); + + $this->response->html($this->template->render('subtask_converter/show', array( + 'subtask' => $subtask, + 'task' => $task, + ))); + } + + public function save() + { + $task = $this->getTask(); + $subtask = $this->getSubtask($task); + + if (! $this->helper->projectRole->canCreateTaskInColumn($task['project_id'], $task['column_id'])) { + $this->flash->failure(t('You cannot create tasks in this column.')); + $this->response->redirect($this->helper->url->to('TaskViewController', 'show', ['task_id' => $task['id']])); + return; + } + + $task_id = $this->subtaskTaskConversionModel->convertToTask($task['project_id'], $subtask['id']); + + if ($task_id !== false) { + $this->flash->success(t('Subtask converted to task successfully.')); + } else { + $this->flash->failure(t('Unable to convert the subtask.')); + } + + $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task_id)), true); + } +} diff --git a/app/Controller/SubtaskRestrictionController.php b/app/Controller/SubtaskRestrictionController.php new file mode 100644 index 0000000..7e35439 --- /dev/null +++ b/app/Controller/SubtaskRestrictionController.php @@ -0,0 +1,64 @@ +getTask(); + $subtask = $this->getSubtask($task); + $subtaskInProgress = $this->subtaskStatusModel->getSubtaskInProgress($this->userSession->getId()); + + $this->response->html($this->template->render('subtask_restriction/show', array( + 'status_list' => array( + SubtaskModel::STATUS_TODO => 'Todo', + SubtaskModel::STATUS_DONE => 'Done', + ), + 'subtask_inprogress' => $subtaskInProgress, + 'subtask' => $subtask, + 'task' => $task, + ))); + } + + /** + * Change status of the in progress subtask and the other subtask + * + * @access public + */ + public function save() + { + $task = $this->getTask(); + $subtask = $this->getSubtask($task); + $values = $this->request->getValues(); + + if (! empty($values)) { + // Change status of the previous "in progress" subtask + $this->subtaskModel->update(array( + 'id' => $values['id'], + 'status' => $values['status'], + )); + + // Set the current subtask to "in progress" + $this->subtaskModel->update(array( + 'id' => $subtask['id'], + 'status' => SubtaskModel::STATUS_INPROGRESS, + )); + } + + $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'])), true); + } +} diff --git a/app/Controller/SubtaskStatusController.php b/app/Controller/SubtaskStatusController.php new file mode 100644 index 0000000..36a1b13 --- /dev/null +++ b/app/Controller/SubtaskStatusController.php @@ -0,0 +1,102 @@ + In Progress -> Done + * + * @access public + */ + public function change() + { + $this->checkReusableGETCSRFParam(); + $task = $this->getTask(); + $subtask = $this->getSubtask($task); + $fragment = $this->request->getStringParam('fragment'); + + $status = $this->subtaskStatusModel->toggleStatus($subtask['id']); + $subtask['status'] = $status; + + if ($fragment === 'table') { + $html = $this->renderTable($task); + } elseif ($fragment === 'rows') { + $html = $this->renderRows($task); + } else { + $html = $this->helper->subtask->renderToggleStatus($task, $subtask); + } + + $this->response->html($html); + } + + /** + * Start/stop timer for subtasks + * + * @access public + */ + public function timer() + { + $this->checkReusableGETCSRFParam(); + $task = $this->getTask(); + $subtask = $this->getSubtask($task); + $timer = $this->request->getStringParam('timer'); + + if ($timer === 'start') { + $this->subtaskTimeTrackingModel->logStartTime($subtask['id'], $this->userSession->getId()); + } elseif ($timer === 'stop') { + $this->subtaskTimeTrackingModel->logEndTime($subtask['id'], $this->userSession->getId()); + $this->subtaskTimeTrackingModel->updateTaskTimeTracking($task['id']); + } + + $this->response->html($this->template->render('subtask/timer', array( + 'task' => $task, + 'subtask' => $this->subtaskModel->getByIdWithDetails($subtask['id']), + ))); + } + + /** + * Render table + * + * @access protected + * @param array $task + * @return string + */ + protected function renderTable(array $task) + { + return $this->template->render('subtask/table', array( + 'task' => $task, + 'subtasks' => $this->subtaskModel->getAll($task['id']), + 'editable' => true, + )); + } + + /** + * Render task list rows + * + * @access protected + * @param array $task + * @return string + */ + protected function renderRows(array $task) + { + $userId = $this->request->getIntegerParam('user_id'); + + if ($userId > 0) { + $task['subtasks'] = $this->subtaskModel->getAllByTaskIdsAndAssignee(array($task['id']), $userId); + } else { + $task['subtasks'] = $this->subtaskModel->getAll($task['id']); + } + + return $this->template->render('task_list/task_subtasks', array( + 'task' => $task, + 'user_id' => $userId, + )); + } +} diff --git a/app/Controller/SwimlaneController.php b/app/Controller/SwimlaneController.php new file mode 100644 index 0000000..e0fc407 --- /dev/null +++ b/app/Controller/SwimlaneController.php @@ -0,0 +1,219 @@ +getProject(); + $swimlanes = $this->swimlaneModel->getAllWithTaskCount($project['id']); + + $this->response->html($this->helper->layout->project('swimlane/index', array( + 'active_swimlanes' => $swimlanes['active'], + 'inactive_swimlanes' => $swimlanes['inactive'], + 'project' => $project, + 'title' => t('Swimlanes') + ))); + } + + /** + * Create a new swimlane + * + * @access public + * @param array $values + * @param array $errors + * @throws \Kanboard\Core\Controller\PageNotFoundException + */ + public function create(array $values = array(), array $errors = array()) + { + $project = $this->getProject(); + + $this->response->html($this->template->render('swimlane/create', array( + 'values' => $values + array('project_id' => $project['id']), + 'errors' => $errors, + 'project' => $project, + ))); + } + + /** + * Validate and save a new swimlane + * + * @access public + */ + public function save() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + $values['project_id'] = $project['id']; + + list($valid, $errors) = $this->swimlaneValidator->validateCreation($values); + + if ($valid) { + if ($this->swimlaneModel->create($project['id'], $values['name'], $values['description'], $values['task_limit']) !== false) { + $this->flash->success(t('Your swimlane has been created successfully.')); + $this->response->redirect($this->helper->url->to('SwimlaneController', 'index', array('project_id' => $project['id'])), true); + return; + } else { + $errors = array('name' => array(t('Another swimlane with the same name exists in the project'))); + } + } + + $this->create($values, $errors); + } + + /** + * Edit a swimlane (display the form) + * + * @access public + * @param array $values + * @param array $errors + * @throws \Kanboard\Core\Controller\PageNotFoundException + */ + public function edit(array $values = array(), array $errors = array()) + { + $project = $this->getProject(); + $swimlane = $this->getSwimlane($project); + + $this->response->html($this->helper->layout->project('swimlane/edit', array( + 'values' => empty($values) ? $swimlane : $values, + 'errors' => $errors, + 'project' => $project, + ))); + } + + /** + * Edit a swimlane (validate the form and update the database) + * + * @access public + */ + public function update() + { + $project = $this->getProject(); + $swimlane = $this->getSwimlane($project); + $values = $this->request->getValues(); + $values['project_id'] = $project['id']; + $values['id'] = $swimlane['id']; + + list($valid, $errors) = $this->swimlaneValidator->validateModification($values); + + if ($valid) { + if ($this->swimlaneModel->update($values['id'], $values)) { + $this->flash->success(t('Swimlane updated successfully.')); + return $this->response->redirect($this->helper->url->to('SwimlaneController', 'index', array('project_id' => $project['id']))); + } else { + $errors = array('name' => array(t('Another swimlane with the same name exists in the project'))); + } + } + + return $this->edit($values, $errors); + } + + /** + * Confirmation dialog before removing a swimlane + * + * @access public + */ + public function confirm() + { + $project = $this->getProject(); + $swimlane = $this->getSwimlane($project); + + $this->response->html($this->helper->layout->project('swimlane/remove', array( + 'project' => $project, + 'swimlane' => $swimlane, + ))); + } + + /** + * Remove a swimlane + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $project = $this->getProject(); + $swimlane = $this->getSwimlane($project); + + if ($this->swimlaneModel->remove($project['id'], $swimlane['id'])) { + $this->flash->success(t('Swimlane removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this swimlane.')); + } + + $this->response->redirect($this->helper->url->to('SwimlaneController', 'index', array('project_id' => $project['id']))); + } + + /** + * Disable a swimlane + * + * @access public + */ + public function disable() + { + $this->checkCSRFParam(); + $project = $this->getProject(); + $swimlane = $this->getSwimlane($project); + + if ($this->swimlaneModel->disable($project['id'], $swimlane['id'])) { + $this->flash->success(t('Swimlane updated successfully.')); + } else { + $this->flash->failure(t('Unable to update this swimlane.')); + } + + $this->response->redirect($this->helper->url->to('SwimlaneController', 'index', array('project_id' => $project['id']))); + } + + /** + * Enable a swimlane + * + * @access public + */ + public function enable() + { + $this->checkCSRFParam(); + $project = $this->getProject(); + $swimlane = $this->getSwimlane($project); + + if ($this->swimlaneModel->enable($project['id'], $swimlane['id'])) { + $this->flash->success(t('Swimlane updated successfully.')); + } else { + $this->flash->failure(t('Unable to update this swimlane.')); + } + + $this->response->redirect($this->helper->url->to('SwimlaneController', 'index', array('project_id' => $project['id']))); + } + + /** + * Move swimlane position + * + * @access public + */ + public function move() + { + $this->checkReusableGETCSRFParam(); + $project = $this->getProject(); + $values = $this->request->getJson(); + + if (! empty($values) && isset($values['swimlane_id']) && isset($values['position'])) { + $result = $this->swimlaneModel->changePosition($project['id'], $values['swimlane_id'], $values['position']); + $this->response->json(array('result' => $result)); + } else { + throw new AccessForbiddenException(); + } + } +} diff --git a/app/Controller/TagController.php b/app/Controller/TagController.php new file mode 100644 index 0000000..6ad9ac8 --- /dev/null +++ b/app/Controller/TagController.php @@ -0,0 +1,124 @@ +response->html($this->helper->layout->config('tag/index', array( + 'tags' => $this->tagModel->getAllByProject(0), + 'colors' => $this->colorModel->getList(), + 'title' => t('Settings').' > '.t('Global tags management'), + ))); + } + + public function create(array $values = array(), array $errors = array()) + { + if (empty($values)) { + $values['project_id'] = 0; + } + + $this->response->html($this->template->render('tag/create', array( + 'values' => $values, + 'colors' => $this->colorModel->getList(), + 'errors' => $errors, + ))); + } + + public function save() + { + $values = $this->request->getValues(); + list($valid, $errors) = $this->tagValidator->validateCreation($values); + + if ($valid) { + if ($this->tagModel->create(0, $values['name'], $values['color_id']) > 0) { + $this->flash->success(t('Tag created successfully.')); + } else { + $this->flash->failure(t('Unable to create this tag.')); + } + + $this->response->redirect($this->helper->url->to('TagController', 'index')); + } else { + $this->create($values, $errors); + } + } + + public function edit(array $values = array(), array $errors = array()) + { + $tag_id = $this->request->getIntegerParam('tag_id'); + $tag = $this->tagModel->getById($tag_id); + + if (empty($values)) { + $values = $tag; + } + + $this->response->html($this->template->render('tag/edit', array( + 'tag' => $tag, + 'values' => $values, + 'colors' => $this->colorModel->getList(), + 'errors' => $errors, + ))); + } + + public function update() + { + $tag_id = $this->request->getIntegerParam('tag_id'); + $tag = $this->tagModel->getById($tag_id); + $values = $this->request->getValues(); + list($valid, $errors) = $this->tagValidator->validateModification($values); + + if ($tag['project_id'] != 0) { + throw new AccessForbiddenException(); + } + + if ($valid) { + if ($this->tagModel->update($values['id'], $values['name'], $values['color_id'])) { + $this->flash->success(t('Tag updated successfully.')); + } else { + $this->flash->failure(t('Unable to update this tag.')); + } + + $this->response->redirect($this->helper->url->to('TagController', 'index')); + } else { + $this->edit($values, $errors); + } + } + + public function confirm() + { + $tag_id = $this->request->getIntegerParam('tag_id'); + $tag = $this->tagModel->getById($tag_id); + + $this->response->html($this->template->render('tag/remove', array( + 'tag' => $tag, + ))); + } + + public function remove() + { + $this->checkCSRFParam(); + $tag_id = $this->request->getIntegerParam('tag_id'); + $tag = $this->tagModel->getById($tag_id); + + if ($tag['project_id'] != 0) { + throw new AccessForbiddenException(); + } + + if ($this->tagModel->remove($tag_id)) { + $this->flash->success(t('Tag removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this tag.')); + } + + $this->response->redirect($this->helper->url->to('TagController', 'index')); + } +} diff --git a/app/Controller/TaskAjaxController.php b/app/Controller/TaskAjaxController.php new file mode 100644 index 0000000..2c69e5c --- /dev/null +++ b/app/Controller/TaskAjaxController.php @@ -0,0 +1,87 @@ +request->getStringParam('term'); + $project_ids = $this->projectPermissionModel->getActiveProjectIds($this->userSession->getId()); + $exclude_task_ids = $this->request->getStringParam('exclude_task_ids'); + $exclude_task_ids = array_filter(array_map('trim', explode(',', $exclude_task_ids))); + + if (empty($project_ids)) { + $this->response->json(array()); + } else { + + $filter = $this->taskQuery->withFilter(new TaskProjectsFilter($project_ids)); + + if (! empty($exclude_task_ids)) { + $filter->withFilter(new TaskIdExclusionFilter($exclude_task_ids)); + } + + if (ctype_digit((string) $search)) { + $filter->withFilter(new TaskIdFilter($search)); + } else { + $filter->withFilter(new TaskTitleFilter($search)); + } + + $this->response->json($filter->format($this->taskAutoCompleteFormatter)); + } + } + + /** + * Task ID suggest menu + */ + public function suggest() + { + $taskId = $this->request->getIntegerParam('search'); + $projectIds = $this->projectPermissionModel->getActiveProjectIds($this->userSession->getId()); + + if (empty($projectIds)) { + $this->response->json(array()); + } else { + $filter = $this->taskQuery + ->withFilter(new TaskProjectsFilter($projectIds)) + ->withFilter(new TaskStatusFilter(TaskModel::STATUS_OPEN)) + ->withFilter(new TaskStartsWithIdFilter($taskId)); + + $this->response->json($filter->format($this->taskSuggestMenuFormatter)); + } + } + + /** + * Task edit preview + */ + public function preview() + { + $text = $this->request->getRawValue('text'); + + if (empty($text)) { + $this->response->json(array()); + } else { + $preview = $this->helper->text->markdown($text); + $this->response->json(array($preview)); + } + } + +} diff --git a/app/Controller/TaskBulkChangePropertyController.php b/app/Controller/TaskBulkChangePropertyController.php new file mode 100644 index 0000000..1d5b600 --- /dev/null +++ b/app/Controller/TaskBulkChangePropertyController.php @@ -0,0 +1,102 @@ +getProject(); + + if (empty($values)) { + $values['task_ids'] = $this->request->getStringParam('task_ids'); + } + + $this->response->html($this->template->render('task_bulk_change_property/show', [ + 'project' => $project, + 'values' => $values, + 'errors' => $errors, + 'users_list' => $this->projectUserRoleModel->getAssignableUsersList($project['id']), + 'categories_list' => $this->categoryModel->getList($project['id']), + 'internallink_list' => $this->linkModel->getList(), + ])); + } + + public function save() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + $taskIDs = explode(',', $values['task_ids']); + + foreach ($taskIDs as $taskID) { + if ($this->taskFinderModel->getProjectId($taskID) != $project['id']) { + throw new AccessForbiddenException(); + } + + $changes = []; + + if (isset($values['change_color']) && $values['change_color'] == 1) { + $changes['color_id'] = $values['color_id']; + } + + if (isset($values['change_assignee']) && $values['change_assignee'] == 1) { + $changes['owner_id'] = $values['owner_id']; + } + + if (isset($values['change_priority']) && $values['change_priority'] == 1) { + $changes['priority'] = $values['priority']; + } + + if (isset($values['change_category']) && $values['change_category'] == 1) { + $changes['category_id'] = $values['category_id']; + } + + if (isset($values['change_tags']) && $values['change_tags'] == 1) { + $changes['tags'] = $values['tags']; + if (isset($values['change_tags_only_add_new']) && $values['change_tags_only_add_new'] == 1) { + $changes['tags_only_add_new'] = $values['change_tags_only_add_new']; + } + } + + if (isset($values['change_due_date']) && $values['change_due_date'] == 1) { + $changes['date_due'] = $values['date_due']; + } + + if (isset($values['change_start_date']) && $values['change_start_date'] == 1) { + $changes['date_started'] = $values['date_started']; + } + + if (isset($values['change_estimated_time']) && $values['change_estimated_time'] == 1) { + $changes['time_estimated'] = $values['time_estimated']; + } + + if (isset($values['change_spent_time']) && $values['change_spent_time'] == 1) { + $changes['time_spent'] = $values['time_spent']; + } + + if (isset($values['change_score']) && $values['change_score'] == 1) { + $changes['score'] = $values['score']; + } + + if (isset($values['change_internallink']) && $values['change_internallink'] == 1) { + $this->taskLinkModel->create($taskID, $values['opposite_task_id'], $values['link_id']); + } + + if (isset($values['change_internallink_remove']) && $values['change_internallink_remove'] == 1) { + $task_link_ids = $this->taskLinkModel->getAll($taskID); + foreach ($task_link_ids as $task_link_id) { + $this->taskLinkModel->remove($task_link_id['id']); + } + } + + if (! empty($changes)) { + $changes['id'] = $taskID; + $this->taskModificationModel->update($changes); + } + } + + $this->response->redirect($this->helper->url->to('TaskListController', 'show', ['project_id' => $project['id']]), true); + } +} diff --git a/app/Controller/TaskBulkController.php b/app/Controller/TaskBulkController.php new file mode 100644 index 0000000..ebca3d4 --- /dev/null +++ b/app/Controller/TaskBulkController.php @@ -0,0 +1,108 @@ +getProject(); + + if (empty($values)) { + $values = array( + 'swimlane_id' => $this->request->getIntegerParam('swimlane_id'), + 'column_id' => $this->request->getIntegerParam('column_id'), + 'project_id' => $project['id'], + ); + } + + $this->response->html($this->template->render('task_bulk/show', array( + 'project' => $project, + 'values' => $values, + 'errors' => $errors, + 'users_list' => $this->projectUserRoleModel->getAssignableUsersList($project['id'], true, false, $project['is_private'] == 1), + 'colors_list' => $this->colorModel->getList(), + 'categories_list' => $this->categoryModel->getList($project['id']), + 'task_description_templates' => $this->predefinedTaskDescriptionModel->getList($project['id']), + ))); + } + + /** + * Save all tasks in the database + */ + public function save() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + list($valid, $errors) = $this->taskValidator->validateBulkCreation($values); + + if (! $valid) { + $this->show($values, $errors); + } elseif (! $this->helper->projectRole->canCreateTaskInColumn($project['id'], $values['column_id'])) { + $this->flash->failure(t('You cannot create tasks in this column.')); + $this->response->redirect($this->helper->url->to('BoardViewController', 'show', array('project_id' => $project['id'])), true); + } else { + $this->createTasks($project, $values); + $this->response->redirect($this->helper->url->to( + 'BoardViewController', + 'show', + array('project_id' => $project['id']), + 'swimlane-'. $values['swimlane_id'] + ), true); + } + } + + /** + * Create all tasks + * + * @param array $project + * @param array $values + */ + protected function createTasks(array $project, array $values) + { + $tasks = preg_split('/\r\n|[\r\n]/', $values['tasks']); + + foreach ($tasks as $title) { + $title = trim($title); + + if (! empty($title)) { + $this->taskCreationModel->create(array( + 'title' => $title, + 'column_id' => $values['column_id'], + 'swimlane_id' => $values['swimlane_id'], + 'category_id' => empty($values['category_id']) ? 0 : $values['category_id'], + 'owner_id' => empty($values['owner_id']) ? 0 : $values['owner_id'], + 'color_id' => $values['color_id'], + 'project_id' => $project['id'], + 'description' => $this->getTaskDescription($project, $values), + 'tags' => $values['tags'], + 'priority' => $values['priority'], + 'score' => $values['score'], + 'time_estimated' => $values['time_estimated'], + 'date_due' => $values['date_due'], + )); + } + } + } + + protected function getTaskDescription(array $project, array $values) + { + if (empty($values['task_description_template_id'])) { + return ''; + } + + return $this->predefinedTaskDescriptionModel->getDescriptionById($project['id'], $values['task_description_template_id']); + } +} diff --git a/app/Controller/TaskBulkMoveColumnController.php b/app/Controller/TaskBulkMoveColumnController.php new file mode 100644 index 0000000..f3607df --- /dev/null +++ b/app/Controller/TaskBulkMoveColumnController.php @@ -0,0 +1,44 @@ +getProject(); + + if (empty($values)) { + $values['task_ids'] = $this->request->getStringParam('task_ids'); + } + + $this->response->html($this->template->render('task_bulk_move_column/show', [ + 'project' => $project, + 'values' => $values, + 'errors' => $errors, + 'columns' => $this->columnModel->getList($project['id']), + 'swimlanes' => $this->swimlaneModel->getList($project['id'], false, true), + ])); + } + + public function save() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + $taskIDs = explode(',', $values['task_ids']); + + foreach ($taskIDs as $taskID) { + $task = $this->taskFinderModel->getById($taskID); + + if (! $this->helper->projectRole->canMoveTask($task['project_id'], $task['column_id'], $values['column_id'])) { + throw new AccessForbiddenException(e('You are not allowed to move this task.')); + } + + $this->taskPositionModel->moveBottom($project['id'], $taskID, $values['swimlane_id'], $values['column_id']); + } + + $this->response->redirect($this->helper->url->to('TaskListController', 'show', ['project_id' => $project['id']]), true); + } +} diff --git a/app/Controller/TaskCreationController.php b/app/Controller/TaskCreationController.php new file mode 100644 index 0000000..2892e73 --- /dev/null +++ b/app/Controller/TaskCreationController.php @@ -0,0 +1,187 @@ +getProject(); + $swimlanesList = $this->swimlaneModel->getList($project['id'], false, true); + $values += $this->prepareValues($project['is_private'], $swimlanesList); + + $values = $this->hook->merge('controller:task:form:default', $values, array('default_values' => $values)); + $values = $this->hook->merge('controller:task-creation:form:default', $values, array('default_values' => $values)); + + $this->response->html($this->template->render('task_creation/show', array( + 'project' => $project, + 'errors' => $errors, + 'values' => $values + array('project_id' => $project['id']), + 'columns_list' => $this->columnModel->getList($project['id']), + 'users_list' => $this->projectUserRoleModel->getAssignableUsersList($project['id'], true, false, $project['is_private'] == 1), + 'categories_list' => $this->categoryModel->getList($project['id']), + 'swimlanes_list' => $swimlanesList, + 'screenshot' => $screenshot, + 'files' => $files, + ))); + } + + /** + * Validate and save a new task + * + * @access public + */ + public function save() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + $values['project_id'] = $project['id']; + $files = $this->request->getFileInfo('files'); + + $screenshot = null; + if (array_key_exists('screenshot', $values)) { + $screenshot = $values['screenshot']; + unset($values['screenshot']); + } + + list($valid, $errors) = $this->taskValidator->validateCreation($values); + + if (! $valid) { + $this->flash->failure(t('Unable to create your task.')); + $this->show($values, $screenshot, $files, $errors); + } elseif (! $this->helper->projectRole->canCreateTaskInColumn($project['id'], $values['column_id'])) { + $this->flash->failure(t('You cannot create tasks in this column.')); + $this->response->redirect($this->helper->url->to('BoardViewController', 'show', array('project_id' => $project['id'])), true); + } else { + $task_id = $this->taskCreationModel->create($values); + if ($task_id === 0) { + $this->flash->failure(t('Unable to create this task.')); + $this->response->redirect($this->helper->url->to('BoardViewController', 'show', array('project_id' => $project['id'])), true); + return; + } + + if ($screenshot) { + $this->taskFileModel->uploadScreenshot($task_id, $screenshot); + } + + if (isset($files['name'][0]) && $files['name'][0] !== '') { + $filesUploaded = $this->taskFileModel->uploadFiles($task_id, $files); + if (! $filesUploaded) { + $this->flash->failure(t('Unable to upload files, check the permissions of your data folder.')); + $this->response->redirect($this->helper->url->to('BoardViewController', 'show', ['project_id' => $project['id']]), true); + return; + } + } + + $this->flash->success(t('Task created successfully.')); + $this->afterSave($project, $values, $task_id); + } + } + + /** + * Duplicate created tasks to multiple projects + * + * @throws PageNotFoundException + */ + public function duplicateProjects() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + + if (isset($values['project_ids'])) { + foreach ($values['project_ids'] as $project_id) { + if (! $this->projectPermissionModel->isUserAllowed($project_id, $this->userSession->getId())) { + throw new AccessForbiddenException(); + } + $this->taskProjectDuplicationModel->duplicateToProject($values['task_id'], $project_id); + } + } + + $this->response->redirect($this->helper->url->to('BoardViewController', 'show', array('project_id' => $project['id'])), true); + } + + /** + * Executed after the task is saved + * + * @param array $project + * @param array $values + * @param integer $task_id + */ + protected function afterSave(array $project, array &$values, $task_id) + { + if (isset($values['duplicate_multiple_projects']) && $values['duplicate_multiple_projects'] == 1) { + $this->chooseProjects($project, $task_id); + } elseif (isset($values['another_task']) && $values['another_task'] == 1) { + $this->show(array( + 'owner_id' => $values['owner_id'], + 'color_id' => $values['color_id'], + 'category_id' => isset($values['category_id']) ? $values['category_id'] : 0, + 'column_id' => $values['column_id'], + 'swimlane_id' => isset($values['swimlane_id']) ? $values['swimlane_id'] : 0, + 'another_task' => 1, + )); + } else { + $this->response->redirect($this->helper->url->to('BoardViewController', 'show', array('project_id' => $project['id'])), true); + } + } + + /** + * Prepare form values + * + * @access protected + * @param bool $isPrivateProject + * @param array $swimlanesList + * @return array + */ + protected function prepareValues($isPrivateProject, array $swimlanesList) + { + $values = array( + 'swimlane_id' => $this->request->getIntegerParam('swimlane_id', key($swimlanesList)), + 'column_id' => $this->request->getIntegerParam('column_id'), + 'color_id' => $this->colorModel->getDefaultColor(), + ); + + if ($isPrivateProject) { + $values['owner_id'] = $this->userSession->getId(); + } + + return $values; + } + + /** + * Choose projects + * + * @param array $project + * @param integer $task_id + */ + protected function chooseProjects(array $project, $task_id) + { + $task = $this->taskFinderModel->getById($task_id); + $projects = $this->projectUserRoleModel->getActiveProjectsByUser($this->userSession->getId()); + unset($projects[$project['id']]); + + $this->response->html($this->template->render('task_creation/duplicate_projects', array( + 'project' => $project, + 'task' => $task, + 'projects_list' => $projects, + 'values' => array('task_id' => $task['id']) + ))); + } +} diff --git a/app/Controller/TaskDuplicationController.php b/app/Controller/TaskDuplicationController.php new file mode 100644 index 0000000..ddf2ffb --- /dev/null +++ b/app/Controller/TaskDuplicationController.php @@ -0,0 +1,159 @@ +getTask(); + + if ($this->request->getStringParam('confirmation') === 'yes') { + $this->checkCSRFParam(); + $task_id = $this->taskDuplicationModel->duplicate($task['id']); + + if ($task_id > 0) { + $this->flash->success(t('Task created successfully.')); + return $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task_id))); + } else { + $this->flash->failure(t('Unable to create this task.')); + return $this->response->redirect($this->helper->url->to('TaskDuplicationController', 'duplicate', array('task_id' => $task['id'])), true); + } + } + + return $this->response->html($this->template->render('task_duplication/duplicate', array( + 'task' => $task, + ))); + } + + /** + * Move a task to another project + * + * @access public + */ + public function move() + { + $task = $this->getTask(); + + if ($this->request->isPost()) { + $values = $this->request->getValues(); + list($valid, ) = $this->taskValidator->validateProjectModification($values); + + if ($valid) { + if (! $this->projectPermissionModel->isUserAllowed($values['project_id'], $this->userSession->getId())) { + throw new AccessForbiddenException(); + } + + if ($this->taskProjectMoveModel->moveToProject( + $task['id'], + $values['project_id'], + $values['swimlane_id'], + $values['column_id'], + $values['category_id'], + $values['owner_id'] + )) { + $this->flash->success(t('Task updated successfully.')); + return $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id']))); + } + } + + $this->flash->failure(t('Unable to update your task.')); + } + + return $this->chooseDestination($task, 'task_duplication/move'); + } + + /** + * Duplicate a task to another project + * + * @access public + */ + public function copy() + { + $task = $this->getTask(); + + if ($this->request->isPost()) { + $values = $this->request->getValues(); + list($valid, ) = $this->taskValidator->validateProjectModification($values); + + if ($valid) { + if (! $this->projectPermissionModel->isUserAllowed($values['project_id'], $this->userSession->getId())) { + throw new AccessForbiddenException(); + } + + $task_id = $this->taskProjectDuplicationModel->duplicateToProject( + $task['id'], + $values['project_id'], + $values['swimlane_id'], + $values['column_id'], + $values['category_id'], + $values['owner_id'] + ); + + if ($task_id > 0) { + $this->flash->success(t('Task created successfully.')); + return $this->response->redirect($this->helper->url->to('BoardViewController', 'show', array('project_id' => $task['project_id'])), true); + } + } + + $this->flash->failure(t('Unable to create your task.')); + } + + return $this->chooseDestination($task, 'task_duplication/copy'); + } + + /** + * Choose destination when move/copy task to another project + * + * @access private + * @param array $task + * @param string $template + */ + private function chooseDestination(array $task, $template) + { + $values = array(); + $projects_list = $this->projectUserRoleModel->getActiveProjectsByUser($this->userSession->getId()); + + unset($projects_list[$task['project_id']]); + + if (! empty($projects_list)) { + $dst_project_id = $this->request->getIntegerParam('dst_project_id', key($projects_list)); + + $swimlanes_list = $this->swimlaneModel->getList($dst_project_id, false, true); + $columns_list = $this->columnModel->getList($dst_project_id); + $categories_list = $this->categoryModel->getList($dst_project_id); + $users_list = $this->projectUserRoleModel->getAssignableUsersList($dst_project_id); + + $values = $this->taskDuplicationModel->checkDestinationProjectValues($task); + $values['project_id'] = $dst_project_id; + } else { + $swimlanes_list = array(); + $columns_list = array(); + $categories_list = array(); + $users_list = array(); + } + + $this->response->html($this->template->render($template, array( + 'values' => $values, + 'task' => $task, + 'projects_list' => $projects_list, + 'swimlanes_list' => $swimlanes_list, + 'columns_list' => $columns_list, + 'categories_list' => $categories_list, + 'users_list' => $users_list, + ))); + } +} diff --git a/app/Controller/TaskExternalLinkController.php b/app/Controller/TaskExternalLinkController.php new file mode 100644 index 0000000..02cf472 --- /dev/null +++ b/app/Controller/TaskExternalLinkController.php @@ -0,0 +1,184 @@ +getTask(); + + $this->response->html($this->template->render('task_external_link/find', array( + 'values' => $values, + 'errors' => $errors, + 'task' => $task, + 'types' => $this->externalLinkManager->getTypes(), + ))); + } + + /** + * Second creation form + * + * @access public + */ + public function create() + { + $task = $this->getTask(); + $values = $this->request->getValues(); + + try { + $provider = $this->externalLinkManager->setUserInput($values)->find(); + $link = $provider->getLink(); + + $this->response->html($this->template->render('task_external_link/create', array( + 'values' => array( + 'title' => $link->getTitle(), + 'url' => $link->getUrl(), + 'link_type' => $provider->getType(), + ), + 'dependencies' => $provider->getDependencies(), + 'errors' => array(), + 'task' => $task, + ))); + + } catch (ExternalLinkProviderNotFound $e) { + $errors = array('text' => array(t('Unable to fetch link information.'))); + $this->find($values, $errors); + } + } + + /** + * Save link + * + * @access public + */ + public function save() + { + $task = $this->getTask(); + $values = $this->request->getValues(); + $values['task_id'] = $task['id']; + + list($valid, $errors) = $this->externalLinkValidator->validateCreation($values); + + if ($valid) { + if ($this->taskExternalLinkModel->create($values) !== false) { + $this->flash->success(t('Link added successfully.')); + } else { + $this->flash->success(t('Unable to create your link.')); + } + + $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'])), true); + } else { + $provider = $this->externalLinkManager->getProvider($values['link_type']); + $this->response->html($this->template->render('task_external_link/create', array( + 'values' => $values, + 'errors' => $errors, + 'dependencies' => $provider->getDependencies(), + 'task' => $task, + ))); + } + } + + /** + * Edit form + * + * @access public + * @param array $values + * @param array $errors + * @throws ExternalLinkProviderNotFound + * @throws PageNotFoundException + * @throws \Kanboard\Core\Controller\AccessForbiddenException + */ + public function edit(array $values = array(), array $errors = array()) + { + $task = $this->getTask(); + $link = $this->getExternalTaskLink($task); + $provider = $this->externalLinkManager->getProvider($link['link_type']); + + $this->response->html($this->template->render('task_external_link/edit', array( + 'values' => empty($values) ? $link : $values, + 'errors' => $errors, + 'task' => $task, + 'link' => $link, + 'dependencies' => $provider->getDependencies(), + ))); + } + + /** + * Update link + * + * @access public + */ + public function update() + { + $task = $this->getTask(); + $link = $this->getExternalTaskLink($task); + + $values = $this->request->getValues(); + $values['id'] = $link['id']; + $values['task_id'] = $link['task_id']; + + list($valid, $errors) = $this->externalLinkValidator->validateModification($values); + + if ($valid && $this->taskExternalLinkModel->update($values)) { + $this->flash->success(t('Link updated successfully.')); + return $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'])), true); + } + + return $this->edit($values, $errors); + } + + /** + * Confirmation dialog before removing a link + * + * @access public + */ + public function confirm() + { + $task = $this->getTask(); + $link = $this->getExternalTaskLink($task); + + $this->response->html($this->template->render('task_external_link/remove', array( + 'link' => $link, + 'task' => $task, + ))); + } + + /** + * Remove a link + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $task = $this->getTask(); + $link = $this->getExternalTaskLink($task); + + if ($this->taskExternalLinkModel->remove($link['id'])) { + $this->flash->success(t('Link removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this link.')); + } + + $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id']))); + } +} diff --git a/app/Controller/TaskFileController.php b/app/Controller/TaskFileController.php new file mode 100644 index 0000000..121731f --- /dev/null +++ b/app/Controller/TaskFileController.php @@ -0,0 +1,108 @@ +getTask(); + + if ($this->request->isPost() && $this->taskFileModel->uploadScreenshot($task['id'], $this->request->getValue('screenshot')) !== false) { + $this->flash->success(t('Screenshot uploaded successfully.')); + return $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'])), true); + } + + return $this->response->html($this->template->render('task_file/screenshot', array( + 'task' => $task, + ))); + } + + /** + * File upload form + * + * @access public + */ + public function create() + { + $task = $this->getTask(); + + $this->response->html($this->template->render('task_file/create', array( + 'task' => $task, + 'max_size' => get_upload_max_size(), + ))); + } + + /** + * File upload (save files) + * + * @access public + */ + public function save() + { + $this->checkReusableCSRFParam(); + $task = $this->getTask(); + $result = $this->taskFileModel->uploadFiles($task['id'], $this->request->getFileInfo('files')); + + if ($this->request->isAjax()) { + if (! $result) { + $this->response->json(array('message' => t('Unable to upload files, check the permissions of your data folder.')), 500); + } else { + $this->response->json(array('message' => 'OK')); + } + } else { + if (! $result) { + $this->flash->failure(t('Unable to upload files, check the permissions of your data folder.')); + } + + $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'])), true); + } + } + + /** + * Remove a file + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $task = $this->getTask(); + $file = $this->getFile(); + + if ($file['task_id'] == $task['id'] && $this->taskFileModel->remove($file['id'])) { + $this->flash->success(t('File removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this file.')); + } + + $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id']))); + } + + /** + * Confirmation dialog before removing a file + * + * @access public + */ + public function confirm() + { + $task = $this->getTask(); + $file = $this->getFile(); + + $this->response->html($this->template->render('task_file/remove', array( + 'task' => $task, + 'file' => $file, + ))); + } +} diff --git a/app/Controller/TaskImportController.php b/app/Controller/TaskImportController.php new file mode 100644 index 0000000..6f36f5f --- /dev/null +++ b/app/Controller/TaskImportController.php @@ -0,0 +1,76 @@ +getProject(); + + $this->response->html($this->template->render('task_import/show', array( + 'project' => $project, + 'values' => $values, + 'errors' => $errors, + 'max_size' => get_upload_max_size(), + 'delimiters' => Csv::getDelimiters(), + 'enclosures' => Csv::getEnclosures(), + ))); + } + + /** + * Process CSV file + */ + public function save() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + $filename = $this->request->getFilePath('file'); + + if (! file_exists($filename)) { + $this->show($values, array('file' => array(t('Unable to read your file')))); + } else { + $taskImport = new TaskImport($this->container); + $taskImport->setProjectId($project['id']); + + $csv = new Csv($values['delimiter'], $values['enclosure']); + $csv->setColumnMapping($taskImport->getColumnMapping()); + $csv->read($filename, array($taskImport, 'importTask')); + + if ($taskImport->getNumberOfImportedTasks() > 0) { + $this->flash->success(t('%d task(s) have been imported successfully.', $taskImport->getNumberOfImportedTasks())); + } else { + $this->flash->failure(t('Nothing has been imported!')); + } + + $this->response->redirect($this->helper->url->to('TaskImportController', 'show', array('project_id' => $project['id'])), true); + } + } + + /** + * Generate template + * + */ + public function template() + { + $taskImport = new TaskImport($this->container); + $this->response->withFileDownload('tasks.csv'); + $this->response->csv(array($taskImport->getColumnMapping())); + } +} diff --git a/app/Controller/TaskInternalLinkController.php b/app/Controller/TaskInternalLinkController.php new file mode 100644 index 0000000..f385307 --- /dev/null +++ b/app/Controller/TaskInternalLinkController.php @@ -0,0 +1,183 @@ +getTask(); + + if (empty($values)) { + $values['another_tasklink'] = $this->request->getIntegerParam('another_tasklink', 0); + $values = $this->hook->merge('controller:tasklink:form:default', $values, array('default_values' => $values)); + } + + $this->response->html($this->template->render('task_internal_link/create', array( + 'values' => $values, + 'errors' => $errors, + 'task' => $task, + 'labels' => $this->linkModel->getList(0, false), + ))); + } + + /** + * Validation and creation + * + * @access public + */ + public function save() + { + $task = $this->getTask(); + $values = $this->request->getValues(); + $values['task_id'] = $task['id']; + + list($valid, $errors) = $this->taskLinkValidator->validateCreation($values); + + if ($valid) { + $opposite_task = $this->taskFinderModel->getById($values['opposite_task_id']); + + if (! $this->projectPermissionModel->isUserAllowed($opposite_task['project_id'], $this->userSession->getId())) { + throw new AccessForbiddenException(); + } + + if ($this->taskLinkModel->create($values['task_id'], $values['opposite_task_id'], $values['link_id']) !== false) { + $this->flash->success(t('Link added successfully.')); + + if (isset($values['another_tasklink']) && $values['another_tasklink'] == 1) { + return $this->create(array( + 'project_id' => $task['project_id'], + 'task_id' => $task['id'], + 'link_id' => $values['link_id'], + 'another_tasklink' => 1 + )); + } + + return $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'])), true); + } + + $errors = array('title' => array(t('The exact same link already exists'))); + $this->flash->failure(t('Unable to create your link.')); + } + + return $this->create($values, $errors); + } + + /** + * Edit form + * + * @access public + * @param array $values + * @param array $errors + * @throws PageNotFoundException + * @throws \Kanboard\Core\Controller\AccessForbiddenException + */ + public function edit(array $values = array(), array $errors = array()) + { + $task = $this->getTask(); + $task_link = $this->getInternalTaskLink($task); + + if (empty($values)) { + $opposite_task = $this->taskFinderModel->getById($task_link['opposite_task_id']); + $values = $task_link; + $values['title'] = '#'.$opposite_task['id'].' - '.$opposite_task['title']; + } + + $this->response->html($this->template->render('task_internal_link/edit', array( + 'values' => $values, + 'errors' => $errors, + 'task_link' => $task_link, + 'task' => $task, + 'labels' => $this->linkModel->getList(0, false) + ))); + } + + /** + * Validation and update + * + * @access public + */ + public function update() + { + $task = $this->getTask(); + $task_link = $this->getInternalTaskLink($task); + + $values = $this->request->getValues(); + $values['task_id'] = $task['id']; + $values['id'] = $task_link['id']; + + list($valid, $errors) = $this->taskLinkValidator->validateModification($values); + + if ($valid) { + $opposite_task = $this->taskFinderModel->getById($values['opposite_task_id']); + + if (! $this->projectPermissionModel->isUserAllowed($opposite_task['project_id'], $this->userSession->getId())) { + throw new AccessForbiddenException(); + } + + if ($this->taskLinkModel->update($values['id'], $values['task_id'], $values['opposite_task_id'], $values['link_id'])) { + $this->flash->success(t('Link updated successfully.')); + return $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'])).'#links'); + } + + $this->flash->failure(t('Unable to update your link.')); + } + + return $this->edit($values, $errors); + } + + /** + * Confirmation dialog before removing a link + * + * @access public + */ + public function confirm() + { + $task = $this->getTask(); + $link = $this->getInternalTaskLink($task); + + $this->response->html($this->template->render('task_internal_link/remove', array( + 'link' => $link, + 'task' => $task, + ))); + } + + /** + * Remove a link + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $task = $this->getTask(); + $link = $this->getInternalTaskLink($task); + + if ($this->taskLinkModel->remove($link['id'])) { + $this->flash->success(t('Link removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this link.')); + } + + $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id']))); + } +} diff --git a/app/Controller/TaskListController.php b/app/Controller/TaskListController.php new file mode 100644 index 0000000..48e0199 --- /dev/null +++ b/app/Controller/TaskListController.php @@ -0,0 +1,71 @@ +getProject(); + $search = $this->helper->projectHeader->getSearchQuery($project); + + if ($this->request->getIntegerParam('show_subtasks') !== 0 || + $this->request->getIntegerParam('hide_subtasks') !== 0 || + $this->request->getStringParam('direction') !== '' || + $this->request->getStringParam('order') !== '') { + $this->checkReusableGETCSRFParam(); + } + + if ($this->request->getIntegerParam('show_subtasks')) { + session_set('subtaskListToggle', true); + } elseif ($this->request->getIntegerParam('hide_subtasks')) { + session_set('subtaskListToggle', false); + } + + if ($this->userSession->hasSubtaskListActivated()) { + $formatter = $this->taskListSubtaskFormatter; + } else { + $formatter = $this->taskListFormatter; + } + + list($order, $direction) = $this->userSession->getListOrder($project['id']); + $direction = $this->request->getStringParam('direction', $direction); + $order = $this->request->getStringParam('order', $order); + $this->userSession->setListOrder($project['id'], $order, $direction); + + $paginator = $this->paginator + ->setUrl('TaskListController', 'show', array('project_id' => $project['id'], 'csrf_token' => $this->token->getReusableCSRFToken())) + ->setMax(30) + ->setOrder($order) + ->setDirection($direction) + ->setFormatter($formatter) + ->setQuery( + $this->taskLexer + ->build($search) + ->withFilter(new TaskProjectFilter($project['id'])) + ->getQuery() + ) + ->calculate(); + + $this->response->html($this->helper->layout->app('task_list/listing', array( + 'project' => $project, + 'title' => $project['name'], + 'description' => $this->helper->projectHeader->getDescription($project), + 'paginator' => $paginator, + ))); + } +} diff --git a/app/Controller/TaskMailController.php b/app/Controller/TaskMailController.php new file mode 100644 index 0000000..5197f4f --- /dev/null +++ b/app/Controller/TaskMailController.php @@ -0,0 +1,62 @@ +getTask(); + + $this->response->html($this->helper->layout->task('task_mail/create', array( + 'values' => $values, + 'errors' => $errors, + 'task' => $task, + 'members' => $this->projectPermissionModel->getMembersWithEmail($task['project_id']), + ))); + } + + public function send() + { + $task = $this->getTask(); + $values = $this->request->getValues(); + + list($valid, $errors) = $this->taskValidator->validateEmailCreation($values); + + if ($valid) { + $this->sendByEmail($values, $task); + $this->flash->success(t('Task sent by email successfully.')); + + $this->commentModel->create(array( + 'comment' => t('This task was sent by email to "%s" with subject "%s".', $values['emails'], $values['subject']), + 'user_id' => $this->userSession->getId(), + 'task_id' => $task['id'], + )); + + $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id']), 'comments'), true); + } else { + $this->create($values, $errors); + } + } + + protected function sendByEmail(array $values, array $task) + { + $emails = explode_csv_field($values['emails']); + $html = $this->template->render('task_mail/email', array('task' => $task)); + + foreach ($emails as $email) { + $this->emailClient->send( + $email, + $email, + $values['subject'], + $html + ); + } + } +} diff --git a/app/Controller/TaskModificationController.php b/app/Controller/TaskModificationController.php new file mode 100644 index 0000000..3e6f5d1 --- /dev/null +++ b/app/Controller/TaskModificationController.php @@ -0,0 +1,182 @@ +checkReusableGETCSRFParam(); + $task = $this->getTask(); + $values = ['id' => $task['id'], 'owner_id' => $this->userSession->getId()]; + + if (! $this->helper->projectRole->canUpdateTask($task)) { + throw new AccessForbiddenException(t('You are not allowed to update tasks assigned to someone else.')); + } + + if (! $this->helper->projectRole->canChangeAssignee($task)) { + throw new AccessForbiddenException(t('You are not allowed to change the assignee.')); + } + + $this->taskModificationModel->update($values); + $this->redirectAfterQuickAction($task); + } + + /** + * Set the start date automatically + * + * @access public + */ + public function start() + { + $this->checkReusableGETCSRFParam(); + $task = $this->getTask(); + $values = ['id' => $task['id'], 'date_started' => time()]; + + if (! $this->helper->projectRole->canUpdateTask($task)) { + throw new AccessForbiddenException(t('You are not allowed to update tasks assigned to someone else.')); + } + + $this->taskModificationModel->update($values); + $this->redirectAfterQuickAction($task); + } + + protected function redirectAfterQuickAction(array $task) + { + switch ($this->request->getStringParam('redirect')) { + case 'board': + $this->response->redirect($this->helper->url->to('BoardViewController', 'show', ['project_id' => $task['project_id']])); + break; + case 'list': + $this->response->redirect($this->helper->url->to('TaskListController', 'show', ['project_id' => $task['project_id']])); + break; + case 'dashboard': + $this->response->redirect($this->helper->url->to('DashboardController', 'show', [], 'project-tasks-'.$task['project_id'])); + break; + case 'dashboard-tasks': + $this->response->redirect($this->helper->url->to('DashboardController', 'tasks', ['user_id' => $this->userSession->getId()])); + break; + default: + $this->response->redirect($this->helper->url->to('TaskViewController', 'show', ['task_id' => $task['id']])); + } + } + + /** + * Display a form to edit a task + * + * @access public + * @param array $values + * @param array $errors + * @throws \Kanboard\Core\Controller\AccessForbiddenException + * @throws \Kanboard\Core\Controller\PageNotFoundException + */ + public function edit(array $values = array(), array $errors = array()) + { + $task = $this->getTask(); + + if (! $this->helper->projectRole->canUpdateTask($task)) { + throw new AccessForbiddenException(t('You are not allowed to update tasks assigned to someone else.')); + } + + $project = $this->projectModel->getById($task['project_id']); + + if (empty($values)) { + $values = $task; + } + + $values = $this->hook->merge('controller:task:form:default', $values, array('default_values' => $values)); + $values = $this->hook->merge('controller:task-modification:form:default', $values, array('default_values' => $values)); + + $params = array( + 'project' => $project, + 'values' => $values, + 'errors' => $errors, + 'task' => $task, + 'tags' => $this->taskTagModel->getList($task['id']), + 'users_list' => $this->projectUserRoleModel->getAssignableUsersList($task['project_id']), + 'categories_list' => $this->categoryModel->getList($task['project_id']), + ); + + $this->renderTemplate($task, $params); + } + + protected function renderTemplate(array &$task, array &$params) + { + if (empty($task['external_uri'])) { + $this->response->html($this->template->render('task_modification/show', $params)); + } else { + + try { + $taskProvider = $this->externalTaskManager->getProvider($task['external_provider']); + $params['template'] = $taskProvider->getModificationFormTemplate(); + $params['external_task'] = $taskProvider->fetch($task['external_uri'], $task['project_id']); + } catch (ExternalTaskAccessForbiddenException $e) { + throw new AccessForbiddenException($e->getMessage()); + } catch (ExternalTaskException $e) { + $params['error_message'] = $e->getMessage(); + } + + $this->response->html($this->template->render('external_task_modification/show', $params)); + } + } + + /** + * Validate and update a task + * + * @access public + */ + public function update() + { + $task = $this->getTask(); + $values = $this->request->getValues(); + $values['id'] = $task['id']; + $values['project_id'] = $task['project_id']; + + list($valid, $errors) = $this->taskValidator->validateModification($values); + + if ($valid && $this->updateTask($task, $values, $errors)) { + $this->flash->success(t('Task updated successfully.')); + $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'])), true); + } else { + $this->flash->failure(t('Unable to update your task.')); + $this->edit($values, $errors); + } + } + + protected function updateTask(array &$task, array &$values, array &$errors) + { + if (isset($values['owner_id']) && $values['owner_id'] != $task['owner_id'] && !$this->helper->projectRole->canChangeAssignee($task)) { + throw new AccessForbiddenException(t('You are not allowed to change the assignee.')); + } + + if (! $this->helper->projectRole->canUpdateTask($task)) { + throw new AccessForbiddenException(t('You are not allowed to update tasks assigned to someone else.')); + } + + $result = $this->taskModificationModel->update($values); + + if ($result && ! empty($task['external_uri'])) { + try { + $taskProvider = $this->externalTaskManager->getProvider($task['external_provider']); + $result = $taskProvider->save($task['external_uri'], $values, $errors); + } catch (ExternalTaskAccessForbiddenException $e) { + throw new AccessForbiddenException($e->getMessage()); + } catch (ExternalTaskException $e) { + $this->logger->error($e->getMessage()); + $result = false; + } + } + + return $result; + } +} diff --git a/app/Controller/TaskMovePositionController.php b/app/Controller/TaskMovePositionController.php new file mode 100644 index 0000000..5303a54 --- /dev/null +++ b/app/Controller/TaskMovePositionController.php @@ -0,0 +1,53 @@ +getTask(); + + $this->response->html($this->template->render('task_move_position/show', array( + 'task' => $task, + 'board' => $this->boardFormatter + ->withProjectId($task['project_id']) + ->withQuery( + $this->taskFinderModel->getExtendedQuery() + ->eq(TaskModel::TABLE.'.is_active', TaskModel::STATUS_OPEN) + ->neq(TaskModel::TABLE.'.id', $task['id']) + ) + ->format() + ))); + } + + public function save() + { + $this->checkReusableGETCSRFParam(); + $task = $this->getTask(); + $values = $this->request->getJson(); + + if (! $this->helper->projectRole->canMoveTask($task['project_id'], $task['column_id'], $values['column_id'])) { + throw new AccessForbiddenException(e('You are not allowed to move this task.')); + } + + $this->taskPositionModel->movePosition( + $task['project_id'], + $task['id'], + $values['column_id'], + $values['position'], + $values['swimlane_id'] + ); + + $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id']))); + } +} diff --git a/app/Controller/TaskPopoverController.php b/app/Controller/TaskPopoverController.php new file mode 100644 index 0000000..4e3c11a --- /dev/null +++ b/app/Controller/TaskPopoverController.php @@ -0,0 +1,26 @@ +getTask(); + + $this->response->html($this->template->render('task_file/screenshot', array( + 'task' => $task, + ))); + } +} diff --git a/app/Controller/TaskRecurrenceController.php b/app/Controller/TaskRecurrenceController.php new file mode 100644 index 0000000..cd23ea3 --- /dev/null +++ b/app/Controller/TaskRecurrenceController.php @@ -0,0 +1,66 @@ +getTask(); + + if (empty($values)) { + $values = $task; + } + + $this->response->html($this->template->render('task_recurrence/edit', array( + 'values' => $values, + 'errors' => $errors, + 'task' => $task, + 'recurrence_status_list' => $this->taskRecurrenceModel->getRecurrenceStatusList(), + 'recurrence_trigger_list' => $this->taskRecurrenceModel->getRecurrenceTriggerList(), + 'recurrence_timeframe_list' => $this->taskRecurrenceModel->getRecurrenceTimeframeList(), + 'recurrence_basedate_list' => $this->taskRecurrenceModel->getRecurrenceBasedateList(), + ))); + } + + /** + * Update recurrence form + * + * @access public + */ + public function update() + { + $task = $this->getTask(); + $values = $this->request->getValues(); + $values['id'] = $task['id']; + + list($valid, $errors) = $this->taskValidator->validateEditRecurrence($values); + + if ($valid) { + if ($this->taskModificationModel->update($values)) { + $this->flash->success(t('Task updated successfully.')); + } else { + $this->flash->failure(t('Unable to update your task.')); + } + + return $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'])), true); + } + + return $this->edit($values, $errors); + } +} diff --git a/app/Controller/TaskReorderController.php b/app/Controller/TaskReorderController.php new file mode 100644 index 0000000..1b258e6 --- /dev/null +++ b/app/Controller/TaskReorderController.php @@ -0,0 +1,43 @@ +checkCSRFParam(); + $project = $this->getProject(); + + if (! $this->helper->user->hasProjectAccess('TaskModificationController', 'update', $project['id'])) { + throw new AccessForbiddenException(); + } + + $swimlaneID = $this->request->getIntegerParam('swimlane_id'); + $columnID = $this->request->getIntegerParam('column_id'); + $direction = $this->request->getStringParam('direction'); + $sort = $this->request->getStringParam('sort'); + + switch ($sort) { + case 'id': + $this->taskReorderModel->reorderByTaskId($project['id'], $swimlaneID, $columnID, $direction); + break; + case 'priority': + $this->taskReorderModel->reorderByPriority($project['id'], $swimlaneID, $columnID, $direction); + break; + case 'assignee-priority': + $this->taskReorderModel->reorderByAssigneeAndPriority($project['id'], $swimlaneID, $columnID, $direction); + break; + case 'assignee': + $this->taskReorderModel->reorderByAssignee($project['id'], $swimlaneID, $columnID, $direction); + break; + case 'due-date': + $this->taskReorderModel->reorderByDueDate($project['id'], $swimlaneID, $columnID, $direction); + break; + } + + $this->response->redirect($this->helper->url->to('BoardViewController', 'show', ['project_id' => $project['id']])); + } +} diff --git a/app/Controller/TaskStatusController.php b/app/Controller/TaskStatusController.php new file mode 100644 index 0000000..5dbd07e --- /dev/null +++ b/app/Controller/TaskStatusController.php @@ -0,0 +1,62 @@ +changeStatus('close', 'task_status/close', t('Task closed successfully.'), t('Unable to close this task.')); + } + + /** + * Open a task + * + * @access public + */ + public function open() + { + $this->changeStatus('open', 'task_status/open', t('Task opened successfully.'), t('Unable to open this task.')); + } + + /** + * Common method to change status + * + * @access private + * @param string $method + * @param string $template + * @param string $success_message + * @param string $failure_message + */ + private function changeStatus($method, $template, $success_message, $failure_message) + { + $task = $this->getTask(); + + if ($this->request->getStringParam('confirmation') === 'yes') { + $this->checkCSRFParam(); + + if ($this->taskStatusModel->$method($task['id'])) { + $this->flash->success($success_message); + } else { + $this->flash->failure($failure_message); + } + + $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'])), true); + } else { + $this->response->html($this->template->render($template, array( + 'task' => $task, + ))); + } + } +} diff --git a/app/Controller/TaskSuppressionController.php b/app/Controller/TaskSuppressionController.php new file mode 100644 index 0000000..019bd97 --- /dev/null +++ b/app/Controller/TaskSuppressionController.php @@ -0,0 +1,53 @@ +getTask(); + + if (! $this->helper->projectRole->canRemoveTask($task)) { + throw new AccessForbiddenException(); + } + + $this->response->html($this->template->render('task_suppression/remove', array( + 'task' => $task, + 'redirect' => $this->request->getStringParam('redirect'), + ))); + } + + /** + * Remove a task + */ + public function remove() + { + $task = $this->getTask(); + $this->checkCSRFParam(); + + if (! $this->helper->projectRole->canRemoveTask($task)) { + throw new AccessForbiddenException(); + } + + if ($this->taskModel->remove($task['id'])) { + $this->flash->success(t('Task removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this task.')); + } + + $redirect = $this->request->getStringParam('redirect') === ''; + $this->response->redirect($this->helper->url->to('BoardViewController', 'show', array('project_id' => $task['project_id'])), $redirect); + } +} diff --git a/app/Controller/TaskViewController.php b/app/Controller/TaskViewController.php new file mode 100644 index 0000000..c7ab6ed --- /dev/null +++ b/app/Controller/TaskViewController.php @@ -0,0 +1,144 @@ +projectModel->getByToken($this->request->getStringParam('token')); + + if (empty($project)) { + throw AccessForbiddenException::getInstance()->withoutLayout(); + } + + $task = $this->taskFinderModel->getDetails($this->request->getIntegerParam('task_id')); + + if (empty($task)) { + throw PageNotFoundException::getInstance()->withoutLayout(); + } + + if ($task['project_id'] != $project['id']) { + throw AccessForbiddenException::getInstance()->withoutLayout(); + } + + $this->response->html($this->helper->layout->app('task/public', array( + 'project' => $project, + 'comments' => array_filter($this->commentModel->getAll($task['id']), function ($comment) { + return $comment['visibility'] === Role::APP_USER; + }), + 'subtasks' => $this->subtaskModel->getAll($task['id']), + 'links' => $this->taskLinkModel->getAllGroupedByLabel($task['id']), + 'task' => $task, + 'columns_list' => $this->columnModel->getList($task['project_id']), + 'colors_list' => $this->colorModel->getList(), + 'tags' => $this->taskTagModel->getTagsByTask($task['id']), + 'title' => $task['title'], + 'no_layout' => true, + 'auto_refresh' => true, + 'not_editable' => true, + ))); + } + + /** + * Show a task + * + * @access public + */ + public function show() + { + $task = $this->getTask(); + $subtasks = $this->subtaskModel->getAll($task['id']); + $commentSortingDirection = $this->userMetadataCacheDecorator->get(UserMetadataModel::KEY_COMMENT_SORTING_DIRECTION, 'ASC'); + + $this->response->html($this->helper->layout->task('task/show', array( + 'task' => $task, + 'project' => $this->projectModel->getById($task['project_id']), + 'files' => $this->taskFileModel->getAllDocuments($task['id']), + 'images' => $this->taskFileModel->getAllImages($task['id']), + 'comments' => $this->commentModel->getAll($task['id'], $commentSortingDirection), + 'subtasks' => $subtasks, + 'internal_links' => $this->taskLinkModel->getAllGroupedByLabel($task['id']), + 'external_links' => $this->taskExternalLinkModel->getAll($task['id']), + 'link_label_list' => $this->linkModel->getList(0, false), + 'tags' => $this->taskTagModel->getTagsByTask($task['id']), + ))); + } + + /** + * Display task analytics + * + * @access public + */ + public function analytics() + { + $task = $this->getTask(); + + $this->response->html($this->helper->layout->task('task/analytics', array( + 'task' => $task, + 'project' => $this->projectModel->getById($task['project_id']), + 'lead_time' => $this->taskAnalyticModel->getLeadTime($task), + 'cycle_time' => $this->taskAnalyticModel->getCycleTime($task), + 'time_spent_columns' => $this->taskAnalyticModel->getTimeSpentByColumn($task), + 'tags' => $this->taskTagModel->getTagsByTask($task['id']), + ))); + } + + /** + * Display the time tracking details + * + * @access public + */ + public function timetracking() + { + $task = $this->getTask(); + + $subtask_paginator = $this->paginator + ->setUrl('TaskViewController', 'timetracking', array('task_id' => $task['id'], 'pagination' => 'subtasks')) + ->setMax(15) + ->setOrder('start') + ->setDirection('DESC') + ->setQuery($this->subtaskTimeTrackingModel->getTaskQuery($task['id'])) + ->calculateOnlyIf($this->request->getStringParam('pagination') === 'subtasks'); + + $this->response->html($this->helper->layout->task('task/time_tracking_details', array( + 'task' => $task, + 'project' => $this->projectModel->getById($task['project_id']), + 'subtask_paginator' => $subtask_paginator, + 'tags' => $this->taskTagModel->getTagsByTask($task['id']), + ))); + } + + /** + * Display the task transitions + * + * @access public + */ + public function transitions() + { + $task = $this->getTask(); + + $this->response->html($this->helper->layout->task('task/transitions', array( + 'task' => $task, + 'project' => $this->projectModel->getById($task['project_id']), + 'transitions' => $this->transitionModel->getAllByTask($task['id']), + 'tags' => $this->taskTagModel->getTagsByTask($task['id']), + ))); + } +} diff --git a/app/Controller/TwoFactorController.php b/app/Controller/TwoFactorController.php new file mode 100644 index 0000000..6ec7353 --- /dev/null +++ b/app/Controller/TwoFactorController.php @@ -0,0 +1,232 @@ +userSession->getId()) { + throw new AccessForbiddenException(); + } + } + + /** + * Show form to disable/enable 2FA + * + * @access public + */ + public function index() + { + $user = $this->getUser(); + $this->checkCurrentUser($user); + session_remove('twoFactorSecret'); + + $this->response->html($this->helper->layout->user('twofactor/index', array( + 'user' => $user, + 'provider' => $this->authenticationManager->getPostAuthenticationProvider()->getName(), + ))); + } + + /** + * Show page with secret and test form + * + * @access public + */ + public function show() + { + $user = $this->getUser(); + $this->checkCurrentUser($user); + + $label = $user['email'] ?: $user['username']; + $provider = $this->authenticationManager->getPostAuthenticationProvider(); + + if (! session_exists('twoFactorSecret')) { + $provider->generateSecret(); + $provider->beforeCode(); + session_set('twoFactorSecret', $provider->getSecret()); + } else { + $provider->setSecret(session_get('twoFactorSecret')); + } + + $this->response->html($this->helper->layout->user('twofactor/show', array( + 'user' => $user, + 'secret' => session_get('twoFactorSecret'), + 'key_url' => $provider->getKeyUrl($label), + ))); + } + + /** + * Test code and save secret + * + * @access public + */ + public function test() + { + $user = $this->getUser(); + $this->checkCurrentUser($user); + + $values = $this->request->getValues(); + + $provider = $this->authenticationManager->getPostAuthenticationProvider(); + $provider->setCode(empty($values['code']) ? '' : $values['code']); + $provider->setSecret(session_get('twoFactorSecret')); + + if ($provider->authenticate()) { + $this->flash->success(t('The two factor authentication code is valid.')); + + $this->userModel->update(array( + 'id' => $user['id'], + 'twofactor_activated' => 1, + 'twofactor_secret' => $this->authenticationManager->getPostAuthenticationProvider()->getSecret(), + )); + + session_remove('twoFactorSecret'); + $this->userSession->disablePostAuthentication(); + + $this->response->redirect($this->helper->url->to('TwoFactorController', 'index', array('user_id' => $user['id'])), true); + } else { + $this->flash->failure(t('The two factor authentication code is not valid.')); + + if ($this->request->isAjax()) { + $this->show(); + } else { + $this->response->redirect($this->helper->url->to('TwoFactorController', 'show', array('user_id' => $user['id']))); + } + } + } + + /** + * Disable 2FA for the current user + * + * @access public + */ + public function deactivate() + { + $this->checkCSRFForm(); + $user = $this->getUser(); + $this->checkCurrentUser($user); + + $this->userModel->update(array( + 'id' => $user['id'], + 'twofactor_activated' => 0, + 'twofactor_secret' => '', + )); + + // Allow the user to test or disable the feature + $this->userSession->disablePostAuthentication(); + + $this->flash->success(t('User updated successfully.')); + $this->response->redirect($this->helper->url->to('TwoFactorController', 'index', array('user_id' => $user['id'])), true); + } + + /** + * Check 2FA + * + * @access public + */ + public function check() + { + $user = $this->getUser(); + $this->checkCurrentUser($user); + + $values = $this->request->getValues(); + + $provider = $this->authenticationManager->getPostAuthenticationProvider(); + $provider->setCode(empty($values['code']) ? '' : $values['code']); + $provider->setSecret($user['twofactor_secret']); + + if ($provider->authenticate()) { + $this->userSession->setPostAuthenticationAsValidated(); + $this->flash->success(t('The two factor authentication code is valid.')); + + if (REMEMBER_ME_AUTH && session_is_true('hasRememberMe')) { + $session = $this->rememberMeSessionModel->create($this->userSession->getId(), $this->request->getIpAddress(), $this->request->getUserAgent()); + $this->rememberMeCookie->write($session['token'], $session['sequence'], $session['expiration']); + } + + $this->response->redirect($this->helper->url->to('DashboardController', 'show')); + } else { + $this->flash->failure(t('The two factor authentication code is not valid.')); + $this->response->redirect($this->helper->url->to('TwoFactorController', 'code')); + } + } + + /** + * Ask the 2FA code + * + * @access public + */ + public function code() + { + if (! session_exists('twoFactorBeforeCodeCalled')) { + $provider = $this->authenticationManager->getPostAuthenticationProvider(); + $provider->beforeCode(); + session_set('twoFactorBeforeCodeCalled', true); + } + + $this->response->html($this->helper->layout->app('twofactor/check', array( + 'title' => t('Check two factor authentication code'), + 'no_layout' => true, + ))); + } + + /** + * Disable 2FA for a user + * + * @access public + */ + public function disable() + { + $user = $this->getUser(); + + if ($this->request->getStringParam('disable') === 'yes') { + $this->checkCSRFParam(); + + $this->userModel->update(array( + 'id' => $user['id'], + 'twofactor_activated' => 0, + 'twofactor_secret' => '', + )); + + $this->response->redirect($this->helper->url->to('UserViewController', 'show', array('user_id' => $user['id'])), true); + } else { + $this->response->html($this->helper->layout->user('twofactor/disable', array( + 'user' => $user, + ))); + } + } + + /** + * Render QR Code image + */ + public function qrcode() + { + if (session_exists('twoFactorSecret')) { + $user = $this->getUser(); + $provider = $this->authenticationManager->getPostAuthenticationProvider(); + $provider->setSecret(session_get('twoFactorSecret')); + $url = $provider->getKeyUrl($user['email'] ?: $user['username']); + + if (! empty($url)) { + PHPQRCode\QRcode::png($url, false, 'L', 6, 0); + } + } + } +} diff --git a/app/Controller/UserAjaxController.php b/app/Controller/UserAjaxController.php new file mode 100644 index 0000000..5759620 --- /dev/null +++ b/app/Controller/UserAjaxController.php @@ -0,0 +1,48 @@ +request->getStringParam('term'); + $users = $this->userManager->find($search); + $this->response->json($this->userAutoCompleteFormatter->withUsers($users)->format()); + } + + /** + * User mention auto-completion (Ajax) + * + * @access public + */ + public function mention() + { + $project_id = $this->request->getStringParam('project_id'); + $query = $this->request->getStringParam('search'); + $users = $this->projectPermissionModel->findUsernames($project_id, $query); + + $this->response->json($this->userMentionFormatter->withUsers($users)->format()); + } + + /** + * Check if the user is connected + * + * @access public + */ + public function status() + { + $this->response->text('OK'); + } +} diff --git a/app/Controller/UserApiAccessController.php b/app/Controller/UserApiAccessController.php new file mode 100644 index 0000000..fab64a5 --- /dev/null +++ b/app/Controller/UserApiAccessController.php @@ -0,0 +1,59 @@ +getUser(); + + return $this->response->html($this->helper->layout->user('user_api_access/show', array( + 'user' => $user, + 'title' => t('API User Access'), + ))); + } + + public function generate() + { + $user = $this->getUser(); + $this->checkCSRFParam(); + + $this->userModel->update(array( + 'id' => $user['id'], + 'api_access_token' => Token::getToken(), + )); + + $this->renderResponse($user); + } + + public function remove() + { + $user = $this->getUser(); + $this->checkCSRFParam(); + + $this->userModel->update(array( + 'id' => $user['id'], + 'api_access_token' => null, + )); + + $this->renderResponse($user); + } + + protected function renderResponse(array $user) + { + if ($this->request->isAjax()) { + $this->show(); + } else { + $this->response->redirect($this->helper->url->to('UserApiAccessController', 'show', array('user_id' => $user['id']))); + } + } +} diff --git a/app/Controller/UserCreationController.php b/app/Controller/UserCreationController.php new file mode 100644 index 0000000..f9897a4 --- /dev/null +++ b/app/Controller/UserCreationController.php @@ -0,0 +1,84 @@ +response->html($this->template->render('user_creation/show', array( + 'themes' => $this->themeModel->getThemes(), + 'timezones' => $this->timezoneModel->getTimezones(true), + 'languages' => $this->languageModel->getLanguages(true), + 'roles' => $this->role->getApplicationRoles(), + 'projects' => $this->projectModel->getList(), + 'errors' => $errors, + 'values' => $values + array('role' => Role::APP_USER), + ))); + } + + /** + * Validate and save a new user + * + * @access public + */ + public function save() + { + $values = $this->request->getValues(); + list($valid, $errors) = $this->userValidator->validateCreation($values); + + if ($valid) { + $this->createUser($values); + } else { + $this->show($values, $errors); + } + } + + /** + * Create user + * + * @param array $values + */ + protected function createUser(array $values) + { + $project_id = empty($values['project_id']) ? 0 : $values['project_id']; + unset($values['project_id']); + + $user_id = $this->userModel->create($values); + + if ($user_id !== false) { + if ($project_id !== 0) { + $this->projectUserRoleModel->addUser($project_id, $user_id, Role::PROJECT_MEMBER); + } + + if ($this->configModel->get('notifications_enabled', 0) == 1) { + $this->userNotificationTypeModel->saveSelectedTypes($user_id, [MailNotification::TYPE, WebNotification::TYPE]); + } elseif (! empty($values['notifications_enabled'])) { + $this->userNotificationTypeModel->saveSelectedTypes($user_id, [MailNotification::TYPE]); + } + + $this->flash->success(t('User created successfully.')); + $this->response->redirect($this->helper->url->to('UserViewController', 'show', array('user_id' => $user_id))); + } else { + $this->flash->failure(t('Unable to create your user.')); + $this->response->redirect($this->helper->url->to('UserListController', 'show')); + } + } +} diff --git a/app/Controller/UserCredentialController.php b/app/Controller/UserCredentialController.php new file mode 100644 index 0000000..a8b90b7 --- /dev/null +++ b/app/Controller/UserCredentialController.php @@ -0,0 +1,134 @@ +getUser(); + + return $this->response->html($this->helper->layout->user('user_credential/password', array( + 'values' => $values + array('id' => $user['id']), + 'errors' => $errors, + 'user' => $user, + ))); + } + + /** + * Save new password + * + * @throws \Kanboard\Core\Controller\AccessForbiddenException + * @throws \Kanboard\Core\Controller\PageNotFoundException + */ + public function savePassword() + { + $user = $this->getUser(); + $values = $this->request->getValues(); + + list($valid, $errors) = $this->userValidator->validatePasswordModification($values); + + if (! $this->userSession->isAdmin()) { + $values = array( + 'id' => $this->userSession->getId(), + 'password' => isset($values['password']) ? $values['password'] : '', + 'confirmation' => isset($values['confirmation']) ? $values['confirmation'] : '', + ); + } + + if ($valid) { + if ($this->userModel->update($values)) { + $this->flash->success(t('Password modified successfully.')); + $this->userLockingModel->resetFailedLogin($user['username']); + $this->response->redirect($this->helper->url->to('UserViewController', 'show', array('user_id' => $user['id'])), true); + return; + } else { + $this->flash->failure(t('Unable to change the password.')); + } + } + + $this->changePassword($values, $errors); + } + + /** + * Display a form to edit authentication + * + * @access public + * @param array $values + * @param array $errors + * @throws \Kanboard\Core\Controller\AccessForbiddenException + * @throws \Kanboard\Core\Controller\PageNotFoundException + */ + public function changeAuthentication(array $values = array(), array $errors = array()) + { + $user = $this->getUser(); + + if (empty($values)) { + $values = $user; + unset($values['password']); + } + + return $this->response->html($this->helper->layout->user('user_credential/authentication', array( + 'values' => $values, + 'errors' => $errors, + 'user' => $user, + ))); + } + + /** + * Save authentication + * + * @throws \Kanboard\Core\Controller\AccessForbiddenException + * @throws \Kanboard\Core\Controller\PageNotFoundException + */ + public function saveAuthentication() + { + $user = $this->getUser(); + $values = $this->request->getValues() + array('disable_login_form' => 0, 'is_ldap_user' => 0); + list($valid, $errors) = $this->userValidator->validateModification($values); + + if ($valid) { + if ($this->userModel->update($values)) { + $this->flash->success(t('User updated successfully.')); + $this->response->redirect($this->helper->url->to('UserCredentialController', 'changeAuthentication', array('user_id' => $user['id'])), true); + return; + } else { + $this->flash->failure(t('Unable to update this user.')); + } + } + + $this->changeAuthentication($values, $errors); + } + + /** + * Unlock user + */ + public function unlock() + { + $user = $this->getUser(); + $this->checkCSRFParam(); + + if ($this->userLockingModel->resetFailedLogin($user['username'])) { + $this->flash->success(t('User unlocked successfully.')); + } else { + $this->flash->failure(t('Unable to unlock the user.')); + } + + $this->response->redirect($this->helper->url->to('UserViewController', 'show', array('user_id' => $user['id']))); + } +} diff --git a/app/Controller/UserImportController.php b/app/Controller/UserImportController.php new file mode 100644 index 0000000..8f3ee12 --- /dev/null +++ b/app/Controller/UserImportController.php @@ -0,0 +1,84 @@ +response->html($this->template->render('user_import/show', array( + 'values' => $values, + 'errors' => $errors, + 'max_size' => get_upload_max_size(), + 'delimiters' => Csv::getDelimiters(), + 'enclosures' => Csv::getEnclosures(), + ))); + } + + /** + * Submit form + */ + public function save() + { + $values = $this->request->getValues(); + + // Note: $values is empty when the CSRF token is invalid. + if (empty($values)) { + throw new AccessForbiddenException(); + } + + $filename = $this->request->getFilePath('file'); + + if (! file_exists($filename)) { + $this->flash->failure(t('Unable to read your file')); + } else { + $this->importFile($values, $filename); + } + + $this->response->redirect($this->helper->url->to('UserListController', 'show')); + } + + /** + * Generate template + * + */ + public function template() + { + $this->response->withFileDownload('users.csv'); + $this->response->csv(array($this->userImport->getColumnMapping())); + } + + /** + * Process file + * + * @param array $values + * @param $filename + */ + private function importFile(array $values, $filename) + { + $csv = new Csv($values['delimiter'], $values['enclosure']); + $csv->setColumnMapping($this->userImport->getColumnMapping()); + $csv->read($filename, array($this->userImport, 'import')); + + if ($this->userImport->counter > 0) { + $this->flash->success(t('%d user(s) have been imported successfully.', $this->userImport->counter)); + } else { + $this->flash->failure(t('Nothing has been imported!')); + } + } +} diff --git a/app/Controller/UserInviteController.php b/app/Controller/UserInviteController.php new file mode 100644 index 0000000..cd38dc1 --- /dev/null +++ b/app/Controller/UserInviteController.php @@ -0,0 +1,119 @@ +response->html($this->template->render('user_invite/show', array( + 'projects' => $this->projectModel->getList(), + 'errors' => $errors, + 'values' => $values, + ))); + } + + public function save() + { + $values = $this->request->getValues(); + + if (! empty($values['emails']) && isset($values['project_id'])) { + $emails = explode("\r\n", trim($values['emails'])); + $nb = $this->inviteModel->createInvites($emails, $values['project_id']); + $this->flash->success($nb > 1 ? t('%d invitations were sent.', $nb) : t('%d invitation was sent.', $nb)); + } + + $this->response->redirect($this->helper->url->to('UserListController', 'show')); + } + + public function signup(array $values = array(), array $errors = array()) + { + $invite = $this->getInvite(); + + $this->response->html($this->helper->layout->app('user_invite/signup', array( + 'no_layout' => true, + 'not_editable' => true, + 'token' => $invite['token'], + 'errors' => $errors, + 'values' => $values + array('email' => $invite['email']), + 'themes' => $this->themeModel->getThemes(), + 'timezones' => $this->timezoneModel->getTimezones(true), + 'languages' => $this->languageModel->getLanguages(true), + ))); + } + + public function register() + { + $invite = $this->getInvite(); + + $input = $this->request->getValues(); + $values = array(); + + foreach (['username', 'name', 'email', 'password', 'confirmation', 'timezone', 'language', 'notifications_enabled'] as $field) { + if (array_key_exists($field, $input)) { + $values[$field] = $input[$field]; + } + } + + list($valid, $errors) = $this->userValidator->validateCreation($values); + + if ($valid) { + $this->createUser($invite, $values); + } else { + $this->signup($values, $errors); + } + } + + protected function getInvite() + { + $token = $this->request->getStringParam('token'); + + if (empty($token)) { + throw PageNotFoundException::getInstance()->withoutLayout(); + } + + $invite = $this->inviteModel->getByToken($token); + + if (empty($invite)) { + throw PageNotFoundException::getInstance()->withoutLayout(); + } + + return $invite; + } + + protected function createUser(array $invite, array $values) + { + $user_id = $this->userModel->create($values); + + if ($user_id !== false) { + if ($invite['project_id'] != 0) { + $this->projectUserRoleModel->addUser($invite['project_id'], $user_id, Role::PROJECT_MEMBER); + } + + if ($this->configModel->get('notifications_enabled', 0) == 1) { + $this->userNotificationTypeModel->saveSelectedTypes($user_id, [MailNotification::TYPE, WebNotification::TYPE]); + } elseif (! empty($values['notifications_enabled'])) { + $this->userNotificationTypeModel->saveSelectedTypes($user_id, [MailNotification::TYPE]); + } + + $this->inviteModel->remove($invite['email']); + + $this->flash->success(t('User created successfully.')); + $this->response->redirect($this->helper->url->to('AuthController', 'login')); + } else { + $this->flash->failure(t('Unable to create this user.')); + $this->response->redirect($this->helper->url->to('UserInviteController', 'signup')); + } + } +} diff --git a/app/Controller/UserListController.php b/app/Controller/UserListController.php new file mode 100644 index 0000000..010c834 --- /dev/null +++ b/app/Controller/UserListController.php @@ -0,0 +1,60 @@ +userPagination->getListingPaginator(); + + $this->response->html($this->helper->layout->app('user_list/listing', array( + 'title' => t('Users').' ('.$paginator->getTotal().')', + 'paginator' => $paginator, + 'values' => array(), + ))); + } + + /** + * Search in users + * + * @access public + */ + public function search() + { + $search = $this->request->getStringParam('search'); + $paginator = $this->userPagination->getListingPaginator(); + + if ($search !== '' && ! $paginator->isEmpty()) { + $paginator = $paginator + ->setUrl('UserListController', 'search', array('search' => $search)) + ->setQuery( + $this->userQuery + ->withFilter(new UserNameFilter($search)) + ->getQuery() + ) + ->calculate(); + } + + $this->response->html($this->helper->layout->app('user_list/listing', array( + 'title' => t('Users').' ('.$paginator->getTotal().')', + 'values' => array( + 'search' => $search, + ), + 'paginator' => $paginator + ))); + } +} diff --git a/app/Controller/UserModificationController.php b/app/Controller/UserModificationController.php new file mode 100644 index 0000000..be432f0 --- /dev/null +++ b/app/Controller/UserModificationController.php @@ -0,0 +1,77 @@ +getUser(); + + if (empty($values)) { + $values = $user; + unset($values['password']); + } + + return $this->response->html($this->helper->layout->user('user_modification/show', array( + 'values' => $values, + 'errors' => $errors, + 'user' => $user, + 'themes' => $this->themeModel->getThemes(), + 'timezones' => $this->timezoneModel->getTimezones(true), + 'languages' => $this->languageModel->getLanguages(true), + 'roles' => $this->role->getApplicationRoles(), + ))); + } + + /** + * Save user information + */ + public function save() + { + $user = $this->getUser(); + $values = $this->request->getValues(); + + if (! $this->userSession->isAdmin()) { + $values = array( + 'id' => $this->userSession->getId(), + 'username' => isset($values['username']) ? $values['username'] : '', + 'name' => isset($values['name']) ? $values['name'] : '', + 'email' => isset($values['email']) ? $values['email'] : '', + 'theme' => isset($values['theme']) ? $values['theme'] : '', + 'timezone' => isset($values['timezone']) ? $values['timezone'] : '', + 'language' => isset($values['language']) ? $values['language'] : '', + 'filter' => isset($values['filter']) ? $values['filter'] : '', + ); + } + + list($valid, $errors) = $this->userValidator->validateModification($values); + + if ($valid) { + if ($this->userModel->update($values)) { + $this->flash->success(t('User updated successfully.')); + $this->response->redirect($this->helper->url->to('UserViewController', 'show', array('user_id' => $user['id'])), true); + return; + } else { + $this->flash->failure(t('Unable to update this user.')); + } + } + + $this->show($values, $errors); + } +} diff --git a/app/Controller/UserStatusController.php b/app/Controller/UserStatusController.php new file mode 100644 index 0000000..070fb6f --- /dev/null +++ b/app/Controller/UserStatusController.php @@ -0,0 +1,111 @@ +getUser(); + + $this->response->html($this->helper->layout->user('user_status/remove', array( + 'user' => $user, + ))); + } + + /** + * Remove a user + * + * @access public + */ + public function remove() + { + $user = $this->getUser(); + $this->checkCSRFParam(); + + if ($this->userModel->remove($user['id'])) { + $this->flash->success(t('User removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this user.')); + } + + $this->response->redirect($this->helper->url->to('UserListController', 'show')); + } + + /** + * Confirm enable a user + * + * @access public + */ + public function confirmEnable() + { + $user = $this->getUser(); + + $this->response->html($this->helper->layout->user('user_status/enable', array( + 'user' => $user, + ))); + } + + /** + * Enable a user + * + * @access public + */ + public function enable() + { + $user = $this->getUser(); + $this->checkCSRFParam(); + + if ($this->userModel->enable($user['id'])) { + $this->flash->success(t('User activated successfully.')); + } else { + $this->flash->failure(t('Unable to enable this user.')); + } + + $this->response->redirect($this->helper->url->to('UserListController', 'show')); + } + + /** + * Confirm disable a user + * + * @access public + */ + public function confirmDisable() + { + $user = $this->getUser(); + + $this->response->html($this->helper->layout->user('user_status/disable', array( + 'user' => $user, + ))); + } + + /** + * Disable a user + * + * @access public + */ + public function disable() + { + $user = $this->getUser(); + $this->checkCSRFParam(); + + if ($this->userModel->disable($user['id'])) { + $this->flash->success(t('User disabled successfully.')); + } else { + $this->flash->failure(t('Unable to disable this user.')); + } + + $this->response->redirect($this->helper->url->to('UserListController', 'show')); + } +} diff --git a/app/Controller/UserViewController.php b/app/Controller/UserViewController.php new file mode 100644 index 0000000..be457bd --- /dev/null +++ b/app/Controller/UserViewController.php @@ -0,0 +1,235 @@ +userModel->getById($this->request->getIntegerParam('user_id')); + + if (empty($user)) { + throw new PageNotFoundException(); + } + + $this->response->html($this->helper->layout->app('user_view/profile', array( + 'title' => $user['name'] ?: $user['username'], + 'user' => $user, + ))); + } + + /** + * Display user information + * + * @access public + */ + public function show() + { + $user = $this->getUser(); + $this->response->html($this->helper->layout->user('user_view/show', array( + 'user' => $user, + 'themes' => $this->themeModel->getThemes(), + 'timezones' => $this->timezoneModel->getTimezones(true), + 'languages' => $this->languageModel->getLanguages(true), + ))); + } + + /** + * Display timesheet + * + * @access public + */ + public function timesheet() + { + $user = $this->getUser(); + + $subtask_paginator = $this->paginator + ->setUrl('UserViewController', 'timesheet', array('user_id' => $user['id'], 'pagination' => 'subtasks')) + ->setMax(20) + ->setOrder('start') + ->setDirection('DESC') + ->setQuery($this->subtaskTimeTrackingModel->getUserQuery($user['id'])) + ->calculateOnlyIf($this->request->getStringParam('pagination') === 'subtasks'); + + $this->response->html($this->helper->layout->user('user_view/timesheet', array( + 'subtask_paginator' => $subtask_paginator, + 'user' => $user, + ))); + } + + /** + * Display last password reset + * + * @access public + */ + public function passwordReset() + { + $user = $this->getUser(); + $this->response->html($this->helper->layout->user('user_view/password_reset', array( + 'tokens' => $this->passwordResetModel->getAll($user['id']), + 'user' => $user, + ))); + } + + /** + * Display last connections + * + * @access public + */ + public function lastLogin() + { + $user = $this->getUser(); + $this->response->html($this->helper->layout->user('user_view/last', array( + 'last_logins' => $this->lastLoginModel->getAll($user['id']), + 'user' => $user, + ))); + } + + /** + * Display user sessions + * + * @access public + */ + public function sessions() + { + if (! REMEMBER_ME_AUTH) { + $this->response->status(404); + return; + } + + $user = $this->getUser(); + $this->response->html($this->helper->layout->user('user_view/sessions', array( + 'sessions' => $this->rememberMeSessionModel->getAll($user['id']), + 'user' => $user, + ))); + } + + /** + * Remove a "RememberMe" token + * + * @access public + */ + public function removeSession() + { + $this->checkCSRFParam(); + $user = $this->getUser(); + $this->rememberMeSessionModel->remove($this->request->getIntegerParam('id')); + + if ($this->request->isAjax()) { + $this->sessions(); + } else { + $this->response->redirect($this->helper->url->to('UserViewController', 'sessions', array('user_id' => $user['id'])), true); + } + } + + /** + * Display user notifications + * + * @access public + */ + public function notifications() + { + $user = $this->getUser(); + + if ($this->request->isPost()) { + $values = $this->request->getValues(); + $this->userNotificationModel->saveSettings($user['id'], $values); + $this->flash->success(t('User updated successfully.')); + $this->response->redirect($this->helper->url->to('UserViewController', 'notifications', array('user_id' => $user['id'])), true); + return; + } + + $this->response->html($this->helper->layout->user('user_view/notifications', array( + 'projects' => $this->projectUserRoleModel->getProjectsByUser($user['id'], array(ProjectModel::ACTIVE)), + 'notifications' => $this->userNotificationModel->readSettings($user['id']), + 'types' => $this->userNotificationTypeModel->getTypes(), + 'filters' => $this->userNotificationFilterModel->getFilters(), + 'user' => $user, + ))); + } + + /** + * Display user integrations + * + * @access public + */ + public function integrations() + { + $user = $this->getUser(); + + if ($this->request->isPost()) { + $values = $this->request->getValues(); + $this->userMetadataModel->save($user['id'], $values); + $this->flash->success(t('User updated successfully.')); + $this->response->redirect($this->helper->url->to('UserViewController', 'integrations', array('user_id' => $user['id'])), true); + return; + } + + $this->response->html($this->helper->layout->user('user_view/integrations', array( + 'user' => $user, + 'values' => $this->userMetadataModel->getAll($user['id']), + ))); + } + + /** + * Display external accounts + * + * @access public + */ + public function external() + { + $user = $this->getUser(); + $this->response->html($this->helper->layout->user('user_view/external', array( + 'last_logins' => $this->lastLoginModel->getAll($user['id']), + 'user' => $user, + ))); + } + + /** + * Public access management + * + * @access public + */ + public function share() + { + $user = $this->getUser(); + $switch = $this->request->getStringParam('switch'); + + if ($switch === 'enable' || $switch === 'disable') { + $this->checkCSRFParam(); + + if ($this->userModel->{$switch . 'PublicAccess'}($user['id'])) { + $this->flash->success(t('User updated successfully.')); + } else { + $this->flash->failure(t('Unable to update this user.')); + } + + if (! $this->request->isAjax()) { + $this->response->redirect($this->helper->url->to('UserViewController', 'share', array('user_id' => $user['id'])), true); + return; + } + + $user = $this->getUser(); + } + + $this->response->html($this->helper->layout->user('user_view/share', array( + 'user' => $user, + 'title' => t('Public access'), + ))); + } +} diff --git a/app/Controller/WebNotificationController.php b/app/Controller/WebNotificationController.php new file mode 100644 index 0000000..9562474 --- /dev/null +++ b/app/Controller/WebNotificationController.php @@ -0,0 +1,96 @@ +getUser(); + $notifications = $this->userUnreadNotificationModel->getAll($user['id']); + + $this->response->html($this->template->render('web_notification/show', array( + 'notifications' => $notifications, + 'nb_notifications' => count($notifications), + 'user' => $user, + ))); + } + + /** + * Mark all notifications as read + * + * @access public + */ + public function flush() + { + $this->checkReusableGETCSRFParam(); + $userId = $this->getUserId(); + $this->userUnreadNotificationModel->markAllAsRead($userId); + $this->show(); + } + + /** + * Mark a notification as read + * + * @access public + */ + public function remove() + { + $this->checkReusableGETCSRFParam(); + $user_id = $this->getUserId(); + $notification_id = $this->request->getIntegerParam('notification_id'); + $this->userUnreadNotificationModel->markAsRead($user_id, $notification_id); + $this->show(); + } + + /** + * Redirect to the task and mark notification as read + */ + public function redirect() + { + $user_id = $this->getUserId(); + $notification_id = $this->request->getIntegerParam('notification_id'); + + $notification = $this->userUnreadNotificationModel->getById($notification_id); + $this->userUnreadNotificationModel->markAsRead($user_id, $notification_id); + + if (empty($notification)) { + $this->show(); + } elseif ($this->helper->text->contains($notification['event_name'], 'comment')) { + $this->response->redirect($this->helper->url->to( + 'TaskViewController', + 'show', + array('task_id' => $this->notificationModel->getTaskIdFromEvent($notification['event_name'], $notification['event_data'])), + 'comment-'.$notification['event_data']['comment']['id'] + )); + } else { + $this->response->redirect($this->helper->url->to( + 'TaskViewController', + 'show', + array('task_id' => $this->notificationModel->getTaskIdFromEvent($notification['event_name'], $notification['event_data'])) + )); + } + } + + private function getUserId() + { + $user_id = $this->request->getIntegerParam('user_id'); + + if (! $this->userSession->isAdmin() && $user_id != $this->userSession->getId()) { + $user_id = $this->userSession->getId(); + } + + return $user_id; + } +} diff --git a/app/Core/Action/ActionManager.php b/app/Core/Action/ActionManager.php new file mode 100644 index 0000000..3a77084 --- /dev/null +++ b/app/Core/Action/ActionManager.php @@ -0,0 +1,167 @@ +actions[$action->getName()] = $action; + return $this; + } + + /** + * Get automatic action instance + * + * @access public + * @param string $name Absolute class name with namespace + * @return ActionBase + */ + public function getAction($name) + { + if (isset($this->actions[$name])) { + return $this->actions[$name]; + } + + throw new RuntimeException('Automatic Action Not Found: '.$name); + } + + /** + * Get available automatic actions + * + * @access public + * @return array + */ + public function getAvailableActions() + { + $actions = array(); + + foreach ($this->actions as $action) { + if (count($action->getEvents()) > 0) { + $actions[$action->getName()] = $action->getDescription(); + } + } + + asort($actions); + + return $actions; + } + + /** + * Get all available action parameters + * + * @access public + * @param array $actions + * @return array + */ + public function getAvailableParameters(array $actions) + { + $params = array(); + + foreach ($actions as $action) { + try { + $currentAction = $this->getAction($action['action_name']); + $params[$currentAction->getName()] = $currentAction->getActionRequiredParameters(); + } catch (Exception $e) { + $this->logger->error(__METHOD__.': '.$e->getMessage()); + } + } + + return $params; + } + + /** + * Get list of compatible events for a given action + * + * @access public + * @param string $name + * @return array + */ + public function getCompatibleEvents($name) + { + $events = array(); + $actionEvents = $this->getAction($name)->getEvents(); + + foreach ($this->eventManager->getAll() as $event => $description) { + if (in_array($event, $actionEvents)) { + $events[$event] = $description; + } + } + + return $events; + } + + /** + * Bind automatic actions to events + * + * @access public + * @return ActionManager + */ + public function attachEvents() + { + if ($this->userSession->isLogged()) { + $actions = $this->actionModel->getAllByUser($this->userSession->getId()); + } else { + $actions = $this->actionModel->getAll(); + } + + foreach ($actions as $action) { + try { + $listener = clone $this->getAction($action['action_name']); + $listener->setProjectId($action['project_id']); + + foreach ($action['params'] as $param_name => $param_value) { + $listener->setParam($param_name, $param_value); + } + + $this->dispatcher->addListener($action['event_name'], array($listener, 'execute')); + } catch (Exception $e) { + $this->logger->error(__METHOD__.': '.$e->getMessage()); + } + } + + return $this; + } + + /** + * Remove all listeners for automated actions + * + * @access public + */ + public function removeEvents() + { + foreach ($this->dispatcher->getListeners() as $eventName => $listeners) { + foreach ($listeners as $listener) { + if (is_array($listener) && $listener[0] instanceof ActionBase) { + $this->dispatcher->removeListener($eventName, $listener); + } + } + } + } +} diff --git a/app/Core/Base.php b/app/Core/Base.php new file mode 100644 index 0000000..3280625 --- /dev/null +++ b/app/Core/Base.php @@ -0,0 +1,261 @@ +container = $container; + } + + /** + * Load automatically dependencies + * + * @access public + * @param string $name Class name + * @return mixed + */ + public function __get($name) + { + return $this->container[$name]; + } + + /** + * Get object instance + * + * @static + * @access public + * @param Container $container + * @return static + */ + public static function getInstance(Container $container) + { + return new static($container); + } +} diff --git a/app/Core/Cache/BaseCache.php b/app/Core/Cache/BaseCache.php new file mode 100644 index 0000000..b51c4c0 --- /dev/null +++ b/app/Core/Cache/BaseCache.php @@ -0,0 +1,38 @@ +get($key); + + if ($result === null) { + $result = call_user_func_array(array($class, $method), array_splice($args, 1)); + $this->set($key, $result); + } + + return $result; + } +} diff --git a/app/Core/Cache/CacheInterface.php b/app/Core/Cache/CacheInterface.php new file mode 100644 index 0000000..033732c --- /dev/null +++ b/app/Core/Cache/CacheInterface.php @@ -0,0 +1,45 @@ +storage[$key] = $value; + } + + /** + * Retrieve an item from the cache by key + * + * @access public + * @param string $key + * @return mixed Null when not found, cached value otherwise + */ + public function get($key) + { + return isset($this->storage[$key]) ? $this->storage[$key] : null; + } + + /** + * Clear all cache + * + * @access public + */ + public function flush() + { + $this->storage = array(); + } + + /** + * Remove cached value + * + * @access public + * @param string $key + */ + public function remove($key) + { + unset($this->storage[$key]); + } +} diff --git a/app/Core/Controller/AccessForbiddenException.php b/app/Core/Controller/AccessForbiddenException.php new file mode 100644 index 0000000..b5dccb7 --- /dev/null +++ b/app/Core/Controller/AccessForbiddenException.php @@ -0,0 +1,14 @@ +withoutLayout = true; + return $this; + } + + /** + * Return true if no layout + * + * @access public + * @return boolean + */ + public function hasLayout() + { + return $this->withoutLayout; + } +} diff --git a/app/Core/Controller/BaseMiddleware.php b/app/Core/Controller/BaseMiddleware.php new file mode 100644 index 0000000..e94ad95 --- /dev/null +++ b/app/Core/Controller/BaseMiddleware.php @@ -0,0 +1,58 @@ +nextMiddleware = $nextMiddleware; + return $this; + } + + /** + * @return BaseMiddleware + */ + public function getNextMiddleware() + { + return $this->nextMiddleware; + } + + /** + * Move to next middleware + */ + public function next() + { + if ($this->nextMiddleware !== null) { + if (DEBUG) { + $this->logger->debug(__METHOD__.' => ' . get_class($this->nextMiddleware)); + } + + $this->nextMiddleware->execute(); + } + } +} diff --git a/app/Core/Controller/PageNotFoundException.php b/app/Core/Controller/PageNotFoundException.php new file mode 100644 index 0000000..e96a205 --- /dev/null +++ b/app/Core/Controller/PageNotFoundException.php @@ -0,0 +1,14 @@ +executeMiddleware(); + + if (!$this->response->isResponseAlreadySent()) { + $this->executeController(); + } + } catch (PageNotFoundException $e) { + $controllerObject = new AppController($this->container); + $controllerObject->notFound($e->hasLayout()); + } catch (AccessForbiddenException $e) { + $controllerObject = new AppController($this->container); + $controllerObject->accessForbidden($e->hasLayout(), $e->getMessage()); + } + } + + /** + * Execute all middleware + */ + protected function executeMiddleware() + { + if (DEBUG) { + $this->logger->debug(__METHOD__); + } + + $bootstrapMiddleware = new BootstrapMiddleware($this->container); + $authenticationMiddleware = new AuthenticationMiddleware($this->container); + $postAuthenticationMiddleware = new PostAuthenticationMiddleware($this->container); + $appAuthorizationMiddleware = new ApplicationAuthorizationMiddleware($this->container); + $projectAuthorizationMiddleware = new ProjectAuthorizationMiddleware($this->container); + + $bootstrapMiddleware->setNextMiddleware($authenticationMiddleware); + $authenticationMiddleware->setNextMiddleware($postAuthenticationMiddleware); + $postAuthenticationMiddleware->setNextMiddleware($appAuthorizationMiddleware); + $appAuthorizationMiddleware->setNextMiddleware($projectAuthorizationMiddleware); + + $bootstrapMiddleware->execute(); + } + + /** + * Execute the controller + */ + protected function executeController() + { + $className = $this->getControllerClassName(); + + if (DEBUG) { + $this->logger->debug(__METHOD__.' => '.$className.'::'.$this->router->getAction()); + } + + $controllerObject = new $className($this->container); + $controllerObject->{$this->router->getAction()}(); + } + + /** + * Get controller class name + * + * @access protected + * @return string + * @throws RuntimeException + */ + protected function getControllerClassName() + { + if ($this->router->getPlugin() !== '') { + $className = '\Kanboard\Plugin\\'.$this->router->getPlugin().'\Controller\\'.$this->router->getController(); + } else { + $className = '\Kanboard\Controller\\'.$this->router->getController(); + } + + if (! class_exists($className)) { + throw new RuntimeException('Controller not found'); + } + + if (! method_exists($className, $this->router->getAction())) { + throw new RuntimeException('Action not implemented'); + } + + return $className; + } +} diff --git a/app/Core/Csv.php b/app/Core/Csv.php new file mode 100644 index 0000000..773a6b3 --- /dev/null +++ b/app/Core/Csv.php @@ -0,0 +1,222 @@ +delimiter = $delimiter; + $this->enclosure = $enclosure; + } + + /** + * Get list of delimiters + * + * @static + * @access public + * @return array + */ + public static function getDelimiters() + { + return array( + ',' => t('Comma'), + ';' => t('Semi-colon'), + '\t' => t('Tab'), + '|' => t('Vertical bar'), + ); + } + + /** + * Get list of enclosures + * + * @static + * @access public + * @return array + */ + public static function getEnclosures() + { + return array( + '"' => t('Double Quote'), + "'" => t('Single Quote'), + '' => t('None'), + ); + } + + /** + * Check boolean field value + * + * @static + * @access public + * @param mixed $value + * @return int + */ + public static function getBooleanValue($value) + { + if (! empty($value)) { + $value = trim(strtolower($value)); + return $value === '1' || $value[0] === 't' || $value[0] === 'y' ? 1 : 0; + } + + return 0; + } + + /** + * Output CSV file to standard output + * + * @static + * @access public + * @param array $rows + * @param bool $addBOM + */ + public static function output(array $rows, $addBOM = false) + { + $csv = new static; + $csv->write('php://output', $rows, $addBOM); + } + + /** + * Define column mapping between CSV and SQL columns + * + * @access public + * @param array $columns + * @return Csv + */ + public function setColumnMapping(array $columns) + { + $this->columns = $columns; + return $this; + } + + /** + * Read CSV file + * + * @access public + * @param string $filename + * @param callable $callback Example: function(array $row, $line_number) + * @return Csv + */ + public function read($filename, $callback) + { + $file = new SplFileObject($filename); + $file->setFlags(SplFileObject::READ_CSV); + $file->setCsvControl($this->delimiter, $this->enclosure, '\\'); + $line_number = 0; + + foreach ($file as $row) { + $row = $this->filterRow($row); + + if (! empty($row) && $line_number > 0) { + call_user_func_array($callback, array($this->associateColumns($row), $line_number)); + } + + $line_number++; + } + + return $this; + } + + /** + * Write CSV file + * + * @access public + * @param string $filename + * @param array $rows + * @param bool $addBOM + * @return Csv + */ + public function write($filename, array $rows, $addBOM = false) + { + $fp = fopen($filename, 'w'); + + if (is_resource($fp)) { + if ($addBOM) { + fwrite($fp, "\xEF\xBB\xBF"); + } + + foreach ($rows as $row) { + fputcsv($fp, $row, $this->delimiter, $this->enclosure, '\\'); + } + + fclose($fp); + } + + return $this; + } + + /** + * Associate columns header with row values + * + * @access private + * @param array $row + * @return array + */ + private function associateColumns(array $row) + { + $line = array(); + $index = 0; + + foreach ($this->columns as $sql_name => $csv_name) { + if (isset($row[$index])) { + $line[$sql_name] = $row[$index]; + } else { + $line[$sql_name] = ''; + } + + $index++; + } + + return $line; + } + + /** + * Filter empty rows + * + * @access private + * @param array $row + * @return array + */ + private function filterRow(array $row) + { + return array_filter($row); + } +} diff --git a/app/Core/DateParser.php b/app/Core/DateParser.php new file mode 100644 index 0000000..26cfaa8 --- /dev/null +++ b/app/Core/DateParser.php @@ -0,0 +1,326 @@ +configModel->get('application_date_format', DateParser::DATE_FORMAT); + } + + /** + * Get date time format from settings + * + * @access public + * @return string + */ + public function getUserDateTimeFormat() + { + return $this->getUserDateFormat().' '.$this->getUserTimeFormat(); + } + + /** + * Get time format from settings + * + * @access public + * @return string + */ + public function getUserTimeFormat() + { + return $this->configModel->get('application_time_format', DateParser::TIME_FORMAT); + } + + /** + * List of time formats + * + * @access public + * @return string[] + */ + public function getTimeFormats() + { + return array( + 'H:i', + 'g:i a', + ); + } + + /** + * List of date formats + * + * @access public + * @param boolean $iso + * @return string[] + */ + public function getDateFormats($iso = false) + { + $formats = array( + $this->getUserDateFormat(), + ); + + $isoFormats = array( + 'Y-m-d', + 'Y_m_d', + ); + + $userFormats = array( + 'm/d/Y', + 'd/m/Y', + 'Y/m/d', + 'd.m.Y', + ); + + if ($iso) { + $formats = array_merge($formats, $isoFormats, $userFormats); + } else { + $formats = array_merge($formats, $userFormats); + } + + return array_unique($formats); + } + + /** + * List of datetime formats + * + * @access public + * @param boolean $iso + * @return string[] + */ + public function getDateTimeFormats($iso = false) + { + $formats = array( + $this->getUserDateTimeFormat(), + ); + + foreach ($this->getDateFormats($iso) as $date) { + foreach ($this->getTimeFormats() as $time) { + $formats[] = $date.' '.$time; + } + } + + return array_unique($formats); + } + + /** + * List of all date formats + * + * @access public + * @param boolean $iso + * @return string[] + */ + public function getAllDateFormats($iso = false) + { + return array_merge($this->getDateFormats($iso), $this->getDateTimeFormats($iso)); + } + + /** + * Get available formats (visible in settings) + * + * @access public + * @param array $formats + * @return array + */ + public function getAvailableFormats(array $formats) + { + $values = array(); + + foreach ($formats as $format) { + $values[$format] = date($format).' ('.$format.')'; + } + + return $values; + } + + /** + * Get formats for date parsing + * + * @access public + * @return array + */ + public function getParserFormats() + { + return array( + $this->getUserDateFormat(), + 'Y-m-d', + 'Y_m_d', + $this->getUserDateTimeFormat(), + 'Y-m-d H:i', + 'Y_m_d H:i', + ); + } + + /** + * Parse a date and return a unix timestamp, try different date formats + * + * @access public + * @param string $value Date to parse + * @return integer + */ + public function getTimestamp($value) + { + if (ctype_digit((string) $value)) { + return (int) $value; + } + + foreach ($this->getParserFormats() as $format) { + $timestamp = $this->getValidDate($value, $format); + + if ($timestamp !== 0) { + return $timestamp; + } + } + + return 0; + } + + /** + * Return a timestamp if the given date format is correct otherwise return 0 + * + * @access private + * @param string $value Date to parse + * @param string $format Date format + * @return integer + */ + private function getValidDate($value, $format) + { + $date = DateTime::createFromFormat($format, $value); + + if ($date !== false) { + $errors = DateTime::getLastErrors(); + if ($errors === false || + $errors['error_count'] === 0 && $errors['warning_count'] === 0) { + $timestamp = $date->getTimestamp(); + return $timestamp > 0 ? $timestamp : 0; + } + } + + return 0; + } + + /** + * 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 = abs($d1->getTimestamp() - $d2->getTimestamp()); + return round($seconds / 3600, 2); + } + + /** + * Get ISO-8601 date from user input + * + * @access public + * @param string $value Date to parse + * @return string + */ + public function getIsoDate($value) + { + return date('Y-m-d', $this->getTimestamp($value)); + } + + /** + * Get a timestamp from an ISO date format + * + * @access public + * @param string $value + * @return integer + */ + public function getTimestampFromIsoFormat($value) + { + return $this->removeTimeFromTimestamp(ctype_digit((string) $value) ? $value : strtotime($value)); + } + + /** + * Remove the time from a timestamp + * + * @access public + * @param integer $timestamp + * @return integer + */ + public function removeTimeFromTimestamp($timestamp) + { + return mktime(0, 0, 0, date('m', $timestamp), date('d', $timestamp), date('Y', $timestamp)); + } + + /** + * Format date (form display) + * + * @access public + * @param array $values Database values + * @param string[] $fields Date fields + * @param string $format Date format + * @return array + */ + public function format(array $values, array $fields, $format) + { + foreach ($fields as $field) { + if (! empty($values[$field])) { + if (ctype_digit((string) $values[$field])) { + $values[$field] = date($format, $values[$field]); + } + } else { + $values[$field] = ''; + } + } + + return $values; + } + + /** + * Convert date to timestamp + * + * @access public + * @param array $values Database values + * @param string[] $fields Date fields + * @param boolean $keep_time Keep time or not + * @return array + */ + public function convert(array $values, array $fields, $keep_time = false) + { + foreach ($fields as $field) { + if (! empty($values[$field])) { + $timestamp = $this->getTimestamp($values[$field]); + $values[$field] = $keep_time ? $timestamp : $this->removeTimeFromTimestamp($timestamp); + } + } + + return $values; + } +} diff --git a/app/Core/Event/EventManager.php b/app/Core/Event/EventManager.php new file mode 100644 index 0000000..75e8198 --- /dev/null +++ b/app/Core/Event/EventManager.php @@ -0,0 +1,66 @@ +events[$event] = $description; + return $this; + } + + /** + * Get the list of events and description that can be used from the user interface + * + * @access public + * @return array + */ + public function getAll() + { + $events = array( + TaskLinkModel::EVENT_CREATE_UPDATE => t('Task link creation or modification'), + TaskModel::EVENT_MOVE_COLUMN => t('Move a task to another column'), + TaskModel::EVENT_UPDATE => t('Task modification'), + TaskModel::EVENT_CREATE => t('Task creation'), + TaskModel::EVENT_OPEN => t('Reopen a task'), + TaskModel::EVENT_CLOSE => t('Closing a task'), + TaskModel::EVENT_CREATE_UPDATE => t('Task creation or modification'), + TaskModel::EVENT_ASSIGNEE_CHANGE => t('Task assignee change'), + TaskModel::EVENT_DAILY_CRONJOB => t('Daily background job for tasks'), + TaskModel::EVENT_MOVE_SWIMLANE => t('Move a task to another swimlane'), + SubtaskModel::EVENT_CREATE_UPDATE => t('Subtask creation or modification'), + ); + + $events = array_merge($events, $this->events); + asort($events); + + return $events; + } +} diff --git a/app/Core/ExternalLink/ExternalLinkInterface.php b/app/Core/ExternalLink/ExternalLinkInterface.php new file mode 100644 index 0000000..2dbc0a1 --- /dev/null +++ b/app/Core/ExternalLink/ExternalLinkInterface.php @@ -0,0 +1,36 @@ +providers, $provider); + return $this; + } + + /** + * Get provider + * + * @access public + * @param string $type + * @throws ExternalLinkProviderNotFound + * @return ExternalLinkProviderInterface + */ + public function getProvider($type) + { + foreach ($this->providers as $provider) { + if ($provider->getType() === $type) { + return $provider; + } + } + + throw new ExternalLinkProviderNotFound('Unable to find link provider: '.$type); + } + + /** + * Get link types + * + * @access public + * @return array + */ + public function getTypes() + { + $types = array(); + + foreach ($this->providers as $provider) { + $types[$provider->getType()] = $provider->getName(); + } + + asort($types); + + return array(self::TYPE_AUTO => t('Auto')) + $types; + } + + /** + * Get dependency label from a provider + * + * @access public + * @param string $type + * @param string $dependency + * @return string + */ + public function getDependencyLabel($type, $dependency) + { + $provider = $this->getProvider($type); + $dependencies = $provider->getDependencies(); + return isset($dependencies[$dependency]) ? $dependencies[$dependency] : $dependency; + } + + /** + * Find a provider that match + * + * @access public + * @throws ExternalLinkProviderNotFound + * @return ExternalLinkProviderInterface + */ + public function find() + { + if ($this->userInputType === self::TYPE_AUTO) { + $provider = $this->findProvider(); + } else { + $provider = $this->getProvider($this->userInputType); + $provider->setUserTextInput($this->userInputText); + + if (! $provider->match()) { + throw new ExternalLinkProviderNotFound('Unable to parse URL with selected provider'); + } + } + + if ($provider === null) { + throw new ExternalLinkProviderNotFound('Unable to find link information from provided information'); + } + + return $provider; + } + + /** + * Set form values + * + * @access public + * @param array $values + * @return ExternalLinkManager + */ + public function setUserInput(array $values) + { + $this->userInputType = empty($values['type']) ? self::TYPE_AUTO : $values['type']; + $this->userInputText = empty($values['text']) ? '' : trim($values['text']); + return $this; + } + + /** + * Set provider type + * + * @access public + * @param string $userInputType + * @return ExternalLinkManager + */ + public function setUserInputType($userInputType) + { + $this->userInputType = $userInputType; + return $this; + } + + /** + * Set external link + * @param string $userInputText + * @return ExternalLinkManager + */ + public function setUserInputText($userInputText) + { + $this->userInputText = $userInputText; + return $this; + } + + /** + * Find a provider that user input + * + * @access private + * @return ExternalLinkProviderInterface + */ + private function findProvider() + { + foreach ($this->providers as $provider) { + $provider->setUserTextInput($this->userInputText); + + if ($provider->match()) { + return $provider; + } + } + + return null; + } +} diff --git a/app/Core/ExternalLink/ExternalLinkProviderInterface.php b/app/Core/ExternalLink/ExternalLinkProviderInterface.php new file mode 100644 index 0000000..961252e --- /dev/null +++ b/app/Core/ExternalLink/ExternalLinkProviderInterface.php @@ -0,0 +1,71 @@ + 'Related', + * 'child' => 'Child', + * 'parent' => 'Parent', + * 'self' => 'Self', + * ] + * + * The dictionary key is saved in the database. + * + * @access public + * @return array + */ + public function getDependencies(); + + /** + * Set text entered by the user + * + * @access public + * @param string $input + */ + public function setUserTextInput($input); + + /** + * Return true if the provider can parse correctly the user input + * + * @access public + * @return boolean + */ + public function match(); + + /** + * Get the link found with the properties + * + * @access public + * @return ExternalLinkInterface + */ + public function getLink(); +} diff --git a/app/Core/ExternalLink/ExternalLinkProviderNotFound.php b/app/Core/ExternalLink/ExternalLinkProviderNotFound.php new file mode 100644 index 0000000..4fd0520 --- /dev/null +++ b/app/Core/ExternalLink/ExternalLinkProviderNotFound.php @@ -0,0 +1,15 @@ +providers[$externalTaskProvider->getName()] = $externalTaskProvider; + return $this; + } + + /** + * Get task provider + * + * @param string $name + * @return ExternalTaskProviderInterface|null + * @throws ProviderNotFoundException + */ + public function getProvider($name) + { + if (isset($this->providers[$name])) { + return $this->providers[$name]; + } + + throw new ProviderNotFoundException('Unable to load this provider: '.$name); + } + + /** + * Get list of task providers + * + * @return array + */ + public function getProvidersList() + { + $providers = array_keys($this->providers); + + if (count($providers)) { + return array_combine($providers, $providers); + } + + return array(); + } + + /** + * Get all providers + * + * @return ExternalTaskProviderInterface[] + */ + public function getProviders() + { + return $this->providers; + } +} diff --git a/app/Core/ExternalTask/ExternalTaskProviderInterface.php b/app/Core/ExternalTask/ExternalTaskProviderInterface.php new file mode 100644 index 0000000..7a8157f --- /dev/null +++ b/app/Core/ExternalTask/ExternalTaskProviderInterface.php @@ -0,0 +1,94 @@ + 'T_WHITESPACE', + '/^([<=>]{0,2}[0-9]{4}-[0-9]{2}-[0-9]{2})/' => 'T_STRING', + '/^([<=>]{1,2}\w+)/u' => 'T_STRING', + '/^([<=>]{1,2}".+")/' => 'T_STRING', + '/^("(.*?)")/' => 'T_STRING', + '/^(\S+)/u' => 'T_STRING', + '/^(#\d+)/' => 'T_STRING', + ); + + /** + * Default token + * + * @access private + * @var string + */ + private $defaultToken = ''; + + /** + * Add token + * + * @access public + * @param string $regex + * @param string $token + * @return $this + */ + public function addToken($regex, $token) + { + $this->tokenMap = array($regex => $token) + $this->tokenMap; + return $this; + } + + /** + * Set default token + * + * @access public + * @param string $token + * @return $this + */ + public function setDefaultToken($token) + { + $this->defaultToken = $token; + return $this; + } + + /** + * Tokenize input string + * + * @access public + * @param string $input + * @return array + */ + public function tokenize($input) + { + $tokens = array(); + $this->offset = 0; + + if (is_null($input)) { + $input = ""; + } + $input_length = mb_strlen($input, 'UTF-8'); + + while ($this->offset < $input_length) { + $result = $this->match(mb_substr($input, $this->offset, $input_length, 'UTF-8')); + + if ($result === false) { + return array(); + } + + $tokens[] = $result; + } + + return $this->map($tokens); + } + + /** + * Find a token that match and move the offset + * + * @access protected + * @param string $string + * @return array|boolean + */ + protected function match($string) + { + foreach ($this->tokenMap as $pattern => $name) { + if (preg_match($pattern, $string, $matches)) { + $this->offset += mb_strlen($matches[1], 'UTF-8'); + + return array( + 'match' => str_replace('"', '', $matches[1]), + 'token' => $name, + ); + } + } + + return false; + } + + /** + * Build map of tokens and matches + * + * @access protected + * @param array $tokens + * @return array + */ + protected function map(array $tokens) + { + $map = array(); + $leftOver = ''; + + while (false !== ($token = current($tokens))) { + if ($token['token'] === 'T_STRING' || $token['token'] === 'T_WHITESPACE') { + $leftOver .= $token['match']; + } else { + $next = next($tokens); + + if ($next !== false && $next['token'] === 'T_STRING') { + $map[$token['token']][] = $next['match']; + } + } + + next($tokens); + } + + $leftOver = trim($leftOver); + + if ($this->defaultToken !== '' && $leftOver !== '') { + $map[$this->defaultToken] = array($leftOver); + } + + return $map; + } +} diff --git a/app/Core/Filter/LexerBuilder.php b/app/Core/Filter/LexerBuilder.php new file mode 100644 index 0000000..e3ab725 --- /dev/null +++ b/app/Core/Filter/LexerBuilder.php @@ -0,0 +1,151 @@ +lexer = new Lexer(); + $this->queryBuilder = new QueryBuilder(); + } + + /** + * Add a filter + * + * @access public + * @param FilterInterface $filter + * @param bool $default + * @return LexerBuilder + */ + public function withFilter(FilterInterface $filter, $default = false) + { + $attributes = $filter->getAttributes(); + + foreach ($attributes as $attribute) { + $this->filters[$attribute] = $filter; + $this->lexer->addToken(sprintf("/^(%s:)/i", $attribute), $attribute); + + if ($default) { + $this->lexer->setDefaultToken($attribute); + } + } + + return $this; + } + + /** + * Set the query + * + * @access public + * @param Table $query + * @return LexerBuilder + */ + public function withQuery(Table $query) + { + $this->query = $query; + $this->queryBuilder->withQuery($this->query); + return $this; + } + + /** + * Parse the input and build the query + * + * @access public + * @param string $input + * @return QueryBuilder + */ + public function build($input) + { + $tokens = $this->lexer->tokenize($input); + + foreach ($tokens as $token => $values) { + if (isset($this->filters[$token])) { + $this->applyFilters($this->filters[$token], $values); + } + } + + return $this->queryBuilder; + } + + /** + * Apply filters to the query + * + * @access protected + * @param FilterInterface $filter + * @param array $values + */ + protected function applyFilters(FilterInterface $filter, array $values) + { + $len = count($values); + + if ($len > 1) { + $criteria = new OrCriteria(); + $criteria->withQuery($this->query); + + foreach ($values as $value) { + $currentFilter = clone($filter); + $criteria->withFilter($currentFilter->withValue($value)); + } + + $this->queryBuilder->withCriteria($criteria); + } elseif ($len === 1) { + $this->queryBuilder->withFilter($filter->withValue($values[0])); + } + } + + /** + * Clone object with deep copy + */ + public function __clone() + { + $this->lexer = clone $this->lexer; + $this->query = clone $this->query; + $this->queryBuilder = clone $this->queryBuilder; + } +} diff --git a/app/Core/Filter/OrCriteria.php b/app/Core/Filter/OrCriteria.php new file mode 100644 index 0000000..174b845 --- /dev/null +++ b/app/Core/Filter/OrCriteria.php @@ -0,0 +1,68 @@ +query = $query; + return $this; + } + + /** + * Set filter + * + * @access public + * @param FilterInterface $filter + * @return CriteriaInterface + */ + public function withFilter(FilterInterface $filter) + { + $this->filters[] = $filter; + return $this; + } + + /** + * Apply condition + * + * @access public + * @return CriteriaInterface + */ + public function apply() + { + $this->query->beginOr(); + + foreach ($this->filters as $filter) { + $filter->withQuery($this->query)->apply(); + } + + $this->query->closeOr(); + return $this; + } +} diff --git a/app/Core/Filter/QueryBuilder.php b/app/Core/Filter/QueryBuilder.php new file mode 100644 index 0000000..bdd6b94 --- /dev/null +++ b/app/Core/Filter/QueryBuilder.php @@ -0,0 +1,115 @@ +query = $query; + return $this; + } + + /** + * Set a filter + * + * @access public + * @param FilterInterface $filter + * @return QueryBuilder + */ + public function withFilter(FilterInterface $filter) + { + $filter->withQuery($this->query)->apply(); + return $this; + } + + /** + * Set a criteria + * + * @access public + * @param CriteriaInterface $criteria + * @return QueryBuilder + */ + public function withCriteria(CriteriaInterface $criteria) + { + $criteria->withQuery($this->query)->apply(); + return $this; + } + + /** + * Set a formatter + * + * @access public + * @param FormatterInterface $formatter + * @return string|array + */ + public function format(FormatterInterface $formatter) + { + return $formatter->withQuery($this->query)->format(); + } + + /** + * Get the query result as array + * + * @access public + * @return array + */ + public function toArray() + { + return $this->query->findAll(); + } + + /** + * Get Query object + * + * @access public + * @return Table + */ + public function getQuery() + { + return $this->query; + } + + /** + * Clone object with deep copy + */ + public function __clone() + { + $this->query = clone $this->query; + } +} diff --git a/app/Core/Group/GroupBackendProviderInterface.php b/app/Core/Group/GroupBackendProviderInterface.php new file mode 100644 index 0000000..0b6e298 --- /dev/null +++ b/app/Core/Group/GroupBackendProviderInterface.php @@ -0,0 +1,21 @@ +providers[] = $provider; + return $this; + } + + /** + * Find a group from a search query + * + * @access public + * @param string $input + * @return GroupProviderInterface[] + */ + public function find($input) + { + $groups = array(); + + foreach ($this->providers as $provider) { + $groups = array_merge($groups, $provider->find($input)); + } + + return $this->removeDuplicates($groups); + } + + /** + * Remove duplicated groups + * + * @access protected + * @param array $groups + * @return GroupProviderInterface[] + */ + protected function removeDuplicates(array $groups) + { + $result = array(); + + foreach ($groups as $group) { + if (! isset($result[$group->getName()])) { + $result[$group->getName()] = $group; + } + } + + return array_values($result); + } +} diff --git a/app/Core/Group/GroupProviderInterface.php b/app/Core/Group/GroupProviderInterface.php new file mode 100644 index 0000000..f312f89 --- /dev/null +++ b/app/Core/Group/GroupProviderInterface.php @@ -0,0 +1,40 @@ +container = $container; + $this->helpers = new Container; + } + + /** + * Expose helpers with magic getter + * + * @access public + * @param string $helper + * @return mixed + */ + public function __get($helper) + { + return $this->getHelper($helper); + } + + /** + * Allow overriding helpers through magic setter + * + * @access public + * @param string $helper + * @param mixed $instance + */ + public function __set($helper, $instance) + { + $this->helpers[$helper] = $instance; + } + + /** + * Expose helpers with method + * + * @access public + * @param string $helper + * @return mixed + */ + public function getHelper($helper) + { + return $this->helpers[$helper]; + } + + /** + * Register a new Helper + * + * @access public + * @param string $property + * @param string $className + * @return Helper + */ + public function register($property, $className) + { + $container = $this->container; + + $this->helpers[$property] = function () use ($className, $container) { + return new $className($container); + }; + + return $this; + } +} diff --git a/app/Core/Http/Client.php b/app/Core/Http/Client.php new file mode 100644 index 0000000..d860f50 --- /dev/null +++ b/app/Core/Http/Client.php @@ -0,0 +1,451 @@ +doRequest('GET', $url, '', $headers, $raiseForErrors, $followRedirects); + } + + /** + * Send a GET HTTP request and parse JSON response + * + * @access public + * @param string $url + * @param string[] $headers + * @param bool $raiseForErrors + * @param bool $followRedirects + * @return array + */ + public function getJson($url, array $headers = [], $raiseForErrors = false, $followRedirects = true) + { + $response = $this->doRequest('GET', $url, '', array_merge(['Accept: application/json'], $headers), $raiseForErrors, $followRedirects); + return json_decode($response, true) ?: []; + } + + /** + * Send a POST HTTP request encoded in JSON + * + * @access public + * @param string $url + * @param array $data + * @param string[] $headers + * @param bool $raiseForErrors + * @param bool $followRedirects + * @return string + */ + public function postJson($url, array $data, array $headers = [], $raiseForErrors = false, $followRedirects = true) + { + return $this->doRequest( + 'POST', + $url, + json_encode($data), + array_merge(['Content-type: application/json'], $headers), + $raiseForErrors, + $followRedirects + ); + } + + /** + * Send a POST HTTP request encoded in JSON (Fire and forget) + * + * @access public + * @param string $url + * @param array $data + * @param string[] $headers + * @param bool $raiseForErrors + */ + public function postJsonAsync($url, array $data, array $headers = [], $raiseForErrors = false) + { + $this->queueManager->push(HttpAsyncJob::getInstance($this->container)->withParams( + 'POST', + $url, + json_encode($data), + array_merge(['Content-type: application/json'], $headers), + $raiseForErrors + )); + } + + /** + * Send a POST HTTP request encoded in www-form-urlencoded + * + * @access public + * @param string $url + * @param array $data + * @param string[] $headers + * @param bool $raiseForErrors + * @return string + */ + public function postForm($url, array $data, array $headers = [], $raiseForErrors = false) + { + return $this->doRequest( + 'POST', + $url, + http_build_query($data), + array_merge(['Content-type: application/x-www-form-urlencoded'], $headers), + $raiseForErrors + ); + } + + /** + * Send a POST HTTP request encoded in www-form-urlencoded (fire and forget) + * + * @access public + * @param string $url + * @param array $data + * @param string[] $headers + * @param bool $raiseForErrors + */ + public function postFormAsync($url, array $data, array $headers = [], $raiseForErrors = false) + { + $this->queueManager->push(HttpAsyncJob::getInstance($this->container)->withParams( + 'POST', + $url, + http_build_query($data), + array_merge(['Content-type: application/x-www-form-urlencoded'], $headers), + $raiseForErrors + )); + } + + /** + * Make the HTTP request with cURL if detected, socket otherwise + * + * @access public + * @param string $method + * @param string $url + * @param string $content + * @param string[] $headers + * @param bool $raiseForErrors + * @param bool $followRedirects + * @return string + */ + public function doRequest($method, $url, $content, array $headers, $raiseForErrors = false, $followRedirects = true) + { + $requestBody = ''; + + if (! empty($url)) { + if (function_exists('curl_version')) { + if (DEBUG) { + $this->logger->debug('HttpClient::doRequest: cURL detected'); + } + $requestBody = $this->doRequestWithCurl($method, $url, $content, $headers, $raiseForErrors, $followRedirects); + } else { + if (DEBUG) { + $this->logger->debug('HttpClient::doRequest: using socket'); + } + $requestBody = $this->doRequestWithSocket($method, $url, $content, $headers, $raiseForErrors, $followRedirects); + } + } + + return $requestBody; + } + + /** + * Make the HTTP request with socket + * + * @access private + * @param string $method + * @param string $url + * @param string $content + * @param string[] $headers + * @param bool $raiseForErrors + * @param bool $followRedirects + * @return string + */ + private function doRequestWithSocket($method, $url, $content, array $headers, $raiseForErrors = false, $followRedirects = true) + { + $startTime = microtime(true); + $stream = @fopen(trim($url), 'r', false, stream_context_create($this->getContext($method, $content, $headers, $raiseForErrors, $followRedirects))); + + if (! is_resource($stream)) { + $this->logger->error('HttpClient: request failed ('.$url.')'); + + if ($raiseForErrors) { + throw new ClientException('Unreachable URL: '.$url); + } + + return ''; + } + + $body = stream_get_contents($stream); + $metadata = stream_get_meta_data($stream); + + if ($raiseForErrors && array_key_exists('wrapper_data', $metadata)) { + $statusCode = $this->getStatusCode($metadata['wrapper_data']); + + if ($statusCode >= 400) { + throw new InvalidStatusException('Request failed with status code '.$statusCode, $statusCode, $body); + } + } + + if (DEBUG) { + $this->logger->debug('HttpClient: url='.$url); + $this->logger->debug('HttpClient: headers='.var_export($headers, true)); + $this->logger->debug('HttpClient: payload='.$content); + $this->logger->debug('HttpClient: metadata='.var_export($metadata, true)); + $this->logger->debug('HttpClient: body='.$body); + $this->logger->debug('HttpClient: executionTime='.(microtime(true) - $startTime)); + } + + return $body; + } + + + /** + * Make the HTTP request with cURL + * + * @access private + * @param string $method + * @param string $url + * @param string $content + * @param string[] $headers + * @param bool $raiseForErrors + * @param bool $followRedirects + * @return string + */ + private function doRequestWithCurl($method, $url, $content, array $headers, $raiseForErrors = false, $followRedirects = true) + { + $startTime = microtime(true); + $curlSession = @curl_init(); + + curl_setopt($curlSession, CURLOPT_URL, trim($url)); + curl_setopt($curlSession, CURLOPT_USERAGENT, self::HTTP_USER_AGENT); + curl_setopt($curlSession, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_setopt($curlSession, CURLOPT_TIMEOUT, HTTP_TIMEOUT); + curl_setopt($curlSession, CURLOPT_FORBID_REUSE, true); + curl_setopt($curlSession, CURLOPT_MAXREDIRS, $followRedirects ? HTTP_MAX_REDIRECTS : 0); + curl_setopt($curlSession, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curlSession, CURLOPT_FOLLOWLOCATION, $followRedirects); + + if ('POST' === $method) { + curl_setopt($curlSession, CURLOPT_POST, true); + curl_setopt($curlSession, CURLOPT_POSTFIELDS, $content); + } elseif ('PUT' === $method) { + curl_setopt($curlSession, CURLOPT_CUSTOMREQUEST, 'PUT'); + curl_setopt($curlSession, CURLOPT_POST, true); + curl_setopt($curlSession, CURLOPT_POSTFIELDS, $content); + } + + if (! empty($headers)) { + curl_setopt($curlSession, CURLOPT_HTTPHEADER, $headers); + } + + if (HTTP_VERIFY_SSL_CERTIFICATE === false) { + curl_setopt($curlSession, CURLOPT_SSL_VERIFYHOST, 0); + curl_setopt($curlSession, CURLOPT_SSL_VERIFYPEER, false); + } + + if (HTTP_PROXY_HOSTNAME) { + curl_setopt($curlSession, CURLOPT_PROXY, HTTP_PROXY_HOSTNAME); + curl_setopt($curlSession, CURLOPT_PROXYPORT, HTTP_PROXY_PORT); + curl_setopt($curlSession, CURLOPT_NOPROXY, HTTP_PROXY_EXCLUDE); + } + + if (HTTP_PROXY_USERNAME) { + curl_setopt($curlSession, CURLOPT_PROXYAUTH, CURLAUTH_BASIC); + curl_setopt($curlSession, CURLOPT_PROXYUSERPWD, HTTP_PROXY_USERNAME.':'.HTTP_PROXY_PASSWORD); + } + + $body = curl_exec($curlSession); + + if ($body === false) { + $errorMsg = curl_error($curlSession); + curl_close($curlSession); + + $this->logger->error('HttpClient: request failed ('.$url.' - '.$errorMsg.')'); + + if ($raiseForErrors) { + throw new ClientException('Unreachable URL: '.$url.' ('.$errorMsg.')'); + } + + return ''; + } + + if ($raiseForErrors) { + $statusCode = curl_getinfo($curlSession, CURLINFO_RESPONSE_CODE); + + if ($statusCode >= 400) { + curl_close($curlSession); + throw new InvalidStatusException('Request failed with status code '.$statusCode, $statusCode, $body); + } + } + + if (DEBUG) { + $this->logger->debug('HttpClient: url='.$url); + $this->logger->debug('HttpClient: headers='.var_export($headers, true)); + $this->logger->debug('HttpClient: payload='.$content); + $this->logger->debug('HttpClient: metadata='.var_export(curl_getinfo($curlSession), true)); + $this->logger->debug('HttpClient: body='.$body); + $this->logger->debug('HttpClient: executionTime='.(microtime(true) - $startTime)); + } + + curl_close($curlSession); + return $body; + } + + /** + * Get stream context + * + * @access private + * @param string $method + * @param string $content + * @param string[] $headers + * @param bool $raiseForErrors + * @param bool $followRedirects + * @return array + */ + private function getContext($method, $content, array $headers, $raiseForErrors = false, $followRedirects = true) + { + $default_headers = [ + 'User-Agent: '.self::HTTP_USER_AGENT, + 'Connection: close', + ]; + + if (HTTP_PROXY_USERNAME) { + $default_headers[] = 'Proxy-Authorization: Basic '.base64_encode(HTTP_PROXY_USERNAME.':'.HTTP_PROXY_PASSWORD); + } + + $headers = array_merge($default_headers, $headers); + + $context = [ + 'http' => [ + 'method' => $method, + 'protocol_version' => 1.1, + 'timeout' => HTTP_TIMEOUT, + 'max_redirects' => $followRedirects ? HTTP_MAX_REDIRECTS : 0, + 'follow_location' => $followRedirects ? 1 : 0, + 'header' => implode("\r\n", $headers), + 'content' => $content, + 'ignore_errors' => $raiseForErrors, + ] + ]; + + if (HTTP_PROXY_HOSTNAME) { + $context['http']['proxy'] = 'tcp://'.HTTP_PROXY_HOSTNAME.':'.HTTP_PROXY_PORT; + $context['http']['request_fulluri'] = true; + } + + if (HTTP_VERIFY_SSL_CERTIFICATE === false) { + $context['ssl'] = [ + 'verify_peer' => false, + 'verify_peer_name' => false, + 'allow_self_signed' => true, + ]; + } + + return $context; + } + + private function getStatusCode(array $lines) + { + $status = 200; + + foreach ($lines as $line) { + if (strpos($line, 'HTTP/1') === 0) { + $status = (int) substr($line, 9, 3); + } + } + + return $status; + } + + /** + * Get backend used for making HTTP connections + * + * @access public + * @return string + */ + public static function backend() + { + return function_exists('curl_version') ? 'cURL' : 'socket'; + } + + /** + * Check if an IP address is private + * + * @access public + * @param string $ip + * @return bool + */ + public function isPrivateIpAddress($ip) + { + if (filter_var($ip, FILTER_VALIDATE_IP) === false) { + return false; + } + + return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false; + } + + /** + * Check if a URL is private (RFC1918, localhost, etc.) + * + * @access public + * @param string $url + * @return bool + */ + public function isPrivateURL($url) + { + $parsedUrl = parse_url($url); + + if (!isset($parsedUrl['scheme']) || !in_array(strtolower($parsedUrl['scheme']), ['http', 'https'], true)) { + return false; + } + + if (!isset($parsedUrl['host'])) { + return false; + } + + $host = trim($parsedUrl['host']); + if ($host === '') { + return false; + } + + $ipv4Address = gethostbyname($host); + if ($this->isPrivateIpAddress($ipv4Address)) { + return true; + } + + if (function_exists('dns_get_record')) { + $dnsRecords = @dns_get_record($host, DNS_AAAA); + if (is_array($dnsRecords)) { + foreach ($dnsRecords as $record) { + if (isset($record['type']) && $record['type'] === 'AAAA' && isset($record['ipv6'])) { + if ($this->isPrivateIpAddress($record['ipv6'])) { + return true; + } + } + } + } + } + + return false; + } +} diff --git a/app/Core/Http/ClientException.php b/app/Core/Http/ClientException.php new file mode 100644 index 0000000..5eb783f --- /dev/null +++ b/app/Core/Http/ClientException.php @@ -0,0 +1,9 @@ +statusCode = $statusCode; + $this->body = $body; + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function getBody() + { + return $this->body; + } +} diff --git a/app/Core/Http/OAuth2.php b/app/Core/Http/OAuth2.php new file mode 100644 index 0000000..f47927e --- /dev/null +++ b/app/Core/Http/OAuth2.php @@ -0,0 +1,151 @@ +clientId = $clientId; + $this->secret = $secret; + $this->callbackUrl = $callbackUrl; + $this->authUrl = $authUrl; + $this->tokenUrl = $tokenUrl; + $this->scopes = $scopes; + + return $this; + } + + /** + * Generate OAuth2 state and return the token value + * + * @access public + * @return string + */ + public function getState() + { + if (! session_exists('oauthState')) { + session_set('oauthState', $this->token->getToken()); + } + + return session_get('oauthState'); + } + + /** + * Check the validity of the state (CSRF token) + * + * @access public + * @param string $state + * @return bool + */ + public function isValidateState($state) + { + return $state === $this->getState(); + } + + /** + * Get authorization url + * + * @access public + * @return string + */ + public function getAuthorizationUrl() + { + $params = array( + 'response_type' => 'code', + 'client_id' => $this->clientId, + 'redirect_uri' => $this->callbackUrl, + 'scope' => implode(' ', $this->scopes), + 'state' => $this->getState(), + ); + + return $this->authUrl.'?'.http_build_query($params); + } + + /** + * Get authorization header + * + * @access public + * @return string + */ + public function getAuthorizationHeader() + { + if (strtolower($this->tokenType) === 'bearer') { + return 'Authorization: Bearer '.$this->accessToken; + } + + return ''; + } + + /** + * Get access token + * + * @access public + * @param string $code + * @return string + */ + public function getAccessToken($code) + { + if (empty($this->accessToken) && ! empty($code)) { + $params = array( + 'code' => $code, + 'client_id' => $this->clientId, + 'client_secret' => $this->secret, + 'redirect_uri' => $this->callbackUrl, + 'grant_type' => 'authorization_code', + 'state' => $this->getState(), + ); + + $response = json_decode($this->httpClient->postForm($this->tokenUrl, $params, array('Accept: application/json')), true); + + $this->tokenType = isset($response['token_type']) ? $response['token_type'] : ''; + $this->accessToken = isset($response['access_token']) ? $response['access_token'] : ''; + } + + return $this->accessToken; + } + + /** + * Set access token + * + * @access public + * @param string $token + * @param string $type + * @return $this + */ + public function setAccessToken($token, $type = 'bearer') + { + $this->accessToken = $token; + $this->tokenType = $type; + return $this; + } +} diff --git a/app/Core/Http/RememberMeCookie.php b/app/Core/Http/RememberMeCookie.php new file mode 100644 index 0000000..4b409f5 --- /dev/null +++ b/app/Core/Http/RememberMeCookie.php @@ -0,0 +1,120 @@ + $token, + 'sequence' => $sequence, + ); + } + + /** + * Return true if the current user has a RememberMe cookie + * + * @access public + * @return bool + */ + public function hasCookie() + { + return $this->request->getCookie(self::COOKIE_NAME) !== ''; + } + + /** + * Write and encode the cookie + * + * @access public + * @param string $token Session token + * @param string $sequence Sequence token + * @param string $expiration Cookie expiration + * @return boolean + */ + public function write($token, $sequence, $expiration) + { + return setcookie( + self::COOKIE_NAME, + $this->encode($token, $sequence), + $expiration, + $this->helper->url->dir(), + '', + $this->request->isHTTPS(), + true + ); + } + + /** + * Read and decode the cookie + * + * @access public + * @return mixed + */ + public function read() + { + $cookie = $this->request->getCookie(self::COOKIE_NAME); + + if (empty($cookie)) { + return false; + } + + return $this->decode($cookie); + } + + /** + * Remove the cookie + * + * @access public + * @return boolean + */ + public function remove() + { + return setcookie( + self::COOKIE_NAME, + '', + time() - 3600, + $this->helper->url->dir(), + '', + $this->request->isHTTPS(), + true + ); + } +} diff --git a/app/Core/Http/Request.php b/app/Core/Http/Request.php new file mode 100644 index 0000000..ef86abc --- /dev/null +++ b/app/Core/Http/Request.php @@ -0,0 +1,596 @@ +server = empty($server) ? $_SERVER : $server; + $this->get = empty($get) ? $_GET : $get; + $this->post = empty($post) ? $_POST : $post; + $this->files = empty($files) ? $_FILES : $files; + $this->cookies = empty($cookies) ? $_COOKIE : $cookies; + } + + /** + * Set GET parameters + * + * @param array $params + */ + public function setParams(array $params) + { + $this->get = array_merge($this->get, $params); + } + + /** + * Get query string string parameter + * + * @access public + * @param string $name Parameter name + * @param string $default_value Default value + * @return string + */ + public function getStringParam($name, $default_value = '') + { + return isset($this->get[$name]) ? $this->get[$name] : $default_value; + } + + /** + * Get query string integer parameter + * + * @access public + * @param string $name Parameter name + * @param integer $default_value Default value + * @return integer + */ + public function getIntegerParam($name, $default_value = 0) + { + return isset($this->get[$name]) && ctype_digit((string) $this->get[$name]) ? (int) $this->get[$name] : $default_value; + } + + /** + * Get a form value + * + * @access public + * @param string $name Form field name + * @return string|null + */ + public function getValue($name) + { + $values = $this->getValues(); + return isset($values[$name]) ? $values[$name] : null; + } + + /** + * Get form values and check for CSRF token + * + * @access public + * @return array + */ + public function getValues() + { + if (! empty($this->post) && ! empty($this->post['csrf_token']) && $this->token->validateCSRFToken($this->post['csrf_token'])) { + unset($this->post['csrf_token']); + return $this->filterValues($this->post); + } + + return array(); + } + + /** + * Get POST values without modification + * + * @return array + */ + public function getRawFormValues() + { + return $this->post; + } + + /** + * Get POST value without modification + * + * @param $name + * @return mixed|null + */ + public function getRawValue($name) + { + return isset($this->post[$name]) ? $this->post[$name] : null; + } + + /** + * Get the raw body of the HTTP request + * + * @access public + * @return string + */ + public function getBody() + { + return file_get_contents('php://input'); + } + + /** + * Get the Json request body + * + * @access public + * @param bool $enforceContentType + * @return array + */ + public function getJson($enforceContentType = true) + { + if ($enforceContentType && ! $this->isJsonContentType()) { + return array(); + } + + return json_decode($this->getBody(), true) ?: array(); + } + + /** + * Get the content of an uploaded file + * + * @access public + * @param string $name Form file name + * @return string + */ + public function getFileContent($name) + { + if (isset($this->files[$name]['tmp_name'])) { + return file_get_contents($this->files[$name]['tmp_name']); + } + + return ''; + } + + /** + * Get the path of an uploaded file + * + * @access public + * @param string $name Form file name + * @return string + */ + public function getFilePath($name) + { + return isset($this->files[$name]['tmp_name']) ? $this->files[$name]['tmp_name'] : ''; + } + + /** + * Get info of an uploaded file + * + * @access public + * @param string $name Form file name + * @return array + */ + public function getFileInfo($name) + { + return isset($this->files[$name]) ? $this->files[$name] : array(); + } + + /** + * Return HTTP method + * + * @access public + * @return bool + */ + public function getMethod() + { + return $this->getServerVariable('REQUEST_METHOD'); + } + + /** + * Return true if the HTTP request is sent with the POST method + * + * @access public + * @return bool + */ + public function isPost() + { + return $this->getServerVariable('REQUEST_METHOD') === 'POST'; + } + + /** + * Return true if the HTTP request is an Ajax request + * + * @access public + * @return bool + */ + public function isAjax() + { + return $this->getHeader('X-Requested-With') === 'XMLHttpRequest'; + } + + /** + * Check if the request Content-Type is JSON + * + * @access public + * @return bool + */ + public function isJsonContentType() + { + $contentType = $this->getServerVariable('CONTENT_TYPE'); + + if ($contentType === '') { + $contentType = $this->getServerVariable('HTTP_CONTENT_TYPE'); + } + + return stripos($contentType, 'application/json') === 0; + } + + /** + * Check if the page is requested through HTTPS + * + * Note: IIS return the value 'off' and other web servers an empty value when it's not HTTPS + * + * @access public + * @return boolean + */ + public function isHTTPS() + { + if ($this->getServerVariable('HTTP_X_FORWARDED_PROTO') === 'https') { + return true; + } + + return $this->getServerVariable('HTTPS') !== '' && $this->server['HTTPS'] !== 'off'; + } + + /** + * Get cookie value + * + * @access public + * @param string $name + * @return string + */ + public function getCookie($name) + { + return isset($this->cookies[$name]) ? $this->cookies[$name] : ''; + } + + /** + * Return a HTTP header value + * + * @access public + * @param string $name Header name + * @return string + */ + public function getHeader($name) + { + $name = 'HTTP_'.str_replace('-', '_', strtoupper($name)); + return $this->getServerVariable($name); + } + + /** + * Get remote user + * + * @access public + * @param array $trustedProxyNetworks + * @return string + */ + public function getRemoteUser(array $trustedProxyNetworks = []) + { + if (! $this->isTrustedProxy($trustedProxyNetworks)) { + return ''; + } + return $this->getServerVariable(REVERSE_PROXY_USER_HEADER); + } + + /** + * Get remote email + * + * @access public + * @param array $trustedProxyNetworks + * @return string + */ + public function getRemoteEmail(array $trustedProxyNetworks = []) + { + if (! $this->isTrustedProxy($trustedProxyNetworks)) { + return ''; + } + return $this->getServerVariable(REVERSE_PROXY_EMAIL_HEADER); + } + + /** + * Get remote user full name + * + * @access public + * @param array $trustedProxyNetworks + * @return string + */ + public function getRemoteName(array $trustedProxyNetworks = []) + { + if (! $this->isTrustedProxy($trustedProxyNetworks)) { + return ''; + } + return $this->getServerVariable(REVERSE_PROXY_FULLNAME_HEADER); + } + + /** + * Returns query string + * + * @access public + * @return string + */ + public function getQueryString() + { + return $this->getServerVariable('QUERY_STRING'); + } + + /** + * Return URI + * + * @access public + * @return string + */ + public function getUri() + { + return $this->getServerVariable('REQUEST_URI'); + } + + /** + * Check if a redirect URI is safe (relative path) + * + * @access public + * @param string $uri Redirect URI + * @return bool + */ + public function isSafeRedirectUri($uri) + { + $uri = trim($uri); + if ($uri === '') { + return false; + } + + // Reject backslashes + if (str_contains($uri, '\\')) { + return false; + } + + // Reject if it starts with // (protocol-relative) + if (str_starts_with($uri, '//')) { + return false; + } + + // Reject if it does not start with a slash (relative path) + if (! str_starts_with($uri, '/')) { + return false; + } + + $parsedUrl = parse_url($uri); + if ($parsedUrl === false) { + return false; + } + + // Reject if it contains a scheme or host (partial or full URL) + if (isset($parsedUrl['scheme']) || isset($parsedUrl['host'])) { + return false; + } + + return true; + } + + /** + * Get the user agent + * + * @access public + * @return string + */ + public function getUserAgent() + { + return empty($this->server['HTTP_USER_AGENT']) ? t('Unknown') : $this->server['HTTP_USER_AGENT']; + } + + /** + * Get the client IP address + * + * It returns the proxy IP address if the request is sent through a reverse proxy or the direct client IP address otherwise. + * + * @access public + * @return string + */ + public function getClientIpAddress() + { + return $this->getServerVariable('REMOTE_ADDR'); + } + + /** + * Get the real user IP address considering trusted proxy headers and networks + * + * @access public + * @param array $trustedProxyHeaders List of trusted proxy headers (default: TRUSTED_PROXY_HEADERS constant) + * @param array $trustedProxyNetworks List of trusted proxy networks (default: TRUSTED_PROXY_NETWORKS constant) + * @return string + */ + public function getIpAddress(array $trustedProxyHeaders = [], array $trustedProxyNetworks = []) + { + $trustedProxyHeaders = array_filter(array_map('trim', $trustedProxyHeaders ?: explode(',', TRUSTED_PROXY_HEADERS))); + $useProxyHeaders = ! empty($trustedProxyHeaders) && $this->isTrustedProxy($trustedProxyNetworks); + $keys = $useProxyHeaders ? $trustedProxyHeaders : []; + + foreach ($keys as $key) { + if ($this->getServerVariable($key) !== '') { + foreach (explode(',', $this->server[$key]) as $ipAddress) { + $ipAddress = trim($ipAddress); + if (filter_var($ipAddress, FILTER_VALIDATE_IP)) { + return $ipAddress; + } + } + } + } + + return $this->getClientIpAddress(); + } + + /** + * Get start time + * + * @access public + * @return float + */ + public function getStartTime() + { + return $this->getServerVariable('REQUEST_TIME_FLOAT') ?: 0; + } + + /** + * Get server variable + * + * @access public + * @param string $variable + * @return string + */ + public function getServerVariable($variable) + { + return isset($this->server[$variable]) ? $this->server[$variable] : ''; + } + + protected function filterValues(array $values) + { + foreach ($values as $key => $value) { + + // IE11 Workaround when submitting multipart/form-data + if (strpos($key, '-----------------------------') === 0) { + unset($values[$key]); + } + } + + return $values; + } + + /** + * Check if an IP address belongs to a trusted proxy network + * + * @access public + * @param array $trustedProxyNetworks + * @return bool + */ + public function isTrustedProxy(array $trustedProxyNetworks = []) + { + $ipAddress = $this->getClientIpAddress(); + if ($ipAddress === '') { + return false; + } + + $trustedProxyNetworks = array_filter(array_map('trim', $trustedProxyNetworks ?: explode(',', TRUSTED_PROXY_NETWORKS))); + if (empty($trustedProxyNetworks)) { + return false; + } + + $this->logger->debug('Checking if IP address {ip} belongs to trusted proxy networks: {networks}', ['ip' => $ipAddress, 'networks' => implode(', ', $trustedProxyNetworks)]); + + return $this->isIpInNetworks($ipAddress, $trustedProxyNetworks); + } + + /** + * Check if an IP belongs to any of the provided networks + * + * @access protected + * @param string $ipAddress + * @param array $networks + * @return bool + */ + protected function isIpInNetworks($ipAddress, array $networks) + { + if (! filter_var($ipAddress, FILTER_VALIDATE_IP)) { + return false; + } + + $ipBinary = inet_pton($ipAddress); + + foreach ($networks as $network) { + if ($network === '') { + continue; + } + + $mask = null; + if (strpos($network, '/') !== false) { + list($networkAddress, $mask) = explode('/', $network, 2); + } else { + $networkAddress = $network; + } + + if (! filter_var($networkAddress, FILTER_VALIDATE_IP)) { + continue; + } + + $networkBinary = inet_pton($networkAddress); + + if ($networkBinary === false || strlen($networkBinary) !== strlen($ipBinary)) { + continue; + } + + $maxMask = strlen($networkBinary) * 8; + $mask = ($mask === null || $mask === '') ? $maxMask : max(0, min((int) $mask, $maxMask)); + + if ($this->ipMatchesNetwork($ipBinary, $networkBinary, $mask)) { + return true; + } + } + + return false; + } + + /** + * Perform a binary comparison between an IP and a network mask + * + * @access protected + * @param string $ipBinary + * @param string $networkBinary + * @param int $mask + * @return bool + */ + protected function ipMatchesNetwork($ipBinary, $networkBinary, $mask) + { + if ($mask === 0) { + return true; + } + + $bytes = (int) floor($mask / 8); + $bits = $mask % 8; + + if ($bytes > 0 && strncmp($ipBinary, $networkBinary, $bytes) !== 0) { + return false; + } + + if ($bits === 0) { + return true; + } + + $maskByte = ~((1 << (8 - $bits)) - 1) & 0xFF; + $ipByte = ord($ipBinary[$bytes]); + $networkByte = ord($networkBinary[$bytes]); + + return ($ipByte & $maskByte) === ($networkByte & $maskByte); + } +} diff --git a/app/Core/Http/Response.php b/app/Core/Http/Response.php new file mode 100644 index 0000000..c4323bf --- /dev/null +++ b/app/Core/Http/Response.php @@ -0,0 +1,419 @@ +responseSent; + } + + /** + * Set HTTP status code + * + * @access public + * @param integer $statusCode + * @return $this + */ + public function withStatusCode($statusCode) + { + $this->httpStatusCode = $statusCode; + return $this; + } + + /** + * Set HTTP header + * + * @access public + * @param string $header + * @param string $value + * @return $this + */ + public function withHeader($header, $value) + { + $this->httpHeaders[$header] = $value; + return $this; + } + + /** + * Set content type header + * + * @access public + * @param string $value + * @return $this + */ + public function withContentType($value) + { + $this->httpHeaders['Content-Type'] = $value; + return $this; + } + + /** + * Set default security headers + * + * @access public + * @return $this + */ + public function withSecurityHeaders() + { + $this->httpHeaders['X-Content-Type-Options'] = 'nosniff'; + $this->httpHeaders['X-XSS-Protection'] = '1; mode=block'; + return $this; + } + + /** + * Set header Content-Security-Policy + * + * @access public + * @param array $policies + * @return $this + */ + public function withContentSecurityPolicy(array $policies = array()) + { + $values = ''; + + foreach ($policies as $policy => $acl) { + $values .= $policy.' '.trim($acl).'; '; + } + + $this->withHeader('Content-Security-Policy', $values); + return $this; + } + + /** + * Set header X-Frame-Options + * + * @access public + * @return $this + */ + public function withXframe() + { + $this->withHeader('X-Frame-Options', 'DENY'); + return $this; + } + + /** + * Set header Strict-Transport-Security (only if we use HTTPS) + * + * @access public + * @return $this + */ + public function withStrictTransportSecurity() + { + if ($this->request->isHTTPS()) { + $this->withHeader('Strict-Transport-Security', 'max-age=31536000'); + } + + return $this; + } + + /** + * Add P3P headers for Internet Explorer + * + * @access public + * @return $this + */ + public function withP3P() + { + $this->withHeader('P3P', 'CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"'); + return $this; + } + + /** + * Set HTTP response body + * + * @access public + * @param string $body + * @return $this + */ + public function withBody($body) + { + $this->httpBody = $body; + return $this; + } + + /** + * Send headers to cache a resource + * + * @access public + * @param integer $duration + * @param string $etag + * @return $this + */ + public function withCache($duration, $etag = '') + { + $this + ->withHeader('Pragma', 'cache') + ->withHeader('Expires', gmdate('D, d M Y H:i:s', time() + $duration) . ' GMT') + ->withHeader('Cache-Control', 'public, max-age=' . $duration) + ; + + if ($etag) { + $this->withHeader('ETag', '"' . $etag . '"'); + } + + return $this; + } + + /** + * Send no cache headers + * + * @access public + * @return $this + */ + public function withoutCache() + { + $this->withHeader('Pragma', 'no-cache'); + $this->withHeader('Expires', 'Sat, 26 Jul 1997 05:00:00 GMT'); + return $this; + } + + /** + * Force the browser to download an attachment + * + * @access public + * @param string $filename + * @return $this + */ + public function withFileDownload($filename) + { + $this->withHeader('Content-Disposition', 'attachment; filename="'.$filename.'"'); + $this->withHeader('Content-Transfer-Encoding', 'binary'); + $this->withHeader('Content-Type', 'application/octet-stream'); + return $this; + } + + /** + * Send headers and body + * + * @access public + */ + public function send() + { + $this->responseSent = true; + + if ($this->httpStatusCode !== 200) { + header('Status: '.$this->httpStatusCode); + header($this->request->getServerVariable('SERVER_PROTOCOL').' '.$this->httpStatusCode); + } + + foreach ($this->httpHeaders as $header => $value) { + header($header.': '.$value); + } + + if (! empty($this->httpBody)) { + echo $this->httpBody; + } + } + + /** + * Send a custom HTTP status code + * + * @access public + * @param integer $statusCode + */ + public function status($statusCode) + { + $this->withStatusCode($statusCode); + $this->send(); + } + + /** + * Redirect to another URL + * + * @access public + * @param string $url Redirection URL + * @param boolean $self If Ajax request and true: refresh the current page + */ + public function redirect($url, $self = false) + { + if ($this->request->isAjax()) { + $this->withHeader('X-Ajax-Redirect', $self ? 'self' : $url); + } else { + $this->withHeader('Location', $url); + } + + $this->send(); + } + + /** + * Send a HTML response + * + * @access public + * @param string $data + * @param integer $statusCode + */ + public function html($data, $statusCode = 200) + { + $this->withStatusCode($statusCode); + $this->withContentType('text/html; charset=utf-8'); + $this->withBody($data); + $this->send(); + } + + /** + * Send a text response + * + * @access public + * @param string $data + * @param integer $statusCode + */ + public function text($data, $statusCode = 200) + { + $this->withStatusCode($statusCode); + $this->withContentType('text/plain; charset=utf-8'); + $this->withBody($data); + $this->send(); + } + + /** + * Send a CSV response + * + * @access public + * @param array $data Data to serialize in csv + * @param bool $addBOM Add BOM header + */ + public function csv(array $data, $addBOM = false) + { + $this->withoutCache(); + $this->withContentType('text/csv; charset=utf-8'); + $this->send(); + Csv::output($data, $addBOM); + } + + /** + * Send a Json response + * + * @access public + * @param array $data Data to serialize in json + * @param integer $statusCode HTTP status code + */ + public function json(array $data, $statusCode = 200) + { + $this->withStatusCode($statusCode); + $this->withContentType('application/json'); + $this->withoutCache(); + $this->withBody(json_encode($data)); + $this->send(); + } + + /** + * Send a XML response + * + * @access public + * @param string $data + * @param integer $statusCode + */ + public function xml($data, $statusCode = 200) + { + $this->withStatusCode($statusCode); + $this->withContentType('text/xml; charset=utf-8'); + $this->withoutCache(); + $this->withBody($data); + $this->send(); + } + + /** + * Send a javascript response + * + * @access public + * @param string $data + * @param integer $statusCode + */ + public function js($data, $statusCode = 200) + { + $this->withStatusCode($statusCode); + $this->withContentType('text/javascript; charset=utf-8'); + $this->withBody($data); + $this->send(); + } + + /** + * Send a css response + * + * @access public + * @param string $data + * @param integer $statusCode + */ + public function css($data, $statusCode = 200) + { + $this->withStatusCode($statusCode); + $this->withContentType('text/css; charset=utf-8'); + $this->withBody($data); + $this->send(); + } + + /** + * Send a binary response + * + * @access public + * @param string $data + * @param integer $statusCode + */ + public function binary($data, $statusCode = 200) + { + $this->withStatusCode($statusCode); + $this->withoutCache(); + $this->withHeader('Content-Transfer-Encoding', 'binary'); + $this->withContentType('application/octet-stream'); + $this->withBody($data); + $this->send(); + } + + /** + * Send a iCal response + * + * @access public + * @param string $data + * @param integer $statusCode + */ + public function ical($data, $statusCode = 200) + { + $this->withStatusCode($statusCode); + $this->withContentType('text/calendar; charset=utf-8'); + $this->withBody($data); + $this->send(); + } + + /** + * Send a PDF response + * + * @access public + * @param string $data + * @param integer $statusCode + * @param string $fileName + */ + public function pdf($data, int $statusCode = 200, string $fileName = "") + { + $this->withStatusCode($statusCode); + $this->withContentType('application/pdf'); + + if (!empty($fileName)) { + $this->httpHeaders["content-disposition"] = "attachment; filename=".$fileName; + } + + $this->withBody($data); + $this->send(); + } +} diff --git a/app/Core/Http/Route.php b/app/Core/Http/Route.php new file mode 100644 index 0000000..c28087d --- /dev/null +++ b/app/Core/Http/Route.php @@ -0,0 +1,188 @@ +activated = true; + return $this; + } + + /** + * Add route + * + * @access public + * @param string $path + * @param string $controller + * @param string $action + * @param string $plugin + * @return Route + */ + public function addRoute($path, $controller, $action, $plugin = '') + { + if ($this->activated) { + $path = ltrim($path, '/'); + $items = explode('/', $path); + $params = $this->findParams($items); + + $this->paths[] = array( + 'items' => $items, + 'count' => count($items), + 'controller' => $controller, + 'action' => $action, + 'plugin' => $plugin, + ); + + $this->urls[$plugin][$controller][$action][] = array( + 'path' => $path, + 'params' => $params, + 'count' => count($params), + ); + } + + return $this; + } + + /** + * Find a route according to the given path + * + * @access public + * @param string $path + * @return array + */ + public function findRoute($path) + { + $items = explode('/', ltrim($path, '/')); + $count = count($items); + + foreach ($this->paths as $route) { + if ($count === $route['count']) { + $params = array(); + + for ($i = 0; $i < $count; $i++) { + if ($route['items'][$i][0] === ':') { + $params[substr($route['items'][$i], 1)] = urldecode($items[$i]); + } elseif ($route['items'][$i] !== $items[$i]) { + break; + } + } + + if ($i === $count) { + $this->request->setParams($params); + return array( + 'controller' => $route['controller'], + 'action' => $route['action'], + 'plugin' => $route['plugin'], + ); + } + } + } + + return array( + 'controller' => 'DashboardController', + 'action' => 'show', + 'plugin' => '', + ); + } + + /** + * Find route url + * + * @access public + * @param string $controller + * @param string $action + * @param array $params + * @param string $plugin + * @return string + */ + public function findUrl($controller, $action, array $params = array(), $plugin = '') + { + if ($plugin === '' && isset($params['plugin'])) { + $plugin = $params['plugin']; + unset($params['plugin']); + } + + if (! isset($this->urls[$plugin][$controller][$action])) { + return ''; + } + + foreach ($this->urls[$plugin][$controller][$action] as $route) { + if (array_diff_key($params, $route['params']) === array()) { + $url = $route['path']; + $i = 0; + + foreach ($params as $variable => $value) { + $value = urlencode($value); + $url = str_replace(':'.$variable, $value, $url); + $i++; + } + + if ($i === $route['count']) { + return $url; + } + } + } + + return ''; + } + + /** + * Find url params + * + * @access public + * @param array $items + * @return array + */ + public function findParams(array $items) + { + $params = array(); + + foreach ($items as $item) { + if ($item !== '' && $item[0] === ':') { + $params[substr($item, 1)] = true; + } + } + + return $params; + } +} diff --git a/app/Core/Http/Router.php b/app/Core/Http/Router.php new file mode 100644 index 0000000..530b46a --- /dev/null +++ b/app/Core/Http/Router.php @@ -0,0 +1,131 @@ +currentPluginName; + } + + /** + * Get controller + * + * @access public + * @return string + */ + public function getController() + { + return $this->currentControllerName; + } + + /** + * Get action + * + * @access public + * @return string + */ + public function getAction() + { + return $this->currentActionName; + } + + /** + * Get the path to compare patterns + * + * @access public + * @return string + */ + public function getPath() + { + $path = substr($this->request->getUri(), strlen($this->helper->url->dir())); + + if ($this->request->getQueryString() !== '') { + $path = substr($path, 0, - strlen($this->request->getQueryString()) - 1); + } + + if ($path !== '' && $path[0] === '/') { + $path = substr($path, 1); + } + + return $path; + } + + /** + * Find controller/action from the route table or from get arguments + * + * @access public + */ + public function dispatch() + { + $controller = $this->request->getStringParam('controller'); + $action = $this->request->getStringParam('action'); + $plugin = $this->request->getStringParam('plugin'); + + if ($controller === '') { + $route = $this->route->findRoute($this->getPath()); + $controller = $route['controller']; + $action = $route['action']; + $plugin = $route['plugin']; + } + + $this->currentControllerName = ucfirst($this->sanitize($controller, self::DEFAULT_CONTROLLER)); + $this->currentActionName = $this->sanitize($action, self::DEFAULT_METHOD); + $this->currentPluginName = ucfirst($this->sanitize($plugin)); + } + + /** + * Check controller and action parameter + * + * @access public + * @param string $value + * @param string $default + * @return string + */ + public function sanitize($value, $default = '') + { + return preg_match('/^[a-zA-Z_0-9]+$/', $value) ? $value : $default; + } +} diff --git a/app/Core/Ldap/Client.php b/app/Core/Ldap/Client.php new file mode 100644 index 0000000..e60b21b --- /dev/null +++ b/app/Core/Ldap/Client.php @@ -0,0 +1,247 @@ +open($client->getLdapServer()); + $username = $username ?: $client->getLdapUsername(); + $password = $password ?: $client->getLdapPassword(); + + if (empty($username) && empty($password)) { + $client->useAnonymousAuthentication(); + } else { + $client->authenticate($username, $password); + } + + return $client; + } + + /** + * Get server connection + * + * @access public + * @return resource + */ + public function getConnection() + { + return $this->ldap; + } + + /** + * Establish server connection + * + * @access public + * + * @param string $server LDAP server URI (ldap[s]://hostname:port) or hostname (deprecated) + * @param int $port LDAP port (deprecated) + * @param bool $tls Start TLS + * @param bool $verify Skip SSL certificate verification + * @return Client + * @throws ClientException + * @throws ConnectionException + */ + public function open($server, $port = LDAP_PORT, $tls = LDAP_START_TLS, $verify = LDAP_SSL_VERIFY) + { + if (! function_exists('ldap_connect')) { + throw new ClientException('LDAP: The PHP LDAP extension is required'); + } + + if (! $verify) { + putenv('LDAPTLS_REQCERT=never'); + } + + if (filter_var($server, FILTER_VALIDATE_URL) !== false) { + $this->ldap = @ldap_connect($server); + } else { + $this->ldap = @ldap_connect($server, $port); + } + + if ($this->ldap === false) { + throw new ConnectionException('Malformed LDAP server hostname or LDAP server port'); + } + + ldap_set_option($this->ldap, LDAP_OPT_PROTOCOL_VERSION, 3); + ldap_set_option($this->ldap, LDAP_OPT_REFERRALS, 0); + ldap_set_option($this->ldap, LDAP_OPT_NETWORK_TIMEOUT, 1); + ldap_set_option($this->ldap, LDAP_OPT_TIMELIMIT, 1); + + if ($tls && ! @ldap_start_tls($this->ldap)) { + throw new ConnectionException('Unable to start LDAP TLS (' . $this->getLdapError() . ')'); + } + + return $this; + } + + /** + * Anonymous authentication + * + * @access public + * @throws ClientException + * @return boolean + */ + public function useAnonymousAuthentication() + { + if (! @ldap_bind($this->ldap)) { + $this->checkForServerConnectionError(); + throw new ClientException('Unable to perform anonymous binding => '.$this->getLdapError()); + } + + return true; + } + + /** + * Authentication with username/password + * + * @access public + * @throws ClientException + * @param string $bind_rdn + * @param string $bind_password + * @return boolean + */ + public function authenticate($bind_rdn, $bind_password) + { + if (! @ldap_bind($this->ldap, $bind_rdn, $bind_password)) { + $this->checkForServerConnectionError(); + throw new ClientException('LDAP authentication failure for "'.$bind_rdn.'" => '.$this->getLdapError()); + } + + return true; + } + + /** + * Get LDAP server name + * + * @access public + * @return string + */ + public function getLdapServer() + { + if (! LDAP_SERVER) { + throw new LogicException('LDAP server not configured, check the parameter LDAP_SERVER'); + } + + return LDAP_SERVER; + } + + /** + * Get LDAP username (proxy auth) + * + * @access public + * @return string + */ + public function getLdapUsername() + { + return LDAP_USERNAME; + } + + /** + * Get LDAP password (proxy auth) + * + * @access public + * @return string + */ + public function getLdapPassword() + { + return LDAP_PASSWORD; + } + + /** + * Set logger + * + * @access public + * @param LoggerInterface $logger + * @return Client + */ + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + return $this; + } + + /** + * Get logger + * + * @access public + * @return LoggerInterface + */ + public function getLogger() + { + return $this->logger; + } + + /** + * Test if a logger is defined + * + * @access public + * @return boolean + */ + public function hasLogger() + { + return $this->logger !== null; + } + + /** + * Raise ConnectionException if the application is not able to connect to LDAP server + * + * @access protected + * @throws ConnectionException + */ + protected function checkForServerConnectionError() + { + if (ldap_errno($this->ldap) === -1) { + throw new ConnectionException('Unable to connect to LDAP server (' . $this->getLdapError() . ')'); + } + } + + /** + * Get extended LDAP error message + * + * @return string + */ + protected function getLdapError() + { + ldap_get_option($this->ldap, LDAP_OPT_ERROR_STRING, $extendedErrorMessage); + $errorMessage = ldap_error($this->ldap); + $errorCode = ldap_errno($this->ldap); + + return 'Code="'.$errorCode.'"; Error="'.$errorMessage.'"; ExtendedError="'.$extendedErrorMessage.'"'; + } +} diff --git a/app/Core/Ldap/ClientException.php b/app/Core/Ldap/ClientException.php new file mode 100644 index 0000000..a0f9f84 --- /dev/null +++ b/app/Core/Ldap/ClientException.php @@ -0,0 +1,15 @@ +entries = $entries; + } + + /** + * Get all entries + * + * @access public + * @return Entry[] + */ + public function getAll() + { + $entities = array(); + + if (! isset($this->entries['count'])) { + return $entities; + } + + for ($i = 0; $i < $this->entries['count']; $i++) { + $entities[] = new Entry($this->entries[$i]); + } + + return $entities; + } + + /** + * Get first entry + * + * @access public + * @return Entry + */ + public function getFirstEntry() + { + return new Entry(isset($this->entries[0]) ? $this->entries[0] : array()); + } +} diff --git a/app/Core/Ldap/Entry.php b/app/Core/Ldap/Entry.php new file mode 100644 index 0000000..87a828e --- /dev/null +++ b/app/Core/Ldap/Entry.php @@ -0,0 +1,99 @@ +entry = $entry; + } + + /** + * Get all attribute values + * + * @access public + * @param string $attribute + * @return string[] + */ + public function getAll($attribute) + { + $attributes = array(); + + if ($attribute === null) { + return $attributes; + } + + if (! isset($this->entry[$attribute]['count'])) { + return $attributes; + } + + for ($i = 0; $i < $this->entry[$attribute]['count']; $i++) { + $attributes[] = $this->entry[$attribute][$i]; + } + + return $attributes; + } + + /** + * Get first attribute value + * + * @access public + * @param string $attribute + * @param string $default + * @return string + */ + public function getFirstValue($attribute, $default = '') + { + if ($attribute === null) { + return $default; + } + + return isset($this->entry[$attribute][0]) ? $this->entry[$attribute][0] : $default; + } + + /** + * Get entry distinguished name + * + * @access public + * @return string + */ + public function getDn() + { + return isset($this->entry['dn']) ? $this->entry['dn'] : ''; + } + + /** + * Return true if the given value exists in attribute list + * + * @access public + * @param string $attribute + * @param string $value + * @return boolean + */ + public function hasValue($attribute, $value) + { + $attributes = $this->getAll($attribute); + return in_array($value, $attributes); + } +} diff --git a/app/Core/Ldap/Group.php b/app/Core/Ldap/Group.php new file mode 100644 index 0000000..8b1d5fe --- /dev/null +++ b/app/Core/Ldap/Group.php @@ -0,0 +1,130 @@ +query = $query; + } + + /** + * Get groups + * + * @static + * @access public + * @param Client $client + * @param string $query + * @return LdapGroupProvider[] + */ + public static function getGroups(Client $client, $query) + { + $self = new static(new Query($client)); + return $self->find($query); + } + + /** + * Find groups + * + * @access public + * @param string $query + * @return array + */ + public function find($query) + { + $this->query->execute($this->getBaseDn(), $query, $this->getAttributes()); + $groups = array(); + + if ($this->query->hasResult()) { + $groups = $this->build(); + } + + return $groups; + } + + /** + * Build groups list + * + * @access protected + * @return array + */ + protected function build() + { + $groups = array(); + + foreach ($this->query->getEntries()->getAll() as $entry) { + $groups[] = new LdapGroupProvider($entry->getDn(), $entry->getFirstValue($this->getAttributeName())); + } + + return $groups; + } + + /** + * Ge the list of attributes to fetch when reading the LDAP group entry + * + * Must returns array with index that start at 0 otherwise ldap_search returns a warning "Array initialization wrong" + * + * @access public + * @return array + */ + public function getAttributes() + { + return array_values(array_filter(array( + $this->getAttributeName(), + ))); + } + + /** + * Get LDAP group name attribute + * + * @access public + * @return string + */ + public function getAttributeName() + { + if (! LDAP_GROUP_ATTRIBUTE_NAME) { + throw new LogicException('LDAP full name attribute empty, check the parameter LDAP_GROUP_ATTRIBUTE_NAME'); + } + + return strtolower(LDAP_GROUP_ATTRIBUTE_NAME); + } + + /** + * Get LDAP group base DN + * + * @access public + * @return string + */ + public function getBaseDn() + { + if (! LDAP_GROUP_BASE_DN) { + throw new LogicException('LDAP group base DN empty, check the parameter LDAP_GROUP_BASE_DN'); + } + + return LDAP_GROUP_BASE_DN; + } +} diff --git a/app/Core/Ldap/Query.php b/app/Core/Ldap/Query.php new file mode 100644 index 0000000..0fe7f2d --- /dev/null +++ b/app/Core/Ldap/Query.php @@ -0,0 +1,98 @@ +client = $client; + } + + /** + * Execute query + * + * @access public + * @param string $baseDn + * @param string $filter + * @param array $attributes + * @param integer $limit + * @return $this + */ + public function execute($baseDn, $filter, array $attributes, $limit = 0) + { + if (DEBUG && $this->client->hasLogger()) { + $this->client->getLogger()->debug('BaseDN='.$baseDn); + $this->client->getLogger()->debug('Filter='.$filter); + $this->client->getLogger()->debug('Attributes='.implode(', ', $attributes)); + } + + $sr = @ldap_search($this->client->getConnection(), $baseDn, $filter, $attributes, null, $limit); + if ($sr === false) { + return $this; + } + + $entries = ldap_get_entries($this->client->getConnection(), $sr); + if ($entries === false || count($entries) === 0 || $entries['count'] == 0) { + return $this; + } + + $this->entries = $entries; + + if (DEBUG && $this->client->hasLogger()) { + $this->client->getLogger()->debug('NbEntries='.$entries['count']); + } + + return $this; + } + + /** + * Return true if the query returned a result + * + * @access public + * @return boolean + */ + public function hasResult() + { + return ! empty($this->entries); + } + + /** + * Get LDAP Entries + * + * @access public + * @return Entries + */ + public function getEntries() + { + return new Entries($this->entries); + } +} diff --git a/app/Core/Ldap/User.php b/app/Core/Ldap/User.php new file mode 100644 index 0000000..bc1c8b1 --- /dev/null +++ b/app/Core/Ldap/User.php @@ -0,0 +1,366 @@ +query = $query; + $this->group = $group; + } + + /** + * Get user profile + * + * @static + * @access public + * @param Client $client + * @param string $username + * @return LdapUserProvider + */ + public static function getUser(Client $client, $username) + { + $self = new static(new Query($client), new Group(new Query($client))); + return $self->find($self->getLdapUserPattern($username)); + } + + /** + * Find user + * + * @access public + * @param string $query + * @return LdapUserProvider + */ + public function find($query) + { + $this->query->execute($this->getBaseDn(), $query, $this->getAttributes()); + $user = null; + + if ($this->query->hasResult()) { + $user = $this->build(); + } + + return $user; + } + + /** + * Get user groupIds (DN) + * + * 1) If configured, use memberUid and posixGroup + * 2) Otherwise, use memberOf + * + * @access protected + * @param Entry $entry + * @return string[] + */ + protected function getGroups(Entry $entry) + { + $userattr = ''; + if ('username' == $this->getGroupUserAttribute()) { + $userattr = $entry->getFirstValue($this->getAttributeUsername()); + } elseif ('dn' == $this->getGroupUserAttribute()) { + $userattr = $entry->getDn(); + } + $groupIds = array(); + + if (! empty($userattr) && $this->group !== null && $this->hasGroupUserFilter()) { + $escapedUserAttribute = ldap_escape($userattr, '', LDAP_ESCAPE_FILTER); + $groups = $this->group->find(sprintf($this->getGroupUserFilter(), $escapedUserAttribute)); + + foreach ($groups as $group) { + $groupIds[] = $group->getExternalId(); + } + } else { + $groupIds = $entry->getAll($this->getAttributeGroup()); + } + + return $groupIds; + } + + /** + * Get role from LDAP groups + * + * Note: Do not touch the current role if groups are not configured + * + * @access protected + * @param string[] $groupIds + * @return string + */ + protected function getRole(array $groupIds) + { + if (! $this->hasGroupsConfigured()) { + return null; + } + + if (LDAP_USER_DEFAULT_ROLE_MANAGER) { + $role = Role::APP_MANAGER; + } else { + $role = Role::APP_USER; + } + + foreach ($groupIds as $groupId) { + $groupId = strtolower($groupId); + + if ($groupId === strtolower($this->getGroupAdminDn())) { + $role = Role::APP_ADMIN; + break; + } + + if ($groupId === strtolower($this->getGroupManagerDn())) { + $role = Role::APP_MANAGER; + } + } + + return $role; + } + + /** + * Build user profile + * + * @access protected + * @return LdapUserProvider + */ + protected function build() + { + $entry = $this->query->getEntries()->getFirstEntry(); + $groupIds = $this->getGroups($entry); + + return new LdapUserProvider( + $entry->getDn(), + $entry->getFirstValue($this->getAttributeUsername()), + $entry->getFirstValue($this->getAttributeName()), + $entry->getFirstValue($this->getAttributeEmail()), + $this->getRole($groupIds), + $groupIds, + $entry->getFirstValue($this->getAttributePhoto()), + $entry->getFirstValue($this->getAttributeLanguage()) + ); + } + + /** + * Ge the list of attributes to fetch when reading the LDAP user entry + * + * Must returns array with index that start at 0 otherwise ldap_search returns a warning "Array initialization wrong" + * + * @access public + * @return array + */ + public function getAttributes() + { + return array_values(array_filter(array( + $this->getAttributeUsername(), + $this->getAttributeName(), + $this->getAttributeEmail(), + $this->getAttributeGroup(), + $this->getAttributePhoto(), + $this->getAttributeLanguage(), + ))); + } + + /** + * Get LDAP account id attribute + * + * @access public + * @return string + */ + public function getAttributeUsername() + { + if (! LDAP_USER_ATTRIBUTE_USERNAME) { + throw new LogicException('LDAP username attribute empty, check the parameter LDAP_USER_ATTRIBUTE_USERNAME'); + } + + return strtolower(LDAP_USER_ATTRIBUTE_USERNAME); + } + + /** + * Get LDAP user name attribute + * + * @access public + * @return string + */ + public function getAttributeName() + { + if (! LDAP_USER_ATTRIBUTE_FULLNAME) { + throw new LogicException('LDAP full name attribute empty, check the parameter LDAP_USER_ATTRIBUTE_FULLNAME'); + } + + return strtolower(LDAP_USER_ATTRIBUTE_FULLNAME); + } + + /** + * Get LDAP account email attribute + * + * @access public + * @return string + */ + public function getAttributeEmail() + { + if (! LDAP_USER_ATTRIBUTE_EMAIL) { + throw new LogicException('LDAP email attribute empty, check the parameter LDAP_USER_ATTRIBUTE_EMAIL'); + } + + return strtolower(LDAP_USER_ATTRIBUTE_EMAIL); + } + + /** + * Get LDAP account memberOf attribute + * + * @access public + * @return string + */ + public function getAttributeGroup() + { + return strtolower(LDAP_USER_ATTRIBUTE_GROUPS); + } + + /** + * Get LDAP profile photo attribute + * + * @access public + * @return string + */ + public function getAttributePhoto() + { + return strtolower(LDAP_USER_ATTRIBUTE_PHOTO); + } + + /** + * Get LDAP language attribute + * + * @access public + * @return string + */ + public function getAttributeLanguage() + { + return strtolower(LDAP_USER_ATTRIBUTE_LANGUAGE); + } + + /** + * Get LDAP Group User filter + * + * @access public + * @return string + */ + public function getGroupUserFilter() + { + return LDAP_GROUP_USER_FILTER; + } + + /** + * Get LDAP Group User attribute + * + * @access public + * @return string + */ + public function getGroupUserAttribute() + { + return LDAP_GROUP_USER_ATTRIBUTE; + } + + /** + * Return true if LDAP Group User filter is defined + * + * @access public + * @return string + */ + public function hasGroupUserFilter() + { + return $this->getGroupUserFilter() !== '' && $this->getGroupUserFilter() !== null; + } + + /** + * Return true if LDAP Group mapping are configured + * + * @access public + * @return boolean + */ + public function hasGroupsConfigured() + { + return $this->getGroupAdminDn() || $this->getGroupManagerDn(); + } + + /** + * Get LDAP admin group DN + * + * @access public + * @return string + */ + public function getGroupAdminDn(): string + { + return strtolower(LDAP_GROUP_ADMIN_DN); + } + + /** + * Get LDAP application manager group DN + * + * @access public + * @return string + */ + public function getGroupManagerDn(): string + { + return LDAP_GROUP_MANAGER_DN; + } + + /** + * Get LDAP user base DN + * + * @access public + * @return string + */ + public function getBaseDn() + { + return LDAP_USER_BASE_DN; + } + + /** + * Get LDAP user pattern + * + * @access public + * @param string $username + * @param string $filter + * @return string + */ + public function getLdapUserPattern($username, $filter = LDAP_USER_FILTER) + { + if (! $filter) { + throw new LogicException('LDAP user filter is not configured. Please set the LDAP_USER_FILTER parameter in your configuration file'); + } + + $escapedUsername = ldap_escape($username, '', LDAP_ESCAPE_FILTER); + return str_replace('%s', $escapedUsername, $filter); + } +} diff --git a/app/Core/Log/Base.php b/app/Core/Log/Base.php new file mode 100644 index 0000000..4bb2699 --- /dev/null +++ b/app/Core/Log/Base.php @@ -0,0 +1,89 @@ +level = $level; + } + + /** + * Get minimum log level + * + * @access public + * @return string + */ + public function getLevel() + { + return $this->level; + } + + /** + * Dump to log a variable (by example an array) + * + * @param mixed $variable + */ + public function dump($variable) + { + $this->log(LogLevel::DEBUG, var_export($variable, true)); + } + + /** + * Interpolates context values into the message placeholders. + * + * @access protected + * @param string $message + * @param array $context + * @return string + */ + protected function interpolate($message, array $context = array()) + { + // build a replacement array with braces around the context keys + $replace = array(); + + foreach ($context as $key => $val) { + $replace['{' . $key . '}'] = $val; + } + + // interpolate replacement values into the message and return + return strtr($message, $replace); + } + + /** + * Format log message + * + * @param mixed $level + * @param string $message + * @param array $context + * @return string + */ + protected function formatMessage($level, $message, array $context = array()) + { + return '['.date('Y-m-d H:i:s').'] ['.$level.'] '.$this->interpolate($message, $context).PHP_EOL; + } +} diff --git a/app/Core/Log/File.php b/app/Core/Log/File.php new file mode 100644 index 0000000..19260b3 --- /dev/null +++ b/app/Core/Log/File.php @@ -0,0 +1,48 @@ +filename = $filename; + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + */ + public function log($level, $message, array $context = array()) + { + $line = $this->formatMessage($level, $message, $context); + + if (file_put_contents($this->filename, $line, FILE_APPEND | LOCK_EX) === false) { + throw new RuntimeException('Unable to write to the log file.'); + } + } +} diff --git a/app/Core/Log/Logger.php b/app/Core/Log/Logger.php new file mode 100644 index 0000000..4155a04 --- /dev/null +++ b/app/Core/Log/Logger.php @@ -0,0 +1,94 @@ +loggers[] = $logger; + } + + /** + * Proxy method to the real loggers + * + * @param mixed $level + * @param string $message + * @param array $context + * @return null + */ + public function log($level, $message, array $context = array()) + { + foreach ($this->loggers as $logger) { + if ($this->getLevelPriority($level) >= $this->getLevelPriority($logger->getLevel())) { + $logger->log($level, $message, $context); + } + } + } + + /** + * Dump variables for debugging + * + * @param mixed $variable + */ + public function dump($variable) + { + foreach ($this->loggers as $logger) { + if ($this->getLevelPriority(LogLevel::DEBUG) >= $this->getLevelPriority($logger->getLevel())) { + $logger->dump($variable); + } + } + } +} diff --git a/app/Core/Log/Stderr.php b/app/Core/Log/Stderr.php new file mode 100644 index 0000000..fbe82a8 --- /dev/null +++ b/app/Core/Log/Stderr.php @@ -0,0 +1,25 @@ +formatMessage($level, $message, $context), FILE_APPEND); + } +} diff --git a/app/Core/Log/Stdout.php b/app/Core/Log/Stdout.php new file mode 100644 index 0000000..9914080 --- /dev/null +++ b/app/Core/Log/Stdout.php @@ -0,0 +1,25 @@ +formatMessage($level, $message, $context), FILE_APPEND); + } +} diff --git a/app/Core/Log/Syslog.php b/app/Core/Log/Syslog.php new file mode 100644 index 0000000..3a354a8 --- /dev/null +++ b/app/Core/Log/Syslog.php @@ -0,0 +1,72 @@ +getSyslogPriority($level); + $syslogMessage = $this->interpolate($message, $context); + + syslog($syslogPriority, $syslogMessage); + } +} diff --git a/app/Core/Log/System.php b/app/Core/Log/System.php new file mode 100644 index 0000000..ce801a0 --- /dev/null +++ b/app/Core/Log/System.php @@ -0,0 +1,25 @@ +interpolate($message, $context)); + } +} diff --git a/app/Core/Mail/Client.php b/app/Core/Mail/Client.php new file mode 100644 index 0000000..c87e9fe --- /dev/null +++ b/app/Core/Mail/Client.php @@ -0,0 +1,138 @@ +transports = new Container; + } + + /** + * Send a HTML email + * + * @access public + * @param string $recipientEmail + * @param string $recipientName + * @param string $subject + * @param string $html + * @return Client + */ + public function send($recipientEmail, $recipientName, $subject, $html, $authorName = null, $authorEmail = null) + { + if (! empty($recipientEmail)) { + $this->queueManager->push(EmailJob::getInstance($this->container)->withParams( + $recipientEmail, + $recipientName, + $subject, + $html, + is_null($authorName) ? $this->getAuthorName() : $authorName, + is_null($authorEmail) ? $this->getAuthorEmail() : $authorEmail + )); + } + + return $this; + } + + /** + * Get author name + * + * @access public + * @return string + */ + public function getAuthorName() + { + $author = 'Kanboard'; + + if ($this->userSession->isLogged()) { + $author = e('%s via Kanboard', $this->helper->user->getFullname()); + } + + return $author; + } + + /** + * Get author email + * + * @access public + * @return string + */ + public function getAuthorEmail() + { + if ($this->userSession->isLogged()) { + $userData = $this->userSession->getAll(); + return ! empty($userData['email']) ? $userData['email'] : ''; + } + + return ''; + } + + /** + * Get mail transport instance + * + * @access public + * @param string $transport + * @return ClientInterface + */ + public function getTransport($transport) + { + return $this->transports[$transport]; + } + + /** + * Add a new mail transport + * + * @access public + * @param string $transport + * @param string $class + * @return Client + */ + public function setTransport($transport, $class) + { + $container = $this->container; + + $this->transports[$transport] = function () use ($class, $container) { + return new $class($container); + }; + + return $this; + } + + /** + * Return the list of registered transports + * + * @access public + * @return array + */ + public function getAvailableTransports() + { + $availableTransports = $this->transports->keys(); + return array_combine($availableTransports, $availableTransports); + } +} diff --git a/app/Core/Mail/ClientInterface.php b/app/Core/Mail/ClientInterface.php new file mode 100644 index 0000000..3bfa32d --- /dev/null +++ b/app/Core/Mail/ClientInterface.php @@ -0,0 +1,25 @@ +setSubject($subject) + ->setFrom($this->helper->mail->getMailSenderAddress(), $authorName) + ->setTo(array($recipientEmail => $recipientName)); + + if (! empty(MAIL_BCC)) { + $message->setBcc(MAIL_BCC); + } + + $headers = $message->getHeaders(); + + // See https://tools.ietf.org/html/rfc3834#section-5 + $headers->addTextHeader('Auto-Submitted', 'auto-generated'); + + if (! empty($authorEmail)) { + $message->setReplyTo($authorEmail); + } + + $message->setBody($html, 'text/html'); + + Swift_Mailer::newInstance($this->getTransport())->send($message); + } catch (Swift_TransportException $e) { + $this->logger->error($e->getMessage()); + } + } + + /** + * Get SwiftMailer transport + * + * @access protected + * @return \Swift_Transport + */ + protected function getTransport() + { + return Swift_MailTransport::newInstance(); + } +} diff --git a/app/Core/Mail/Transport/Sendmail.php b/app/Core/Mail/Transport/Sendmail.php new file mode 100644 index 0000000..a1a2cbd --- /dev/null +++ b/app/Core/Mail/Transport/Sendmail.php @@ -0,0 +1,25 @@ +setUsername(MAIL_SMTP_USERNAME); + $transport->setPassword(MAIL_SMTP_PASSWORD); + if (!is_null(MAIL_SMTP_HELO_NAME)) { + $transport->setLocalDomain(MAIL_SMTP_HELO_NAME); + } + $transport->setEncryption(MAIL_SMTP_ENCRYPTION); + + if (HTTP_VERIFY_SSL_CERTIFICATE === false) { + $transport->setStreamOptions(array( + 'ssl' => array( + 'allow_self_signed' => true, + 'verify_peer' => false, + 'verify_peer_name' => false, + ) + )); + } + + return $transport; + } +} diff --git a/app/Core/Markdown.php b/app/Core/Markdown.php new file mode 100644 index 0000000..f8880b0 --- /dev/null +++ b/app/Core/Markdown.php @@ -0,0 +1,186 @@ +isPublicLink = $isPublicLink; + $this->container = $container; + $this->BlockTypes['#'][0] = 'CustomHeader'; + $this->InlineTypes['#'][] = 'TaskLink'; + $this->InlineTypes['@'][] = 'UserLink'; + $this->inlineMarkerList .= '#@'; + } + + protected function blockCustomHeader($Line) + { + if (preg_match('!#(\d+)!i', $Line['text'], $matches)) { + $link = $this->buildTaskLink($matches[1]); + + if (! empty($link)) { + return [ + 'extent' => strlen($matches[0]), + 'element' => [ + 'name' => 'a', + 'text' => $matches[0], + 'attributes' => ['href' => $link], + ], + ]; + } + } + + return $this->blockHeader($Line); + } + + /** + * Handle Task Links + * + * Replace "#123" by a link to the task + * + * @access public + * @param array $Excerpt + * @return array|null + */ + protected function inlineTaskLink(array $Excerpt) + { + if (preg_match('!#(\d+)!i', $Excerpt['text'], $matches)) { + $link = $this->buildTaskLink($matches[1]); + + if (! empty($link)) { + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'a', + 'text' => $matches[0], + 'attributes' => array('href' => $link), + ), + ); + } + } + + return null; + } + + /** + * Handle User Mentions + * + * Replace "@username" by a link to the user + * + * @access public + * @param array $Excerpt + * @return array|null + */ + protected function inlineUserLink(array $Excerpt) + { + if (! $this->isPublicLink && preg_match('/^@([^\s,!:?]+)/', $Excerpt['text'], $matches)) { + $username = rtrim($matches[1], '.'); + $user = $this->container['userCacheDecorator']->getByUsername($username); + + if (! empty($user)) { + $url = $this->container['helper']->url->to('UserViewController', 'profile', array('user_id' => $user['id'])); + $name = $user['name'] ?: $user['username']; + + return array( + 'extent' => strlen($username) + 1, + 'element' => array( + 'name' => 'a', + 'text' => '@' . $username, + 'attributes' => array( + 'href' => $url, + 'class' => 'user-mention-link', + 'title' => $name, + 'aria-label' => $name, + ), + ), + ); + } + } + + return null; + } + + /** + * Build task link + * + * @access private + * @param integer $task_id + * @return string + */ + private function buildTaskLink($task_id) + { + if ($this->isPublicLink) { + $token = $this->container['memoryCache']->proxy($this->container['taskFinderModel'], 'getProjectToken', $task_id); + + if (! empty($token)) { + return $this->container['helper']->url->to( + 'TaskViewController', + 'readonly', + array( + 'token' => $token, + 'task_id' => $task_id, + ), + '', + true + ); + } + + return ''; + } + + return $this->container['helper']->url->to( + 'TaskViewController', + 'show', + array('task_id' => $task_id) + ); + } + + /** + * Exclude from nesting task links and user mentions for links + * + * @param array $Excerpt + * @return array|null + */ + protected function inlineLink($Excerpt) + { + $Inline = parent::inlineLink($Excerpt); + if (is_array($Inline)) { + array_push($Inline['element']['nonNestables'], 'TaskLink', 'UserLink'); + } + return $Inline; + } +} diff --git a/app/Core/Notification/NotificationInterface.php b/app/Core/Notification/NotificationInterface.php new file mode 100644 index 0000000..d336983 --- /dev/null +++ b/app/Core/Notification/NotificationInterface.php @@ -0,0 +1,32 @@ +baseDir = $realBaseDir; + } + + /** + * Fetch object contents + * + * @access public + * @throws ObjectStorageException + * @param string $key + * @return string + */ + public function get($key) + { + return file_get_contents($this->getRealFilePath($key)); + } + + /** + * Save object + * + * @access public + * @throws ObjectStorageException + * @param string $key + * @param string $blob + */ + public function put($key, &$blob) + { + $filename = $this->getSanitizedFilePath($key); + $this->createFolder($key); + + if (file_put_contents($filename, $blob) === false) { + throw new ObjectStorageException('Unable to write the file: '.$filename); + } + } + + /** + * Output directly object content + * + * @access public + * @throws ObjectStorageException + * @param string $key + */ + public function output($key) + { + readfile($this->getRealFilePath($key)); + } + + /** + * Move local file to object storage + * + * @access public + * @throws ObjectStorageException + * @param string $srcFilename + * @param string $key + * @return boolean + */ + public function moveFile($srcFilename, $key) + { + if (! file_exists($srcFilename)) { + throw new ObjectStorageException('Source file does not exist: '.$srcFilename); + } + + $dstFilename = $this->getSanitizedFilePath($key); + $this->createFolder($key); + + if (! rename($srcFilename, $dstFilename)) { + throw new ObjectStorageException('Unable to move the file: '.$srcFilename.' to '.$dstFilename); + } + + return true; + } + + /** + * Move uploaded file to object storage + * + * @access public + * @param string $srcFilename + * @param string $key + * @return boolean + */ + public function moveUploadedFile($srcFilename, $key) + { + if (! file_exists($srcFilename)) { + throw new ObjectStorageException('Source file does not exist: '.$srcFilename); + } + + $dstFilename = $this->getSanitizedFilePath($key); + $this->createFolder($key); + + return move_uploaded_file($srcFilename, $dstFilename); + } + + /** + * Remove object + * + * @access public + * @param string $key + * @return boolean + */ + public function remove($key) + { + $filename = $this->getRealFilePath($key); + $result = unlink($filename); + + // Remove parent folder if empty + $parentFolder = dirname($filename); + $files = glob($parentFolder.DIRECTORY_SEPARATOR.'*'); + + if ($files !== false && is_dir($parentFolder) && count($files) === 0) { + rmdir($parentFolder); + } + + return $result; + } + + private function createFolder(string $key) + { + $folder = strpos($key, DIRECTORY_SEPARATOR) !== false ? $this->baseDir.DIRECTORY_SEPARATOR.dirname($key) : $this->baseDir; + + if (! is_dir($folder) && ! mkdir($folder, 0o755, true)) { + throw new ObjectStorageException('Unable to create folder: '.$folder); + } + } + + private function getRealFilePath(string $key): string + { + $filename = $this->baseDir.DIRECTORY_SEPARATOR.$key; + + // Resolve the real path and make sure the file exists + $realFilePath = realpath($filename); + if ($realFilePath === false) { + throw new ObjectStorageException('Invalid file path: '.$filename); + } + + $this->validateBasePath($realFilePath); + + return $realFilePath; + } + + public function getSanitizedFilePath(string $key): string + { + $filename = $this->baseDir.DIRECTORY_SEPARATOR.$key; + $sanitizedKey = sanitize_path($filename); + + if ($sanitizedKey === false) { + throw new ObjectStorageException('Invalid file path: '.$key); + } + + $this->validateBasePath($sanitizedKey); + + return $sanitizedKey; + } + + private function validateBasePath(string $filePath) + { + if (strpos($filePath, $this->baseDir) !== 0) { + throw new ObjectStorageException('File '.$filePath.' is not in base directory: '.$this->baseDir); + } + } +} diff --git a/app/Core/ObjectStorage/ObjectStorageException.php b/app/Core/ObjectStorage/ObjectStorageException.php new file mode 100644 index 0000000..9e98ff5 --- /dev/null +++ b/app/Core/ObjectStorage/ObjectStorageException.php @@ -0,0 +1,9 @@ +container = $container; + } + + /** + * Set a PicoDb query + * + * @access public + * @param \PicoDb\Table + * @return $this + */ + public function setQuery(Table $query) + { + $this->query = $query; + $this->total = $this->query->count(); + return $this; + } + + /** + * Set Formatter + * + * @param FormatterInterface $formatter + * @return $this + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->formatter = $formatter; + return $this; + } + + /** + * Execute a PicoDb query + * + * @access public + * @return array + */ + public function executeQuery() + { + if ($this->query !== null) { + + $this->query + ->offset($this->offset) + ->limit($this->limit); + + if (preg_match('/^[a-zA-Z0-9._]+$/', $this->order)) { + $this->query->orderBy($this->order, $this->direction); + } else { + $this->order = ''; + } + + if ($this->formatter !== null) { + return $this->formatter->withQuery($this->query)->format(); + } else { + return $this->query->findAll(); + } + } + + return array(); + } + + /** + * Set url parameters + * + * @access public + * @param string $controller + * @param string $action + * @param array $params + * @param string $anchor + * @return $this + */ + public function setUrl($controller, $action, array $params = array(), $anchor = '') + { + $this->controller = $controller; + $this->action = $action; + $this->params = $params; + $this->anchor = $anchor; + return $this; + } + + /** + * Add manually items + * + * @access public + * @param array $items + * @return $this + */ + public function setCollection(array $items) + { + $this->items = $items; + return $this; + } + + /** + * Return the items + * + * @access public + * @return array + */ + public function getCollection() + { + return $this->items ?: $this->executeQuery(); + } + + /** + * Set the total number of items + * + * @access public + * @param integer $total + * @return $this + */ + public function setTotal($total) + { + $this->total = $total; + return $this; + } + + /** + * Get the total number of items + * + * @access public + * @return integer + */ + public function getTotal() + { + return $this->total; + } + + /** + * Set the default page number + * + * @access public + * @param integer $page + * @return $this + */ + public function setPage($page) + { + $this->page = $page; + return $this; + } + + /** + * Get the number of current page + * + * @access public + * @return integer + */ + public function getPage() + { + return $this->page; + } + + /** + * Set the default column order + * + * @access public + * @param string $order + * @return $this + */ + public function setOrder($order) + { + $this->order = $order; + return $this; + } + + /** + * Set the default sorting direction + * + * @access public + * @param string $direction + * @return $this + */ + public function setDirection($direction) + { + $this->direction = $direction; + return $this; + } + + /** + * Set the maximum number of items per page + * + * @access public + * @param integer $limit + * @return $this + */ + public function setMax($limit) + { + $this->limit = $limit; + return $this; + } + + /** + * Get the maximum number of items per page. + * + * @return int + */ + public function getMax() + { + return $this->limit; + } + + /** + * Return true if the collection is empty + * + * @access public + * @return boolean + */ + public function isEmpty() + { + return $this->total === 0; + } + + /** + * Execute the offset calculation only if the $condition is true + * + * @access public + * @param boolean $condition + * @return $this + */ + public function calculateOnlyIf($condition) + { + if ($condition) { + $this->calculate(); + } + + return $this; + } + + /** + * Calculate the offset value according to url params and the page number + * + * @access public + * @return $this + */ + public function calculate() + { + $this->page = $this->container['request']->getIntegerParam('page', 1); + $this->direction = $this->container['request']->getStringParam('direction', $this->direction); + $this->order = $this->container['request']->getStringParam('order', $this->order); + + if ($this->page < 1) { + $this->page = 1; + } + + $this->offset = (int) (($this->page - 1) * $this->limit); + + return $this; + } + + /** + * Get url params for link generation + * + * @access public + * @param integer $page + * @param string $order + * @param string $direction + * @return string + */ + public function getUrlParams($page, $order, $direction) + { + $params = array( + 'page' => $page, + 'order' => $order, + 'direction' => $direction, + ); + + return array_merge($this->params, $params); + } + + /** + * Generate the previous link + * + * @access public + * @return string + */ + public function generatePreviousLink() + { + $html = ''; + + if ($this->offset > 0) { + $html .= $this->container['helper']->url->link( + '← '.t('Previous'), + $this->controller, + $this->action, + $this->getUrlParams($this->page - 1, $this->order, $this->direction), + false, + 'js-modal-replace', + t('Previous'), + false, + $this->anchor + ); + } else { + $html .= '← '.t('Previous'); + } + + $html .= ''; + + return $html; + } + + /** + * Generate the next link + * + * @access public + * @return string + */ + public function generateNextLink() + { + $html = ''; + + if (($this->total - $this->offset) > $this->limit) { + $html .= $this->container['helper']->url->link( + t('Next').' →', + $this->controller, + $this->action, + $this->getUrlParams($this->page + 1, $this->order, $this->direction), + false, + 'js-modal-replace', + t('Next'), + false, + $this->anchor + ); + } else { + $html .= t('Next').' →'; + } + + $html .= ''; + + return $html; + } + + /** + * Generate the page showing. + * + * @access public + * @return string + */ + public function generatePageShowing() + { + return ''.t('Showing %d-%d of %d', (($this->getPage() - 1) * $this->getMax() + 1), min($this->getTotal(), $this->getPage() * $this->getMax()), $this->getTotal()).''; + } + + /** + * Return true if there is no pagination to show + * + * @access public + * @return boolean + */ + public function hasNothingToShow() + { + return $this->offset === 0 && ($this->total - $this->offset) <= $this->limit; + } + + /** + * Generation pagination links + * + * @access public + * @return string + */ + public function toHtml() + { + $html = ''; + + if (! $this->hasNothingToShow()) { + $html .= ''; + } + + return $html; + } + + /** + * Magic method to output pagination links + * + * @access public + * @return string + */ + public function __toString() + { + return $this->toHtml(); + } + + /** + * Column sorting + * + * @param string $label Column title + * @param string $column SQL column name + * @return string + */ + public function order($label, $column) + { + $prefix = ''; + $direction = 'ASC'; + + if ($this->order === $column) { + $prefix = $this->direction === 'DESC' ? '▼ ' : '▲ '; + $direction = $this->direction === 'DESC' ? 'ASC' : 'DESC'; + } + + return $prefix.$this->container['helper']->url->link( + $label, + $this->controller, + $this->action, + $this->getUrlParams($this->page, $column, $direction), + false, + 'js-modal-replace' + ); + } +} diff --git a/app/Core/Plugin/Base.php b/app/Core/Plugin/Base.php new file mode 100644 index 0000000..e0b5954 --- /dev/null +++ b/app/Core/Plugin/Base.php @@ -0,0 +1,147 @@ +container['cspRules'] = $rules; + } + + /** + * Returns all classes that needs to be stored in the DI container + * + * @access public + * @return array + */ + public function getClasses() + { + return array(); + } + + /** + * Returns all helper classes that needs to be stored in the DI container + * + * @access public + * @return array + */ + public function getHelpers() + { + return array(); + } + + /** + * Listen on internal events + * + * @access public + * @param string $event + * @param callable $callback + */ + public function on($event, $callback) + { + $container = $this->container; + + $this->dispatcher->addListener($event, function () use ($container, $callback) { + call_user_func($callback, $container); + }); + } + + /** + * Get plugin name + * + * This method should be overridden by your Plugin class + * + * @access public + * @return string + */ + public function getPluginName() + { + return ucfirst(substr(get_called_class(), 16, -7)); + } + + /** + * Get plugin description + * + * This method should be overridden by your Plugin class + * + * @access public + * @return string + */ + public function getPluginDescription() + { + return ''; + } + + /** + * Get plugin author + * + * This method should be overridden by your Plugin class + * + * @access public + * @return string + */ + public function getPluginAuthor() + { + return '?'; + } + + /** + * Get plugin version + * + * This method should be overridden by your Plugin class + * + * @access public + * @return string + */ + public function getPluginVersion() + { + return '?'; + } + + /** + * Get plugin homepage + * + * This method should be overridden by your Plugin class + * + * @access public + * @return string + */ + public function getPluginHomepage() + { + return ''; + } + + /** + * Get application compatibility version + * + * Examples: >=1.0.36, 1.0.37, APP_VERSION + * + * @access public + * @return string + */ + public function getCompatibleVersion() + { + return APP_VERSION; + } +} diff --git a/app/Core/Plugin/Directory.php b/app/Core/Plugin/Directory.php new file mode 100644 index 0000000..dc32e65 --- /dev/null +++ b/app/Core/Plugin/Directory.php @@ -0,0 +1,52 @@ +httpClient->getJson($url); + $plugins = array_filter($plugins, array($this, 'isCompatible')); + $plugins = array_filter($plugins, array($this, 'isInstallable')); + return $plugins; + } + + /** + * Filter plugins + * + * @param array $plugin + * @param string $appVersion + * @return bool + */ + public function isCompatible(array $plugin, $appVersion = APP_VERSION) + { + return Version::isCompatible($plugin['compatible_version'], $appVersion); + } + + /** + * Filter plugins + * + * @param array $plugin + * @return bool + */ + public function isInstallable(array $plugin) + { + return $plugin['remote_install']; + } +} diff --git a/app/Core/Plugin/Hook.php b/app/Core/Plugin/Hook.php new file mode 100644 index 0000000..ca19793 --- /dev/null +++ b/app/Core/Plugin/Hook.php @@ -0,0 +1,116 @@ +hooks[$hook])) { + $this->hooks[$hook] = array(); + } + + $this->hooks[$hook][] = $value; + } + + /** + * Get all bindings for a hook + * + * @access public + * @param string $hook + * @return array + */ + public function getListeners($hook) + { + return isset($this->hooks[$hook]) ? $this->hooks[$hook] : array(); + } + + /** + * Return true if the hook is used + * + * @access public + * @param string $hook + * @return boolean + */ + public function exists($hook) + { + return isset($this->hooks[$hook]); + } + + /** + * Merge listener results with input array + * + * @access public + * @param string $hook + * @param array $values + * @param array $params + * @return array + */ + public function merge($hook, array &$values, array $params = array()) + { + foreach ($this->getListeners($hook) as $listener) { + $result = call_user_func_array($listener, $params); + + if (is_array($result) && ! empty($result)) { + $values = array_merge($values, $result); + } + } + + return $values; + } + + /** + * Execute only first listener + * + * @access public + * @param string $hook + * @param array $params + * @return mixed + */ + public function first($hook, array $params = array()) + { + foreach ($this->getListeners($hook) as $listener) { + return call_user_func_array($listener, $params); + } + + return null; + } + + /** + * Hook with reference + * + * @access public + * @param string $hook + * @param mixed $param + * @return mixed + */ + public function reference($hook, &$param) + { + foreach ($this->getListeners($hook) as $listener) { + $listener($param); + } + + return $param; + } +} diff --git a/app/Core/Plugin/Installer.php b/app/Core/Plugin/Installer.php new file mode 100644 index 0000000..cf6079b --- /dev/null +++ b/app/Core/Plugin/Installer.php @@ -0,0 +1,144 @@ +downloadPluginArchive($archiveUrl); + + if (! $zip->extractTo(PLUGINS_DIR)) { + $this->cleanupArchive($zip); + throw new PluginInstallerException(t('Unable to extract plugin archive.')); + } + + $this->cleanupArchive($zip); + } + + /** + * Uninstall a plugin + * + * @access public + * @param string $pluginId + * @throws PluginInstallerException + */ + public function uninstall($pluginId) + { + $pluginFolder = PLUGINS_DIR.DIRECTORY_SEPARATOR.basename($pluginId); + + if (! file_exists($pluginFolder)) { + throw new PluginInstallerException(t('Plugin not found.')); + } + + if (! is_writable($pluginFolder)) { + throw new PluginInstallerException(e('You don\'t have the permission to remove this plugin.')); + } + + Tool::removeAllFiles($pluginFolder); + } + + /** + * Update a plugin + * + * @access public + * @param string $archiveUrl + * @throws PluginInstallerException + */ + public function update($archiveUrl) + { + $zip = $this->downloadPluginArchive($archiveUrl); + + $firstEntry = $zip->statIndex(0); + $this->uninstall($firstEntry['name']); + + if (! $zip->extractTo(PLUGINS_DIR)) { + $this->cleanupArchive($zip); + throw new PluginInstallerException(t('Unable to extract plugin archive.')); + } + + $this->cleanupArchive($zip); + } + + /** + * Download archive from URL + * + * @access protected + * @param string $archiveUrl + * @return ZipArchive + * @throws PluginInstallerException + */ + protected function downloadPluginArchive($archiveUrl) + { + if (!preg_match('/^https?:\/\//', $archiveUrl) || !filter_var($archiveUrl, FILTER_VALIDATE_URL)) { + throw new PluginInstallerException(t('This URL is invalid')); + } + + $archiveData = $this->httpClient->get($archiveUrl); + $archiveFile = tempnam(ini_get('upload_tmp_dir') ? ini_get('upload_tmp_dir') : sys_get_temp_dir(), 'kb_plugin'); + + if (empty($archiveData)) { + unlink($archiveFile); + throw new PluginInstallerException(t('Unable to download plugin archive.')); + } + + if (file_put_contents($archiveFile, $archiveData) === false) { + unlink($archiveFile); + throw new PluginInstallerException(t('Unable to write temporary file for plugin.')); + } + + $zip = new ZipArchive(); + if ($zip->open($archiveFile) !== true) { + unlink($archiveFile); + throw new PluginInstallerException(t('Unable to open plugin archive.')); + } + + if ($zip->numFiles === 0) { + unlink($archiveFile); + throw new PluginInstallerException(t('There is no file in the plugin archive.')); + } + + return $zip; + } + + /** + * Remove archive file + * + * @access protected + * @param ZipArchive $zip + */ + protected function cleanupArchive(ZipArchive $zip) + { + $filename = $zip->filename; + $zip->close(); + unlink($filename); + } +} diff --git a/app/Core/Plugin/Loader.php b/app/Core/Plugin/Loader.php new file mode 100644 index 0000000..38f41d3 --- /dev/null +++ b/app/Core/Plugin/Loader.php @@ -0,0 +1,137 @@ +plugins; + } + + /** + * Get list of not compatible plugins + * + * @access public + * @return Base[] + */ + public function getIncompatiblePlugins() + { + return $this->incompatiblePlugins; + } + + /** + * Scan plugin folder and load plugins + * + * @access public + */ + public function scan() + { + if (file_exists(PLUGINS_DIR)) { + $loader = new ClassLoader(); + $loader->addPsr4('Kanboard\Plugin\\', PLUGINS_DIR); + $loader->register(); + + $dir = new DirectoryIterator(PLUGINS_DIR); + + foreach ($dir as $fileInfo) { + if ($fileInfo->isDir() && substr($fileInfo->getFilename(), 0, 1) !== '.') { + $pluginName = $fileInfo->getFilename(); + $this->initializePlugin($pluginName); + } + } + } + } + + /** + * Load plugin schema + * + * @access public + * @param string $pluginName + */ + public function loadSchema($pluginName) + { + if (SchemaHandler::hasSchema($pluginName)) { + $schemaHandler = new SchemaHandler($this->container); + $schemaHandler->loadSchema($pluginName); + } + } + + /** + * Load plugin + * + * @access public + * @throws LogicException + * @param string $pluginName + * @return Base + */ + public function loadPlugin($pluginName) + { + $className = '\Kanboard\Plugin\\'.$pluginName.'\\Plugin'; + + if (! class_exists($className)) { + throw new LogicException('Unable to load this plugin class: '.$className); + } + + return new $className($this->container); + } + + /** + * Initialize plugin + * + * @access public + * @param string $pluginName + */ + public function initializePlugin($pluginName) + { + try { + $plugin = $this->loadPlugin($pluginName); + + if (Version::isCompatible($plugin->getCompatibleVersion(), APP_VERSION)) { + $this->loadSchema($pluginName); + + if (method_exists($plugin, 'onStartup')) { + $this->dispatcher->addListener('app.bootstrap', array($plugin, 'onStartup')); + } + + Tool::buildDIC($this->container, $plugin->getClasses()); + Tool::buildDICHelpers($this->container, $plugin->getHelpers()); + + $plugin->initialize(); + $this->plugins[$pluginName] = $plugin; + } else { + $this->incompatiblePlugins[$pluginName] = $plugin; + $this->logger->error($pluginName.' is not compatible with this version'); + } + } catch (Exception $e) { + $this->logger->critical($pluginName.': '.$e->getMessage()); + } + } +} diff --git a/app/Core/Plugin/PluginException.php b/app/Core/Plugin/PluginException.php new file mode 100644 index 0000000..fae7de3 --- /dev/null +++ b/app/Core/Plugin/PluginException.php @@ -0,0 +1,15 @@ +migrateSchema($pluginName); + } + + /** + * Execute plugin schema migrations + * + * @access public + * @param string $pluginName + */ + public function migrateSchema($pluginName) + { + $lastVersion = constant('\Kanboard\Plugin\\'.$pluginName.'\Schema\VERSION'); + $currentVersion = $this->getSchemaVersion($pluginName); + + try { + $this->db->startTransaction(); + $this->db->getDriver()->disableForeignKeys(); + + for ($i = $currentVersion + 1; $i <= $lastVersion; $i++) { + $functionName = '\Kanboard\Plugin\\'.$pluginName.'\Schema\version_'.$i; + + if (function_exists($functionName)) { + call_user_func($functionName, $this->db->getConnection()); + } + } + + $this->db->getDriver()->enableForeignKeys(); + $this->db->closeTransaction(); + $this->setSchemaVersion($pluginName, $i - 1); + } catch (PDOException $e) { + $this->db->cancelTransaction(); + $this->db->getDriver()->enableForeignKeys(); + throw new RuntimeException('Unable to migrate schema for the plugin: '.$pluginName.' => '.$e->getMessage()); + } + } + + /** + * Get current plugin schema version + * + * @access public + * @param string $plugin + * @return integer + */ + public function getSchemaVersion($plugin) + { + return (int) $this->db->table(self::TABLE_SCHEMA)->eq('plugin', strtolower($plugin))->findOneColumn('version'); + } + + /** + * Save last plugin schema version + * + * @access public + * @param string $plugin + * @param integer $version + * @return boolean + */ + public function setSchemaVersion($plugin, $version) + { + $dictionary = array( + strtolower($plugin) => $version + ); + + return $this->db->getDriver()->upsert(self::TABLE_SCHEMA, 'plugin', 'version', $dictionary); + } +} diff --git a/app/Core/Plugin/Version.php b/app/Core/Plugin/Version.php new file mode 100644 index 0000000..13f7209 --- /dev/null +++ b/app/Core/Plugin/Version.php @@ -0,0 +1,38 @@ +=', '>', '<=', '<') as $operator) { + if (strpos($pluginCompatibleVersion, $operator) === 0) { + $pluginVersion = substr($pluginCompatibleVersion, strlen($operator)); + return version_compare($appVersion, $pluginVersion, $operator); + } + } + + return $pluginCompatibleVersion === $appVersion; + } +} diff --git a/app/Core/Queue/JobHandler.php b/app/Core/Queue/JobHandler.php new file mode 100644 index 0000000..bc63fe0 --- /dev/null +++ b/app/Core/Queue/JobHandler.php @@ -0,0 +1,90 @@ + get_class($job), + 'params' => $job->getJobParams(), + 'user_id' => $this->userSession->getId(), + )); + } + + /** + * Execute a job + * + * @access public + * @param Job $job + */ + public function executeJob(Job $job) + { + $payload = $job->getBody(); + + try { + $className = $payload['class']; + $this->prepareJobSession($payload['user_id']); + $this->prepareJobEnvironment(); + + if (DEBUG) { + $this->logger->debug(__METHOD__.' Received job => '.$className.' ('.getmypid().')'); + $this->logger->debug(__METHOD__.' => '.json_encode($payload)); + } + + $worker = new $className($this->container); + call_user_func_array(array($worker, 'execute'), $payload['params']); + } catch (Exception $e) { + $this->logger->error(__METHOD__.': Error during job execution: '.$e->getMessage()); + $this->logger->error(__METHOD__ .' => '.json_encode($payload)); + } + } + + /** + * Create the session for the job + * + * @access protected + * @param integer $user_id + */ + protected function prepareJobSession($user_id) + { + session_flush(); + + if ($user_id > 0) { + $user = $this->userModel->getById($user_id); + $this->userSession->initialize($user); + } + } + + /** + * Flush in-memory caching and specific events + * + * @access protected + */ + protected function prepareJobEnvironment() + { + $this->memoryCache->flush(); + $this->actionManager->removeEvents(); + $this->dispatcher->dispatch(new Event(), 'app.bootstrap'); + } +} diff --git a/app/Core/Queue/QueueManager.php b/app/Core/Queue/QueueManager.php new file mode 100644 index 0000000..1d7c2d1 --- /dev/null +++ b/app/Core/Queue/QueueManager.php @@ -0,0 +1,75 @@ +queue = $queue; + return $this; + } + + /** + * Send a new job to the queue + * + * @access public + * @param BaseJob $job + * @return $this + */ + public function push(BaseJob $job) + { + $jobClassName = get_class($job); + + if ($this->queue !== null) { + $this->logger->debug(__METHOD__.': Job pushed in queue: '.$jobClassName); + $this->queue->push(JobHandler::getInstance($this->container)->serializeJob($job)); + } else { + $this->logger->debug(__METHOD__.': Job executed synchronously: '.$jobClassName); + call_user_func_array(array($job, 'execute'), $job->getJobParams()); + } + + return $this; + } + + /** + * Wait for new jobs + * + * @access public + * @throws LogicException + */ + public function listen() + { + if ($this->queue === null) { + throw new LogicException('No queue driver defined or unable to connect to broker!'); + } + + while ($job = $this->queue->pull()) { + JobHandler::getInstance($this->container)->executeJob($job); + $this->queue->completed($job); + } + } +} diff --git a/app/Core/Security/AccessMap.php b/app/Core/Security/AccessMap.php new file mode 100644 index 0000000..2431a92 --- /dev/null +++ b/app/Core/Security/AccessMap.php @@ -0,0 +1,175 @@ +defaultRole = $role; + return $this; + } + + /** + * Define role hierarchy + * + * @access public + * @param string $role + * @param array $subroles + * @return AccessMap + */ + public function setRoleHierarchy($role, array $subroles) + { + foreach ($subroles as $subrole) { + if (isset($this->hierarchy[$subrole])) { + $this->hierarchy[$subrole][] = $role; + } else { + $this->hierarchy[$subrole] = array($role); + } + } + + return $this; + } + + /** + * Get computed role hierarchy + * + * @access public + * @param string $role + * @return array + */ + public function getRoleHierarchy($role) + { + $roles = array($role); + + if (isset($this->hierarchy[$role])) { + $roles = array_merge($roles, $this->hierarchy[$role]); + } + + return $roles; + } + + /** + * Get the highest role from a list + * + * @access public + * @param array $roles + * @return string + */ + public function getHighestRole(array $roles) + { + $rank = array(); + + foreach ($roles as $role) { + $rank[$role] = count($this->getRoleHierarchy($role)); + } + + asort($rank); + + return key($rank); + } + + /** + * Add new access rules + * + * @access public + * @param string $controller Controller class name + * @param mixed $methods List of method name or just one method + * @param string $role Lowest role required + * @return AccessMap + */ + public function add($controller, $methods, $role) + { + if (is_array($methods)) { + foreach ($methods as $method) { + $this->addRule($controller, $method, $role); + } + } else { + $this->addRule($controller, $methods, $role); + } + + return $this; + } + + /** + * Add new access rule + * + * @access private + * @param string $controller + * @param string $method + * @param string $role + * @return AccessMap + */ + private function addRule($controller, $method, $role) + { + $controller = strtolower($controller); + $method = strtolower($method); + + if (! isset($this->map[$controller])) { + $this->map[$controller] = array(); + } + + $this->map[$controller][$method] = $role; + + return $this; + } + + /** + * Get roles that match the given controller/method + * + * @access public + * @param string $controller + * @param string $method + * @return array + */ + public function getRoles($controller, $method) + { + $controller = strtolower($controller); + $method = strtolower($method); + + foreach (array($method, '*') as $key) { + if (isset($this->map[$controller][$key])) { + return $this->getRoleHierarchy($this->map[$controller][$key]); + } + } + + return $this->getRoleHierarchy($this->defaultRole); + } +} diff --git a/app/Core/Security/AuthenticationManager.php b/app/Core/Security/AuthenticationManager.php new file mode 100644 index 0000000..8b0808a --- /dev/null +++ b/app/Core/Security/AuthenticationManager.php @@ -0,0 +1,200 @@ +providers = []; + } + + /** + * Register a new authentication provider + * + * @access public + * @param AuthenticationProviderInterface $provider + * @return AuthenticationManager + */ + public function register(AuthenticationProviderInterface $provider) + { + $this->providers[$provider->getName()] = $provider; + return $this; + } + + /** + * Register a new authentication provider + * + * @access public + * @param string $name + * @return AuthenticationProviderInterface|OAuthAuthenticationProviderInterface|PasswordAuthenticationProviderInterface|PreAuthenticationProviderInterface|OAuthAuthenticationProviderInterface + */ + public function getProvider($name) + { + if (! isset($this->providers[$name])) { + throw new LogicException('Authentication provider not found: '.$name); + } + + return $this->providers[$name]; + } + + /** + * Execute providers that are able to validate the current session + * + * @access public + * @return boolean + */ + public function checkCurrentSession() + { + if ($this->userSession->isLogged()) { + foreach ($this->filterProviders('SessionCheckProviderInterface') as $provider) { + if ($provider instanceof OptionalAuthenticationProviderInterface && ! $provider->isEnabled()) { + continue; + } + + if (! $provider->isValidSession()) { + $this->logger->debug('Invalidate session for '.$this->userSession->getUsername()); + session_flush(); + $this->preAuthentication(); + return false; + } + } + } + + return true; + } + + /** + * Execute pre-authentication providers + * + * @access public + * @return boolean + */ + public function preAuthentication() + { + foreach ($this->filterProviders('PreAuthenticationProviderInterface') as $provider) { + if ($provider instanceof OptionalAuthenticationProviderInterface && ! $provider->isEnabled()) { + continue; + } + + if ($provider->authenticate() && $this->userProfile->initialize($provider->getUser())) { + $this->dispatcher->dispatch(new AuthSuccessEvent($provider->getName()), self::EVENT_SUCCESS); + return true; + } + } + + return false; + } + + /** + * Execute username/password authentication providers + * + * @access public + * @param string $username + * @param string $password + * @param boolean $fireEvent + * @return boolean + */ + public function passwordAuthentication($username, $password, $fireEvent = true) + { + foreach ($this->filterProviders('PasswordAuthenticationProviderInterface') as $provider) { + $provider->setUsername($username); + $provider->setPassword($password); + + if ($provider->authenticate() && $this->userProfile->initialize($provider->getUser())) { + if ($fireEvent) { + $this->dispatcher->dispatch(new AuthSuccessEvent($provider->getName()), self::EVENT_SUCCESS); + } + + return true; + } + } + + if ($fireEvent) { + $this->dispatcher->dispatch(new AuthFailureEvent($username), self::EVENT_FAILURE); + } + + return false; + } + + /** + * Perform OAuth2 authentication + * + * @access public + * @param string $name + * @return boolean + */ + public function oauthAuthentication($name) + { + $provider = $this->getProvider($name); + + if ($provider->authenticate() && $this->userProfile->initialize($provider->getUser())) { + $this->dispatcher->dispatch(new AuthSuccessEvent($provider->getName()), self::EVENT_SUCCESS); + return true; + } + + $this->dispatcher->dispatch(new AuthFailureEvent, self::EVENT_FAILURE); + + return false; + } + + /** + * Get the last Post-Authentication provider + * + * @access public + * @return PostAuthenticationProviderInterface + */ + public function getPostAuthenticationProvider() + { + $providers = $this->filterProviders('PostAuthenticationProviderInterface'); + + if (empty($providers)) { + throw new LogicException('You must have at least one Post-Authentication Provider configured'); + } + + return array_pop($providers); + } + + /** + * Filter registered providers by interface type + * + * @access private + * @param string $interface + * @return array + */ + private function filterProviders($interface) + { + $interface = '\Kanboard\Core\Security\\'.$interface; + + return array_filter($this->providers, function (AuthenticationProviderInterface $provider) use ($interface) { + return is_a($provider, $interface); + }); + } +} diff --git a/app/Core/Security/AuthenticationProviderInterface.php b/app/Core/Security/AuthenticationProviderInterface.php new file mode 100644 index 0000000..828e272 --- /dev/null +++ b/app/Core/Security/AuthenticationProviderInterface.php @@ -0,0 +1,28 @@ +accessMap = $accessMap; + } + + /** + * Check if the given role is allowed to access to the specified resource + * + * @access public + * @param string $controller + * @param string $method + * @param string $role + * @return boolean + */ + public function isAllowed($controller, $method, $role) + { + $roles = $this->accessMap->getRoles($controller, $method); + return in_array($role, $roles); + } +} diff --git a/app/Core/Security/OAuthAuthenticationProviderInterface.php b/app/Core/Security/OAuthAuthenticationProviderInterface.php new file mode 100644 index 0000000..3092672 --- /dev/null +++ b/app/Core/Security/OAuthAuthenticationProviderInterface.php @@ -0,0 +1,46 @@ + t('Administrator'), + self::APP_MANAGER => t('Manager'), + self::APP_USER => t('User'), + ); + } + + /** + * Get project roles + * + * @access public + * @return array + */ + public function getProjectRoles() + { + return array( + self::PROJECT_MANAGER => t('Project Manager'), + self::PROJECT_MEMBER => t('Project Member'), + self::PROJECT_VIEWER => t('Project Viewer'), + ); + } + + /** + * Check if the given role is custom or not + * + * @access public + * @param string $role + * @return bool + */ + public function isCustomProjectRole($role) + { + return ! empty($role) && $role !== self::PROJECT_MANAGER && $role !== self::PROJECT_MEMBER && $role !== self::PROJECT_VIEWER; + } + + /** + * Get role name + * + * @access public + * @param string $role + * @return string + */ + public function getRoleName($role) + { + $roles = $this->getApplicationRoles() + $this->getProjectRoles(); + return isset($roles[$role]) ? $roles[$role] : t('Unknown'); + } +} diff --git a/app/Core/Security/SessionCheckProviderInterface.php b/app/Core/Security/SessionCheckProviderInterface.php new file mode 100644 index 0000000..232fe1d --- /dev/null +++ b/app/Core/Security/SessionCheckProviderInterface.php @@ -0,0 +1,20 @@ +createSessionToken('csrf'); + } + + /** + * Generate and store a reusable CSRF token + * + * @access public + * @return string + */ + public function getReusableCSRFToken() + { + return $this->createSessionToken('pcsrf'); + } + + /** + * Check if the token exists for the current session (a token can be used only one time) + * + * @access public + * @param string $token CSRF token + * @return bool + */ + public function validateCSRFToken($token) + { + return $this->validateSessionToken('csrf', $token); + } + + /** + * Check if the token exists as a reusable CSRF token + * + * @access public + * @param string $token CSRF token + * @return bool + */ + public function validateReusableCSRFToken($token) + { + return $this->validateSessionToken('pcsrf', $token); + } + + /** + * Generate a session token of the given type + * + * @access protected + * @param string $type Token type + * @return string Random token + */ + protected function createSessionToken($type) + { + $nonce = self::getToken(self::$NONCE_LENGTH); + return $nonce . $this->signSessionToken($type, $nonce); + } + + /** + * Check a session token of the given type + * + * @access protected + * @param string $type Token type + * @param string $token Session token + * @return bool + */ + protected function validateSessionToken($type, $token) + { + if (!is_string($token)) { + return false; + } + + if (strlen($token) != (self::$NONCE_LENGTH + self::$HMAC_LENGTH) * 2) { + return false; + } + + $nonce = substr($token, 0, self::$NONCE_LENGTH * 2); + $hmac = substr($token, self::$NONCE_LENGTH * 2, self::$HMAC_LENGTH * 2); + + return hash_equals($this->signSessionToken($type, $nonce), $hmac); + } + + /** + * Sign a nonce with the key belonging to the given type + * + * @access protected + * @param string $type Token type + * @param string $nonce Nonce to sign + * @return string + */ + protected function signSessionToken($type, $nonce) + { + if (!session_exists($type . '_key')) { + session_set($type . '_key', self::getToken(self::$KEY_LENGTH)); + } + + $data = $nonce . '-' . session_id(); + $key = session_get($type . '_key'); + $hmac = hash_hmac(self::$HMAC_ALGO, $data, $key, true); + + return bin2hex(substr($hmac, 0, self::$HMAC_LENGTH)); + } +} diff --git a/app/Core/Session/FlashMessage.php b/app/Core/Session/FlashMessage.php new file mode 100644 index 0000000..037717c --- /dev/null +++ b/app/Core/Session/FlashMessage.php @@ -0,0 +1,76 @@ +setMessage('success', $message); + } + + /** + * Add failure message + * + * @access public + * @param string $message + */ + public function failure($message) + { + $this->setMessage('failure', $message); + } + + /** + * Add new flash message + * + * @access public + * @param string $key + * @param string $message + */ + public function setMessage($key, $message) + { + if (! session_exists('flash')) { + session_set('flash', []); + } + + session_merge('flash', [$key => $message]); + } + + /** + * Get flash message + * + * @access public + * @param string $key + * @return string + */ + public function getMessage($key) + { + $message = ''; + + if (session_exists('flash')) { + $messages = session_get('flash'); + + if (isset($messages[$key])) { + $message = $messages[$key]; + unset($messages[$key]); + session_set('flash', $messages); + } + } + + return $message; + } +} diff --git a/app/Core/Session/SessionHandler.php b/app/Core/Session/SessionHandler.php new file mode 100644 index 0000000..a0594b5 --- /dev/null +++ b/app/Core/Session/SessionHandler.php @@ -0,0 +1,107 @@ +db = $db; + } + + #[\ReturnTypeWillChange] + public function close() + { + return true; + } + + #[\ReturnTypeWillChange] + public function destroy($sessionID) + { + return $this->db->table(self::TABLE)->eq('id', $sessionID)->remove(); + } + + #[\ReturnTypeWillChange] + public function gc($maxlifetime) + { + return $this->db->table(self::TABLE)->lt('expire_at', time())->remove(); + } + + #[\ReturnTypeWillChange] + public function open($savePath, $name) + { + return true; + } + + #[\ReturnTypeWillChange] + public function read($sessionID) + { + $result = $this->db->table(self::TABLE)->eq('id', $sessionID)->gt('expire_at', time())->findOneColumn('data'); + + // Note: Returning false display an error message and write() is never called + // preventing new sessions to be created when calling session_start() + if (empty($result)) { + return ''; + } + + // Sanitize session data to prevent object deserialization attacks (CWE-502). + // Using allowed_classes: false converts any serialized objects to harmless + // __PHP_Incomplete_Class instances, preventing exploitation via gadget chains. + $sanitized = @unserialize($result, ['allowed_classes' => false]); + + // unserialize() returns false both on failure AND when the data legitimately + // represents boolean false (serialized as 'b:0;'). Check the raw string to + // distinguish a real deserialization error from a valid false value. + if ($sanitized === false && $result !== 'b:0;') { + // Data could not be unserialized (e.g. legacy format after handler change); + // discard it so a fresh session is created. + return ''; + } + + return serialize($sanitized); + } + + #[\ReturnTypeWillChange] + public function write($sessionID, $data) + { + if (SESSION_DURATION > 0) { + $lifetime = time() + SESSION_DURATION; + } else { + $lifetime = time() + (ini_get('session.gc_maxlifetime') ?: 1440); + } + + $this->db->startTransaction(); + + if ($this->db->table(self::TABLE)->eq('id', $sessionID)->exists()) { + $this->db->table(self::TABLE)->eq('id', $sessionID)->update([ + 'expire_at' => $lifetime, + 'data' => $data, + ]); + } else { + $this->db->table(self::TABLE)->insert([ + 'id' => $sessionID, + 'expire_at' => $lifetime, + 'data' => $data, + ]); + } + + $this->db->closeTransaction(); + + return true; + } +} diff --git a/app/Core/Session/SessionManager.php b/app/Core/Session/SessionManager.php new file mode 100644 index 0000000..0ce0254 --- /dev/null +++ b/app/Core/Session/SessionManager.php @@ -0,0 +1,117 @@ +db), true); + } + + $this->configure(); + + if (ini_get('session.auto_start') == 1) { + session_destroy(); + } + + session_name('KB_SID'); + session_start(); + } + + /** + * Destroy the session + * + * @access public + */ + public function close() + { + $this->dispatcher->dispatch(new Event(), self::EVENT_DESTROY); + + // Destroy the session cookie + $params = session_get_cookie_params(); + + setcookie( + session_name(), + '', + time() - 42000, + $params['path'], + $params['domain'], + $params['secure'], + $params['httponly'] + ); + + session_unset(); + session_destroy(); + } + + /** + * Define session settings + * + * @access private + */ + private function configure() + { + // Session cookie: HttpOnly and secure flags + session_set_cookie_params( + SESSION_DURATION, + $this->helper->url->dir() ?: '/', + null, + $this->request->isHTTPS(), + true + ); + + // Use php_serialize handler so session data is a single serialize() call, + // which allows safe sanitization in SessionHandler::read() (CWE-502 mitigation). + ini_set('session.serialize_handler', 'php_serialize'); + + // Avoid session id in the URL + ini_set('session.use_only_cookies', '1'); + ini_set('session.use_trans_sid', '0'); + + // Enable strict mode + ini_set('session.use_strict_mode', '1'); + + // Better session hash + ini_set('session.hash_function', '1'); // 'sha512' is not compatible with FreeBSD, only MD5 '0' and SHA-1 '1' seems to work + ini_set('session.hash_bits_per_character', 6); + + // Set an additional entropy + ini_set('session.entropy_file', '/dev/urandom'); + ini_set('session.entropy_length', '256'); + } +} diff --git a/app/Core/Template.php b/app/Core/Template.php new file mode 100644 index 0000000..d3ec26d --- /dev/null +++ b/app/Core/Template.php @@ -0,0 +1,124 @@ +helper = $helper; + } + + /** + * Expose helpers with magic getter + * + * @access public + * @param string $helper + * @return mixed + */ + public function __get($helper) + { + return $this->helper->getHelper($helper); + } + + /** + * Render a template + * + * Example: + * + * $template->render('template_name', ['bla' => 'value']); + * + * @access public + * @param string $__template_name Template name + * @param array $__template_args Key/Value map of template variables + * @return string + */ + public function render($__template_name, array $__template_args = array()) + { + extract($__template_args); + ob_start(); + include $this->getTemplateFile($__template_name); + return ob_get_clean(); + } + + /** + * Define a new template override + * + * @access public + * @param string $original_template + * @param string $new_template + */ + public function setTemplateOverride($original_template, $new_template) + { + $this->overrides[$original_template] = $new_template; + } + + /** + * Find template filename + * + * Core template: 'task/show' or 'kanboard:task/show' + * Plugin template: 'myplugin:task/show' + * + * @access public + * @param string $template + * @return string + */ + public function getTemplateFile($template) + { + $plugin = ''; + $template = isset($this->overrides[$template]) ? $this->overrides[$template] : $template; + + if (strpos($template, ':') !== false) { + list($plugin, $template) = explode(':', $template); + } + + if ($plugin !== 'kanboard' && $plugin !== '') { + return implode(DIRECTORY_SEPARATOR, array(PLUGINS_DIR, ucfirst($plugin), 'Template', $template.'.php')); + } + + return implode(DIRECTORY_SEPARATOR, array(__DIR__, '..', 'Template', $template.'.php')); + } +} diff --git a/app/Core/Thumbnail.php b/app/Core/Thumbnail.php new file mode 100644 index 0000000..1ea9181 --- /dev/null +++ b/app/Core/Thumbnail.php @@ -0,0 +1,177 @@ +fromFile($filename); + return $self; + } + + /** + * Create a thumbnail from a string + * + * @static + * @access public + * @param string $blob + * @return Thumbnail + */ + public static function createFromString($blob) + { + $self = new static(); + $self->fromString($blob); + return $self; + } + + /** + * Load the local image file in memory with GD + * + * @access public + * @param string $filename + * @return Thumbnail + */ + public function fromFile($filename) + { + $this->metadata = getimagesize($filename); + $this->srcImage = @imagecreatefromstring(file_get_contents($filename)); + return $this; + } + + /** + * Load the image blob in memory with GD + * + * @access public + * @param string $blob + * @return Thumbnail + */ + public function fromString($blob) + { + if (!function_exists('getimagesizefromstring')) { + $uri = 'data://application/octet-stream;base64,' . base64_encode($blob); + $this->metadata = getimagesize($uri); + } else { + $this->metadata = getimagesizefromstring($blob); + } + + // Avoid warning from libpng when loading PNG image with obscure or incorrect iCCP profiles + $this->srcImage = @imagecreatefromstring($blob); + return $this; + } + + /** + * Resize the image + * + * @access public + * @param int $width + * @param int $height + * @return Thumbnail + */ + public function resize($width = 250, $height = 100) + { + $srcWidth = $this->metadata[0]; + $srcHeight = $this->metadata[1]; + $dstX = 0; + $dstY = 0; + + if ($width == 0 && $height == 0) { + $width = 100; + $height = 100; + } + + if ($width > 0 && $height == 0) { + $dstWidth = $width; + $dstHeight = floor($srcHeight * ($width / $srcWidth)); + $this->dstImage = imagecreatetruecolor($dstWidth, $dstHeight); + } elseif ($width == 0 && $height > 0) { + $dstWidth = floor($srcWidth * ($height / $srcHeight)); + $dstHeight = $height; + $this->dstImage = imagecreatetruecolor($dstWidth, $dstHeight); + } else { + $srcRatio = $srcWidth / $srcHeight; + $resizeRatio = $width / $height; + + if ($srcRatio <= $resizeRatio) { + $dstWidth = $width; + $dstHeight = floor($srcHeight * ($width / $srcWidth)); + $dstY = ($dstHeight - $height) / 2 * (-1); + } else { + $dstWidth = floor($srcWidth * ($height / $srcHeight)); + $dstHeight = $height; + $dstX = ($dstWidth - $width) / 2 * (-1); + } + + $this->dstImage = imagecreatetruecolor($width, $height); + } + + imagealphablending($this->dstImage, false); + imagesavealpha($this->dstImage, true); + + imagecopyresampled($this->dstImage, $this->srcImage, (int) $dstX, (int) $dstY, 0, 0, (int) $dstWidth, (int) $dstHeight, (int) $srcWidth, (int) $srcHeight); + + return $this; + } + + /** + * Save the thumbnail to a local file + * + * @access public + * @param string $filename + * @return Thumbnail + */ + public function toFile($filename) + { + imagepng($this->dstImage, $filename, $this->compression); + imagedestroy($this->dstImage); + imagedestroy($this->srcImage); + return $this; + } + + /** + * Return the thumbnail as a string + * + * @access public + * @return string + */ + public function toString() + { + ob_start(); + imagepng($this->dstImage, null, $this->compression); + imagedestroy($this->dstImage); + imagedestroy($this->srcImage); + return ob_get_clean(); + } + + /** + * Output the thumbnail directly to the browser or stdout + * + * @access public + */ + public function toOutput() + { + imagepng($this->dstImage, null, $this->compression); + imagedestroy($this->dstImage); + imagedestroy($this->srcImage); + } +} diff --git a/app/Core/Tool.php b/app/Core/Tool.php new file mode 100644 index 0000000..6e45764 --- /dev/null +++ b/app/Core/Tool.php @@ -0,0 +1,109 @@ +isDir()) { + rmdir($file->getRealPath()); + } else { + unlink($file->getRealPath()); + } + } + + if ($removeDirectory) { + rmdir($directory); + } + } + + /** + * Build dependency injection containers from an array + * + * @static + * @access public + * @param Container $container + * @param array $namespaces + * @return Container + */ + public static function buildDIC(Container $container, array $namespaces) + { + foreach ($namespaces as $namespace => $classes) { + foreach ($classes as $name) { + $class = '\\Kanboard\\'.$namespace.'\\'.$name; + $container[lcfirst($name)] = function ($c) use ($class) { + return new $class($c); + }; + } + } + + return $container; + } + + /** + * Build dependency injection container from an array + * + * @static + * @access public + * @param Container $container + * @param array $namespaces + * @return Container + */ + public static function buildFactories(Container $container, array $namespaces) + { + foreach ($namespaces as $namespace => $classes) { + foreach ($classes as $name) { + $class = '\\Kanboard\\'.$namespace.'\\'.$name; + $container[lcfirst($name)] = $container->factory(function ($c) use ($class) { + return new $class($c); + }); + } + } + + return $container; + } + + /** + * Build dependency injection container for custom helpers from an array + * + * @static + * @access public + * @param Container $container + * @param array $namespaces + * @return Container + */ + public static function buildDICHelpers(Container $container, array $namespaces) + { + foreach ($namespaces as $namespace => $classes) { + foreach ($classes as $name) { + $class = '\\Kanboard\\'.$namespace.'\\'.$name; + $container['helper']->register($name, $class); + } + } + + return $container; + } +} diff --git a/app/Core/Translator.php b/app/Core/Translator.php new file mode 100644 index 0000000..a0b4631 --- /dev/null +++ b/app/Core/Translator.php @@ -0,0 +1,214 @@ +translate('I have %d kids', 5); + * + * @access public + * @param string $identifier Default string + * @return string + */ + public function translate($identifier) + { + $args = func_get_args(); + + array_shift($args); + array_unshift($args, $this->get($identifier, $identifier)); + + foreach ($args as &$arg) { + $arg = htmlspecialchars((string) $arg, ENT_QUOTES, 'UTF-8', false); + } + + return call_user_func_array( + 'sprintf', + $args + ); + } + + /** + * Get a translation with no HTML escaping + * + * $translator->translateNoEscaping('I have %d kids', 5); + * + * @access public + * @param string $identifier Default string + * @return string + */ + public function translateNoEscaping($identifier) + { + $args = func_get_args(); + + array_shift($args); + array_unshift($args, $this->get($identifier, $identifier)); + + return call_user_func_array( + 'sprintf', + $args + ); + } + + /** + * Get a formatted number + * + * $translator->number(1234.56); + * + * @access public + * @param float $number Number to format + * @return string + */ + public function number($number) + { + return number_format( + $number, + $this->get('number.decimals', 2), + $this->get('number.decimals_separator', '.'), + $this->get('number.thousands_separator', ',') + ); + } + + /** + * Get a formatted currency number + * + * $translator->currency(1234.56); + * + * @access public + * @param float $amount Number to format + * @return string + */ + public function currency($amount) + { + $position = $this->get('currency.position', 'before'); + $symbol = $this->get('currency.symbol', '$'); + $str = ''; + + if ($position === 'before') { + $str .= $symbol; + } + + $str .= $this->number($amount); + + if ($position === 'after') { + $str .= ' '.$symbol; + } + + return $str; + } + + /** + * Get an identifier from the translations or return the default + * + * @access public + * @param string $identifier Locale identifier + * @param string $default Default value + * @return string + */ + public function get($identifier, $default = '') + { + if (isset(self::$locales[$identifier])) { + return self::$locales[$identifier]; + } else { + return $default; + } + } + + /** + * Load translations + * + * @static + * @access public + * @param string $languageCode Locale code: fr_FR + * @param string $localeDir Locale folder + * @return boolean True if the translation file has been loaded + */ + public static function load($languageCode, $localeDir = '') + { + if (! preg_match('/^[a-z]{2}(_[a-zA-Z]{2,4}(_[a-zA-Z]{2,4})?)?$/', $languageCode)) { + return false; + } + + if ($localeDir === '') { + $localeDir = self::getDefaultFolder(); + } + + $baseDir = realpath($localeDir); + if ($baseDir === false) { + return false; + } + + $realFilePath = realpath(implode(DIRECTORY_SEPARATOR, [$localeDir, $languageCode, 'translations.php'])); + + if ($realFilePath !== false && strpos($realFilePath, $baseDir) === 0) { + self::$locales = array_merge(self::$locales, require($realFilePath)); + return true; + } + + return false; + } + + /** + * Clear locales stored in memory + * + * @static + * @access public + */ + public static function unload() + { + self::$locales = array(); + } + + /** + * Get default locales folder + * + * @access public + * @return string + */ + public static function getDefaultFolder() + { + return implode(DIRECTORY_SEPARATOR, array(__DIR__, '..', 'Locale')); + } +} diff --git a/app/Core/User/Avatar/AvatarManager.php b/app/Core/User/Avatar/AvatarManager.php new file mode 100644 index 0000000..5b61cbd --- /dev/null +++ b/app/Core/User/Avatar/AvatarManager.php @@ -0,0 +1,93 @@ +providers[] = $provider; + return $this; + } + + /** + * Render avatar HTML element + * + * @access public + * @param string $user_id + * @param string $username + * @param string $name + * @param string $email + * @param string $avatar_path + * @param int $size + * @return string + */ + public function render($user_id, $username, $name, $email, $avatar_path, $size) + { + $user = array( + 'id' => $user_id, + 'username' => $username, + 'name' => $name, + 'email' => $email, + 'avatar_path' => $avatar_path, + ); + + krsort($this->providers); + + foreach ($this->providers as $provider) { + if ($provider->isActive($user)) { + return $provider->render($user, $size); + } + } + + return ''; + } + + /** + * Render default provider for unknown users (first provider registered) + * + * @access public + * @param integer $size + * @return string + */ + public function renderDefault($size) + { + if (count($this->providers) > 0) { + ksort($this->providers); + $provider = current($this->providers); + + $user = array( + 'id' => 0, + 'username' => '', + 'name' => '?', + 'email' => '', + 'avatar_path' => '', + ); + + return $provider->render($user, $size); + } + + return ''; + } +} diff --git a/app/Core/User/Avatar/AvatarProviderInterface.php b/app/Core/User/Avatar/AvatarProviderInterface.php new file mode 100644 index 0000000..e0375d2 --- /dev/null +++ b/app/Core/User/Avatar/AvatarProviderInterface.php @@ -0,0 +1,30 @@ +groupMemberModel->getGroups($userId); + $this->addGroups($userId, $userGroups, $externalGroupIds); + $this->removeGroups($userId, $userGroups, $externalGroupIds); + } + + /** + * Add missing groups to the user + * + * @access protected + * @param integer $userId + * @param array $userGroups + * @param string[] $externalGroupIds + */ + protected function addGroups($userId, array $userGroups, array $externalGroupIds) + { + $userGroupIds = array_column($userGroups, 'external_id', 'external_id'); + $externalGroups = $this->groupModel->getByExternalIds($externalGroupIds); + + foreach ($externalGroups as $externalGroup) { + if (! isset($userGroupIds[$externalGroup['external_id']])) { + $this->groupMemberModel->addUser($externalGroup['id'], $userId); + } + } + } + + /** + * Remove groups from the user + * + * @access protected + * @param integer $userId + * @param array $userGroups + * @param string[] $externalGroupIds + */ + protected function removeGroups($userId, array $userGroups, array $externalGroupIds) + { + foreach ($userGroups as $userGroup) { + if (! empty($userGroup['external_id']) && ! in_array($userGroup['external_id'], $externalGroupIds)) { + $this->groupMemberModel->removeUser($userGroup['id'], $userId); + } + } + } +} diff --git a/app/Core/User/UserBackendProviderInterface.php b/app/Core/User/UserBackendProviderInterface.php new file mode 100644 index 0000000..5f8cab4 --- /dev/null +++ b/app/Core/User/UserBackendProviderInterface.php @@ -0,0 +1,21 @@ +providers[] = $provider; + return $this; + } + + /** + * Find a group from a search query + * + * @access public + * @param string $input + * @return UserProviderInterface[] + */ + public function find($input) + { + $groups = array(); + + foreach ($this->providers as $provider) { + $groups = array_merge($groups, $provider->find($input)); + } + + return $this->removeDuplicates($groups); + } + + /** + * Remove duplicated users + * + * @access protected + * @param array $users + * @return UserProviderInterface[] + */ + protected function removeDuplicates(array $users) + { + $result = array(); + + foreach ($users as $user) { + if (! isset($result[$user->getUsername()])) { + $result[$user->getUsername()] = $user; + } + } + + return array_values($result); + } +} diff --git a/app/Core/User/UserProfile.php b/app/Core/User/UserProfile.php new file mode 100644 index 0000000..26ee0f6 --- /dev/null +++ b/app/Core/User/UserProfile.php @@ -0,0 +1,68 @@ +userModel->getById($userId); + + $values = UserProperty::filterProperties($profile, UserProperty::getProperties($user)); + $values['id'] = $userId; + + if ($this->userModel->update($values)) { + $profile = array_merge($profile, $values); + $this->userSession->initialize($profile); + return true; + } + + return false; + } + + /** + * Synchronize user properties with the local database and create the user session + * + * @access public + * @param UserProviderInterface $user + * @return boolean + */ + public function initialize(UserProviderInterface $user) + { + if ($user->getInternalId()) { + $profile = $this->userModel->getById($user->getInternalId()); + } elseif ($user->getExternalIdColumn() && $user->getExternalId()) { + $profile = $this->userSync->synchronize($user); // The profile can be null when external user creation is disabled + if (LDAP_GROUP_SYNC && ! empty($profile)) { + $this->groupSync->synchronize($profile['id'], $user->getExternalGroupIds()); + } + } + + if (! empty($profile) && $profile['is_active'] == 1) { + $this->userSession->initialize($profile); + $this->dispatcher->dispatch(new UserProfileSyncEvent($profile, $user), self::EVENT_USER_PROFILE_AFTER_SYNC); + return true; + } + + return false; + } +} diff --git a/app/Core/User/UserProperty.php b/app/Core/User/UserProperty.php new file mode 100644 index 0000000..685690c --- /dev/null +++ b/app/Core/User/UserProperty.php @@ -0,0 +1,74 @@ + $user->getUsername(), + 'name' => $user->getName(), + 'email' => $user->getEmail(), + 'role' => $user->getRole(), + $user->getExternalIdColumn() => $user->getExternalId(), + ); + + $properties = array_merge($properties, $user->getExtraAttributes()); + + return array_filter($properties, array(__NAMESPACE__.'\UserProperty', 'isNotEmptyValue')); + } + + /** + * Filter user properties compared to existing user profile + * + * @static + * @access public + * @param array $profile + * @param array $properties + * @return array + */ + public static function filterProperties(array $profile, array $properties) + { + $excludedProperties = explode_csv_field(EXTERNAL_AUTH_EXCLUDE_FIELDS); + $values = array(); + + foreach ($properties as $property => $value) { + if (self::isNotEmptyValue($value) && + ! in_array($property, $excludedProperties) && + array_key_exists($property, $profile) && + $value !== $profile[$property]) { + $values[$property] = $value; + } + } + + return $values; + } + + /** + * Check if a value is not empty + * + * @static + * @access public + * @param string $value + * @return boolean + */ + public static function isNotEmptyValue($value) + { + return $value !== null && $value !== ''; + } +} diff --git a/app/Core/User/UserProviderInterface.php b/app/Core/User/UserProviderInterface.php new file mode 100644 index 0000000..07e01f4 --- /dev/null +++ b/app/Core/User/UserProviderInterface.php @@ -0,0 +1,103 @@ +getId() == $user_id) { + $this->initialize($this->userModel->getById($user_id)); + } + } + + /** + * Update user session + * + * @access public + * @param array $user + */ + public function initialize(array $user) + { + foreach (array('password', 'is_admin', 'is_project_admin', 'twofactor_secret') as $column) { + if (isset($user[$column])) { + unset($user[$column]); + } + } + + $user['id'] = (int) $user['id']; + $user['is_ldap_user'] = isset($user['is_ldap_user']) ? (bool) $user['is_ldap_user'] : false; + $user['twofactor_activated'] = isset($user['twofactor_activated']) ? (bool) $user['twofactor_activated'] : false; + + if (session_status() === PHP_SESSION_ACTIVE) { + // Note: Do not delete the old session to avoid possible race condition and a PHP warning. + session_regenerate_id(false); + } + + session_set('user', $user); + session_set('postAuthenticationValidated', false); + } + + /** + * Get user properties + * + * @access public + * @return array + */ + public function getAll() + { + return session_get('user'); + } + + /** + * Get user application role + * + * @access public + * @return string + */ + public function getRole() + { + if (! $this->isLogged()) { + return ''; + } + + return session_get('user')['role']; + } + + /** + * Return true if the user has validated the 2FA key + * + * @access public + * @return bool + */ + public function isPostAuthenticationValidated() + { + return session_is_true('postAuthenticationValidated'); + } + + /** + * Validate 2FA for the current session + * + * @access public + */ + public function setPostAuthenticationAsValidated() + { + session_set('postAuthenticationValidated', true); + } + + /** + * Return true if the user has 2FA enabled + * + * @access public + * @return bool + */ + public function hasPostAuthentication() + { + if (! $this->isLogged()) { + return false; + } + + return session_get('user')['twofactor_activated'] === true; + } + + /** + * Disable 2FA for the current session + * + * @access public + */ + public function disablePostAuthentication() + { + session_merge('user', ['twofactor_activated' => false]); + } + + /** + * Return true if the logged user is admin + * + * @access public + * @return bool + */ + public function isAdmin() + { + return $this->getRole() === Role::APP_ADMIN; + } + + /** + * Get the connected user id + * + * @access public + * @return integer + */ + public function getId() + { + if (! $this->isLogged()) { + return 0; + } + + return session_get('user')['id']; + } + + /** + * Get username + * + * @access public + * @return string + */ + public function getUsername() + { + if (! $this->isLogged()) { + return ''; + } + + return session_get('user')['username']; + } + + /** + * Get user language + * + * @access public + * @return string + */ + public function getLanguage() + { + if (! $this->isLogged()) { + return ''; + } + + return session_get('user')['language']; + } + + /** + * Get user timezone + * + * @access public + * @return string + */ + public function getTimezone() + { + if (! $this->isLogged()) { + return ''; + } + + return session_get('user')['timezone']; + } + + /** + * Get user theme + * + * @access public + * @return string + */ + public function getTheme() + { + if (! $this->isLogged()) { + return 'light'; + } + + $user_session = session_get('user'); + + if (array_key_exists('theme', $user_session)) { + return $user_session['theme']; + } + + return 'light'; + } + + /** + * Return true if subtask list toggle is active + * + * @access public + * @return string + */ + public function hasSubtaskListActivated() + { + return session_is_true('subtaskListToggle'); + } + + /** + * Check is the user is connected + * + * @access public + * @return bool + */ + public function isLogged() + { + return session_exists('user') && session_get('user') !== []; + } + + /** + * Get project filters from the session + * + * @access public + * @param integer $projectID + * @return string + */ + public function getFilters($projectID) + { + if (! session_exists('filters:'.$projectID)) { + return session_get('user') ? session_get('user')['filter'] ?: 'status:open' : 'status:open'; + } + + return session_get('filters:'.$projectID); + } + + /** + * Save project filters in the session + * + * @access public + * @param integer $projectID + * @param string $filters + */ + public function setFilters($projectID, $filters) + { + session_set('filters:'.$projectID, $filters); + } + + /** + * Get project list order from the session + * + * @access public + * @param integer $projectID + * @return array + */ + public function getListOrder($projectID) + { + $default = ['tasks.id', 'DESC']; + + if (! session_exists('listOrder:'.$projectID)) { + return $default; + } + + return session_get('listOrder:'.$projectID); + } + + /** + * Save project list order in the session + * + * @access public + * @param integer $projectID + * @param string $listOrder + * @param string $listDirection + */ + public function setListOrder($projectID, $listOrder, $listDirection) + { + session_set('listOrder:'.$projectID, [$listOrder, $listDirection]); + } +} diff --git a/app/Core/User/UserSync.php b/app/Core/User/UserSync.php new file mode 100644 index 0000000..a536693 --- /dev/null +++ b/app/Core/User/UserSync.php @@ -0,0 +1,82 @@ +userModel->getByExternalId($user->getExternalIdColumn(), $user->getExternalId()); + $properties = UserProperty::getProperties($user); + + if (! empty($profile)) { + $profile = $this->updateUser($profile, $properties); + } elseif ($user->isUserCreationAllowed()) { + $profile = $this->createUser($user, $properties); + } + + return $profile; + } + + /** + * Update user profile + * + * @access public + * @param array $profile + * @param array $properties + * @return array + */ + private function updateUser(array $profile, array $properties) + { + $values = UserProperty::filterProperties($profile, $properties); + + if (! empty($values)) { + $values['id'] = $profile['id']; + $result = $this->userModel->update($values); + return $result ? array_merge($profile, $properties) : $profile; + } + + return $profile; + } + + /** + * Create user + * + * @access public + * @param UserProviderInterface $user + * @param array $properties + * @return array + */ + private function createUser(UserProviderInterface $user, array $properties) + { + $userId = $this->userModel->create($properties); + + if ($userId === false) { + $this->logger->error('Unable to create user profile: '.$user->getExternalId()); + return array(); + } + + if ($this->configModel->get('notifications_enabled', 0) == 1) { + $this->userNotificationTypeModel->saveSelectedTypes($userId, [MailNotification::TYPE, WebNotification::TYPE]); + } + + return $this->userModel->getById($userId); + } +} diff --git a/app/Decorator/ColumnMoveRestrictionCacheDecorator.php b/app/Decorator/ColumnMoveRestrictionCacheDecorator.php new file mode 100644 index 0000000..82140d1 --- /dev/null +++ b/app/Decorator/ColumnMoveRestrictionCacheDecorator.php @@ -0,0 +1,59 @@ +cache = $cache; + $this->columnMoveRestrictionModel = $columnMoveRestrictionModel; + } + + /** + * Proxy method to get sortable columns + * + * @param int $project_id + * @param string $role + * @return array|mixed + */ + public function getSortableColumns($project_id, $role) + { + $key = $this->cachePrefix.$project_id.$role; + $columnIds = $this->cache->get($key); + + if ($columnIds === null) { + $columnIds = $this->columnMoveRestrictionModel->getSortableColumns($project_id, $role); + $this->cache->set($key, $columnIds); + } + + return $columnIds; + } +} diff --git a/app/Decorator/ColumnRestrictionCacheDecorator.php b/app/Decorator/ColumnRestrictionCacheDecorator.php new file mode 100644 index 0000000..a615030 --- /dev/null +++ b/app/Decorator/ColumnRestrictionCacheDecorator.php @@ -0,0 +1,59 @@ +cache = $cache; + $this->columnRestrictionModel = $columnMoveRestrictionModel; + } + + /** + * Proxy method to get sortable columns + * + * @param int $project_id + * @param string $role + * @return array|mixed + */ + public function getAllByRole($project_id, $role) + { + $key = $this->cachePrefix.$project_id.$role; + $columnRestrictions = $this->cache->get($key); + + if ($columnRestrictions === null) { + $columnRestrictions = $this->columnRestrictionModel->getAllByRole($project_id, $role); + $this->cache->set($key, $columnRestrictions); + } + + return $columnRestrictions; + } +} diff --git a/app/Decorator/MetadataCacheDecorator.php b/app/Decorator/MetadataCacheDecorator.php new file mode 100644 index 0000000..0897b51 --- /dev/null +++ b/app/Decorator/MetadataCacheDecorator.php @@ -0,0 +1,96 @@ +cache = $cache; + $this->metadataModel = $metadataModel; + $this->cachePrefix = $cachePrefix; + $this->entityId = $entityId; + } + + /** + * Get metadata value by key + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function get($key, $default) + { + $metadata = $this->cache->get($this->getCacheKey()); + + if ($metadata === null) { + $metadata = $this->metadataModel->getAll($this->entityId); + $this->cache->set($this->getCacheKey(), $metadata); + } + + return isset($metadata[$key]) ? $metadata[$key] : $default; + } + + /** + * Set new metadata value + * + * @param $key + * @param $value + */ + public function set($key, $value) + { + $this->metadataModel->save($this->entityId, array( + $key => $value, + )); + + $metadata = $this->metadataModel->getAll($this->entityId); + $this->cache->set($this->getCacheKey(), $metadata); + } + + /** + * Get cache key + * + * @return string + */ + protected function getCacheKey() + { + return $this->cachePrefix.$this->entityId; + } +} diff --git a/app/Decorator/ProjectRoleRestrictionCacheDecorator.php b/app/Decorator/ProjectRoleRestrictionCacheDecorator.php new file mode 100644 index 0000000..a6e2404 --- /dev/null +++ b/app/Decorator/ProjectRoleRestrictionCacheDecorator.php @@ -0,0 +1,59 @@ +cache = $cache; + $this->projectRoleRestrictionModel = $projectRoleRestrictionModel; + } + + /** + * Proxy method to get sortable columns + * + * @param int $project_id + * @param string $role + * @return array|mixed + */ + public function getAllByRole($project_id, $role) + { + $key = $this->cachePrefix.$project_id.$role; + $projectRestrictions = $this->cache->get($key); + + if ($projectRestrictions === null) { + $projectRestrictions = $this->projectRoleRestrictionModel->getAllByRole($project_id, $role); + $this->cache->set($key, $projectRestrictions); + } + + return $projectRestrictions; + } +} diff --git a/app/Decorator/UserCacheDecorator.php b/app/Decorator/UserCacheDecorator.php new file mode 100644 index 0000000..1cfe31c --- /dev/null +++ b/app/Decorator/UserCacheDecorator.php @@ -0,0 +1,59 @@ +cache = $cache; + $this->userModel = $userModel; + } + + /** + * Get a specific user by the username + * + * @access public + * @param string $username Username + * @return array + */ + public function getByUsername($username) + { + $key = $this->cachePrefix.$username; + $user = $this->cache->get($key); + + if ($user === null) { + $user = $this->userModel->getByUsername($username); + $this->cache->set($key, $user); + } + + return $user; + } +} diff --git a/app/Event/AuthFailureEvent.php b/app/Event/AuthFailureEvent.php new file mode 100644 index 0000000..4b073aa --- /dev/null +++ b/app/Event/AuthFailureEvent.php @@ -0,0 +1,44 @@ +username = $username; + } + + /** + * Get username + * + * @access public + * @return string + */ + public function getUsername() + { + return $this->username; + } +} diff --git a/app/Event/AuthSuccessEvent.php b/app/Event/AuthSuccessEvent.php new file mode 100644 index 0000000..8655104 --- /dev/null +++ b/app/Event/AuthSuccessEvent.php @@ -0,0 +1,43 @@ +authType = $authType; + } + + /** + * Get authentication type + * + * @return string + */ + public function getAuthType() + { + return $this->authType; + } +} diff --git a/app/Event/CommentEvent.php b/app/Event/CommentEvent.php new file mode 100644 index 0000000..3ac6a23 --- /dev/null +++ b/app/Event/CommentEvent.php @@ -0,0 +1,7 @@ +container = $values; + } + + public function getTaskId() + { + if (isset($this->container['task']['id'])) { + return $this->container['task']['id']; + } + + if (isset($this->container['task_id'])) { + return $this->container['task_id']; + } + + return null; + } + + public function getProjectId() + { + if (isset($this->container['task']['project_id'])) { + return $this->container['task']['project_id']; + } + + return null; + } + + public function getAll() + { + return $this->container; + } + + #[\ReturnTypeWillChange] + public function offsetSet($offset, $value) + { + if (is_null($offset)) { + $this->container[] = $value; + } else { + $this->container[$offset] = $value; + } + } + + #[\ReturnTypeWillChange] + public function offsetExists($offset) + { + return isset($this->container[$offset]); + } + + #[\ReturnTypeWillChange] + public function offsetUnset($offset) + { + unset($this->container[$offset]); + } + + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + return isset($this->container[$offset]) ? $this->container[$offset] : null; + } +} diff --git a/app/Event/ProjectFileEvent.php b/app/Event/ProjectFileEvent.php new file mode 100644 index 0000000..e1d29c4 --- /dev/null +++ b/app/Event/ProjectFileEvent.php @@ -0,0 +1,15 @@ +container['file']['project_id'])) { + return $this->container['file']['project_id']; + } + + return null; + } +} diff --git a/app/Event/SubtaskEvent.php b/app/Event/SubtaskEvent.php new file mode 100644 index 0000000..f34bb60 --- /dev/null +++ b/app/Event/SubtaskEvent.php @@ -0,0 +1,7 @@ +container['tasks'] =& $tasks; + } +} diff --git a/app/Event/UserProfileSyncEvent.php b/app/Event/UserProfileSyncEvent.php new file mode 100644 index 0000000..96ab423 --- /dev/null +++ b/app/Event/UserProfileSyncEvent.php @@ -0,0 +1,64 @@ +profile = $profile; + $this->user = $user; + } + + /** + * Get user profile + * + * @access public + * @return array + */ + public function getProfile() + { + return $this->profile; + } + + /** + * Get user provider object + * + * @access public + * @return UserProviderInterface|LdapUserProvider + */ + public function getUser() + { + return $this->user; + } +} diff --git a/app/EventBuilder/BaseEventBuilder.php b/app/EventBuilder/BaseEventBuilder.php new file mode 100644 index 0000000..5aa777a --- /dev/null +++ b/app/EventBuilder/BaseEventBuilder.php @@ -0,0 +1,44 @@ +commentId = $commentId; + return $this; + } + + /** + * Build event data + * + * @access public + * @return CommentEvent|null + */ + public function buildEvent() + { + $comment = $this->commentModel->getById($this->commentId); + + if (empty($comment)) { + return null; + } + + return new CommentEvent(array( + 'comment' => $comment, + 'task' => $this->taskFinderModel->getDetails($comment['task_id']), + )); + } + + /** + * Get event title with author + * + * @access public + * @param string $author + * @param string $eventName + * @param array $eventData + * @return string + */ + public function buildTitleWithAuthor($author, $eventName, array $eventData) + { + switch ($eventName) { + case CommentModel::EVENT_UPDATE: + return e('%s updated a comment on the task #%d', $author, $eventData['task']['id']); + case CommentModel::EVENT_CREATE: + return e('%s commented on the task #%d', $author, $eventData['task']['id']); + case CommentModel::EVENT_DELETE: + return e('%s removed a comment on the task #%d', $author, $eventData['task']['id']); + case CommentModel::EVENT_USER_MENTION: + return e('%s mentioned you in a comment on the task #%d', $author, $eventData['task']['id']); + default: + return ''; + } + } + + /** + * Get event title without author + * + * @access public + * @param string $eventName + * @param array $eventData + * @return string + */ + public function buildTitleWithoutAuthor($eventName, array $eventData) + { + switch ($eventName) { + case CommentModel::EVENT_CREATE: + return e('New comment on task #%d', $eventData['comment']['task_id']); + case CommentModel::EVENT_UPDATE: + return e('Comment updated on task #%d', $eventData['comment']['task_id']); + case CommentModel::EVENT_DELETE: + return e('Comment removed on task #%d', $eventData['comment']['task_id']); + case CommentModel::EVENT_USER_MENTION: + return e('You were mentioned in a comment on the task #%d', $eventData['task']['id']); + default: + return ''; + } + } +} diff --git a/app/EventBuilder/EventIteratorBuilder.php b/app/EventBuilder/EventIteratorBuilder.php new file mode 100644 index 0000000..73f856e --- /dev/null +++ b/app/EventBuilder/EventIteratorBuilder.php @@ -0,0 +1,63 @@ +builders[] = $builder; + return $this; + } + + #[\ReturnTypeWillChange] + public function rewind() + { + $this->position = 0; + } + + /** + * @return BaseEventBuilder + */ + #[\ReturnTypeWillChange] + public function current() + { + return $this->builders[$this->position]; + } + + #[\ReturnTypeWillChange] + public function key() + { + return $this->position; + } + + #[\ReturnTypeWillChange] + public function next() + { + ++$this->position; + } + + #[\ReturnTypeWillChange] + public function valid() + { + return isset($this->builders[$this->position]); + } +} diff --git a/app/EventBuilder/ProjectFileEventBuilder.php b/app/EventBuilder/ProjectFileEventBuilder.php new file mode 100644 index 0000000..6698f78 --- /dev/null +++ b/app/EventBuilder/ProjectFileEventBuilder.php @@ -0,0 +1,77 @@ +fileId = $fileId; + return $this; + } + + /** + * Build event data + * + * @access public + * @return GenericEvent|null + */ + public function buildEvent() + { + $file = $this->projectFileModel->getById($this->fileId); + + if (empty($file)) { + $this->logger->debug(__METHOD__.': File not found'); + return null; + } + + return new ProjectFileEvent(array( + 'file' => $file, + 'project' => $this->projectModel->getById($file['project_id']), + )); + } + + /** + * Get event title with author + * + * @access public + * @param string $author + * @param string $eventName + * @param array $eventData + * @return string + */ + public function buildTitleWithAuthor($author, $eventName, array $eventData) + { + return ''; + } + + /** + * Get event title without author + * + * @access public + * @param string $eventName + * @param array $eventData + * @return string + */ + public function buildTitleWithoutAuthor($eventName, array $eventData) + { + return ''; + } +} diff --git a/app/EventBuilder/SubtaskEventBuilder.php b/app/EventBuilder/SubtaskEventBuilder.php new file mode 100644 index 0000000..c1a41ae --- /dev/null +++ b/app/EventBuilder/SubtaskEventBuilder.php @@ -0,0 +1,125 @@ +subtaskId = $subtaskId; + return $this; + } + + /** + * Set values + * + * @param array $values + * @return $this + */ + public function withValues(array $values) + { + $this->values = $values; + return $this; + } + + /** + * Build event data + * + * @access public + * @return GenericEvent|null + */ + public function buildEvent() + { + $eventData = array(); + $eventData['subtask'] = $this->subtaskModel->getByIdWithDetails($this->subtaskId); + + if (empty($eventData['subtask'])) { + $this->logger->debug(__METHOD__.': Subtask not found'); + return null; + } + + if (! empty($this->values)) { + $eventData['changes'] = array_diff_assoc($this->values, $eventData['subtask']); + } + + $eventData['task'] = $this->taskFinderModel->getDetails($eventData['subtask']['task_id']); + return new SubtaskEvent($eventData); + } + + /** + * Get event title with author + * + * @access public + * @param string $author + * @param string $eventName + * @param array $eventData + * @return string + */ + public function buildTitleWithAuthor($author, $eventName, array $eventData) + { + switch ($eventName) { + case SubtaskModel::EVENT_UPDATE: + return e('%s updated a subtask for the task #%d', $author, $eventData['task']['id']); + case SubtaskModel::EVENT_CREATE: + return e('%s created a subtask for the task #%d', $author, $eventData['task']['id']); + case SubtaskModel::EVENT_DELETE: + return e('%s removed a subtask for the task #%d', $author, $eventData['task']['id']); + default: + return ''; + } + } + + /** + * Get event title without author + * + * @access public + * @param string $eventName + * @param array $eventData + * @return string + */ + public function buildTitleWithoutAuthor($eventName, array $eventData) + { + switch ($eventName) { + case SubtaskModel::EVENT_CREATE: + return e('New subtask on task #%d', $eventData['subtask']['task_id']); + case SubtaskModel::EVENT_UPDATE: + return e('Subtask updated on task #%d', $eventData['subtask']['task_id']); + case SubtaskModel::EVENT_DELETE: + return e('Subtask removed on task #%d', $eventData['subtask']['task_id']); + default: + return ''; + } + } +} diff --git a/app/EventBuilder/TaskEventBuilder.php b/app/EventBuilder/TaskEventBuilder.php new file mode 100644 index 0000000..192cbd1 --- /dev/null +++ b/app/EventBuilder/TaskEventBuilder.php @@ -0,0 +1,233 @@ +taskId = $taskId; + return $this; + } + + /** + * Set task + * + * @param array $task + * @return $this + */ + public function withTask(array $task) + { + $this->task = $task; + return $this; + } + + /** + * Set values + * + * @param array $values + * @return $this + */ + public function withValues(array $values) + { + $this->values = $values; + return $this; + } + + /** + * Set changes + * + * @param array $changes + * @return $this + */ + public function withChanges(array $changes) + { + $this->changes = $changes; + return $this; + } + + /** + * Build event data + * + * @access public + * @return TaskEvent|null + */ + public function buildEvent() + { + $eventData = array(); + $eventData['task_id'] = $this->taskId; + $eventData['task'] = $this->taskFinderModel->getDetails($this->taskId); + + if (empty($eventData['task'])) { + $this->logger->debug(__METHOD__.': Task not found'); + return null; + } + + if (! empty($this->changes)) { + if (empty($this->task)) { + $this->task = $eventData['task']; + } + + $eventData['changes'] = array_diff_assoc($this->changes, $this->task); + unset($eventData['changes']['date_modification']); + } + + return new TaskEvent(array_merge($eventData, $this->values)); + } + + /** + * Get event title with author + * + * @access public + * @param string $author + * @param string $eventName + * @param array $eventData + * @return string + */ + public function buildTitleWithAuthor($author, $eventName, array $eventData) + { + switch ($eventName) { + case TaskModel::EVENT_ASSIGNEE_CHANGE: + $assignee = $eventData['task']['assignee_name'] ?: $eventData['task']['assignee_username']; + + if (! empty($assignee)) { + return e('%s changed the assignee of the task #%d to %s', $author, $eventData['task']['id'], $assignee); + } + + return e('%s removed the assignee of the task %s', $author, e('#%d', $eventData['task']['id'])); + case TaskModel::EVENT_UPDATE: + return e('%s updated the task #%d', $author, $eventData['task']['id']); + case TaskModel::EVENT_CREATE: + return e('%s created the task #%d', $author, $eventData['task']['id']); + case TaskModel::EVENT_CLOSE: + return e('%s closed the task #%d', $author, $eventData['task']['id']); + case TaskModel::EVENT_OPEN: + return e('%s opened the task #%d', $author, $eventData['task']['id']); + case TaskModel::EVENT_MOVE_PROJECT: + return e( + '%s moved the task #%d "%s" to the project "%s"', + $author, + $eventData['task']['id'], + $eventData['task']['title'], + $eventData['task']['project_name'] + ); + case TaskModel::EVENT_MOVE_COLUMN: + return e( + '%s moved the task #%d to the column "%s"', + $author, + $eventData['task']['id'], + $eventData['task']['column_title'] + ); + case TaskModel::EVENT_MOVE_POSITION: + return e( + '%s moved the task #%d to the position %d in the column "%s"', + $author, + $eventData['task']['id'], + $eventData['task']['position'], + $eventData['task']['column_title'] + ); + case TaskModel::EVENT_MOVE_SWIMLANE: + if ($eventData['task']['swimlane_id'] == 0) { + return e('%s moved the task #%d to the first swimlane', $author, $eventData['task']['id']); + } + + return e( + '%s moved the task #%d to the swimlane "%s"', + $author, + $eventData['task']['id'], + $eventData['task']['swimlane_name'] + ); + + case TaskModel::EVENT_USER_MENTION: + return e('%s mentioned you in the task #%d', $author, $eventData['task']['id']); + default: + return ''; + } + } + + /** + * Get event title without author + * + * @access public + * @param string $eventName + * @param array $eventData + * @return string + */ + public function buildTitleWithoutAuthor($eventName, array $eventData) + { + switch ($eventName) { + case TaskModel::EVENT_CREATE: + return e('New task #%d: %s', $eventData['task']['id'], $eventData['task']['title']); + case TaskModel::EVENT_UPDATE: + return e('Task updated #%d', $eventData['task']['id']); + case TaskModel::EVENT_CLOSE: + return e('Task #%d closed', $eventData['task']['id']); + case TaskModel::EVENT_OPEN: + return e('Task #%d opened', $eventData['task']['id']); + case TaskModel::EVENT_MOVE_PROJECT: + return e('Task #%d "%s" has been moved to the project "%s"', $eventData['task']['id'], $eventData['task']['title'], $eventData['task']['project_name']); + case TaskModel::EVENT_MOVE_COLUMN: + return e('Column changed for task #%d', $eventData['task']['id']); + case TaskModel::EVENT_MOVE_POSITION: + return e('New position for task #%d', $eventData['task']['id']); + case TaskModel::EVENT_MOVE_SWIMLANE: + return e('Swimlane changed for task #%d', $eventData['task']['id']); + case TaskModel::EVENT_ASSIGNEE_CHANGE: + return e('Assignee changed on task #%d', $eventData['task']['id']); + case TaskModel::EVENT_OVERDUE: + $nb = count($eventData['tasks']); + return $nb > 1 ? e('%d overdue tasks', $nb) : e('Task #%d "%s" is overdue', $eventData['tasks'][0]['id'], $eventData['tasks'][0]['title']); + case TaskModel::EVENT_USER_MENTION: + return e('You were mentioned in the task #%d', $eventData['task']['id']); + default: + return ''; + } + } +} diff --git a/app/EventBuilder/TaskFileEventBuilder.php b/app/EventBuilder/TaskFileEventBuilder.php new file mode 100644 index 0000000..de514b5 --- /dev/null +++ b/app/EventBuilder/TaskFileEventBuilder.php @@ -0,0 +1,94 @@ +fileId = $fileId; + return $this; + } + + /** + * Build event data + * + * @access public + * @return GenericEvent|null + */ + public function buildEvent() + { + $file = $this->taskFileModel->getById($this->fileId); + + if (empty($file)) { + $this->logger->debug(__METHOD__.': File not found'); + return null; + } + + return new TaskFileEvent(array( + 'file' => $file, + 'task' => $this->taskFinderModel->getDetails($file['task_id']), + )); + } + + /** + * Get event title with author + * + * @access public + * @param string $author + * @param string $eventName + * @param array $eventData + * @return string + */ + public function buildTitleWithAuthor($author, $eventName, array $eventData) + { + if ($eventName === TaskFileModel::EVENT_CREATE) { + return e('%s attached a file to the task #%d', $author, $eventData['task']['id']); + } + + if ($eventName === TaskFileModel::EVENT_DESTROY) { + return e('%s removed a file from the task #%d', $author, $eventData['task']['id']); + } + + return ''; + } + + /** + * Get event title without author + * + * @access public + * @param string $eventName + * @param array $eventData + * @return string + */ + public function buildTitleWithoutAuthor($eventName, array $eventData) + { + if ($eventName === TaskFileModel::EVENT_CREATE) { + return e('New attachment on task #%d: %s', $eventData['file']['task_id'], $eventData['file']['name']); + } + + if ($eventName === TaskFileModel::EVENT_DESTROY) { + return e('Attachment removed from task #%d: %s', $eventData['file']['task_id'], $eventData['file']['name']); + } + + return ''; + } +} diff --git a/app/EventBuilder/TaskLinkEventBuilder.php b/app/EventBuilder/TaskLinkEventBuilder.php new file mode 100644 index 0000000..2cec20d --- /dev/null +++ b/app/EventBuilder/TaskLinkEventBuilder.php @@ -0,0 +1,89 @@ +taskLinkId = $taskLinkId; + return $this; + } + + /** + * Build event data + * + * @access public + * @return TaskLinkEvent|null + */ + public function buildEvent() + { + $taskLink = $this->taskLinkModel->getById($this->taskLinkId); + + if (empty($taskLink)) { + $this->logger->debug(__METHOD__.': TaskLink not found'); + return null; + } + + return new TaskLinkEvent(array( + 'task_link' => $taskLink, + 'task' => $this->taskFinderModel->getDetails($taskLink['task_id']), + )); + } + + /** + * Get event title with author + * + * @access public + * @param string $author + * @param string $eventName + * @param array $eventData + * @return string + */ + public function buildTitleWithAuthor($author, $eventName, array $eventData) + { + if ($eventName === TaskLinkModel::EVENT_CREATE_UPDATE) { + return e('%s set a new internal link for the task #%d', $author, $eventData['task']['id']); + } elseif ($eventName === TaskLinkModel::EVENT_DELETE) { + return e('%s removed an internal link for the task #%d', $author, $eventData['task']['id']); + } + + return ''; + } + + /** + * Get event title without author + * + * @access public + * @param string $eventName + * @param array $eventData + * @return string + */ + public function buildTitleWithoutAuthor($eventName, array $eventData) + { + if ($eventName === TaskLinkModel::EVENT_CREATE_UPDATE) { + return e('A new internal link for the task #%d has been defined', $eventData['task']['id']); + } elseif ($eventName === TaskLinkModel::EVENT_DELETE) { + return e('Internal link removed for the task #%d', $eventData['task']['id']); + } + + return ''; + } +} diff --git a/app/Export/SubtaskExport.php b/app/Export/SubtaskExport.php new file mode 100644 index 0000000..64af357 --- /dev/null +++ b/app/Export/SubtaskExport.php @@ -0,0 +1,124 @@ +subtask_status = $this->subtaskModel->getStatusList(); + $subtasks = $this->getSubtasks($project_id, $from, $to); + $results = array($this->getColumns()); + + foreach ($subtasks as $subtask) { + $results[] = $this->format($subtask); + } + + return $results; + } + + /** + * Get column titles + * + * @access public + * @return string[] + */ + public function getColumns() + { + return array( + e('Subtask Id'), + e('Title'), + e('Status'), + e('Assignee'), + e('Time estimated'), + e('Time spent'), + e('Task Id'), + e('Task Title'), + ); + } + + /** + * Format the output of a subtask array + * + * @access public + * @param array $subtask Subtask properties + * @return array + */ + public function format(array $subtask) + { + $values = array(); + $values[] = $subtask['id']; + $values[] = $subtask['title']; + $values[] = t($this->subtask_status[$subtask['status']]); + $values[] = $subtask['assignee_name'] ?: $subtask['assignee_username']; + $values[] = $subtask['time_estimated']; + $values[] = $subtask['time_spent']; + $values[] = $subtask['task_id']; + $values[] = $subtask['task_title']; + + return $values; + } + + /** + * Get all subtasks for a given project + * + * @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 getSubtasks($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(SubtaskModel::TABLE) + ->eq('project_id', $project_id) + ->columns( + SubtaskModel::TABLE.'.*', + UserModel::TABLE.'.username AS assignee_username', + UserModel::TABLE.'.name AS assignee_name', + TaskModel::TABLE.'.title AS task_title' + ) + ->gte('date_creation', $from) + ->lte('date_creation', $to) + ->join(TaskModel::TABLE, 'id', 'task_id') + ->join(UserModel::TABLE, 'id', 'user_id') + ->asc(SubtaskModel::TABLE.'.id') + ->findAll(); + } +} diff --git a/app/Export/TaskExport.php b/app/Export/TaskExport.php new file mode 100644 index 0000000..f068557 --- /dev/null +++ b/app/Export/TaskExport.php @@ -0,0 +1,169 @@ +getTasks($project_id, $from, $to); + $taskIds = array_column($tasks, 'id'); + $tags = $this->taskTagModel->getTagsByTaskIds($taskIds); + $colors = $this->colorModel->getList(); + $results = array($this->getColumns()); + + foreach ($tasks as &$task) { + $task = $this->format($task, $colors, $tags); + $results[] = array_values($task); + } + + return $results; + } + + /** + * Get the list of tasks for a given project and date range + * + * @access protected + * @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 + */ + protected function getTasks($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(TaskModel::TABLE) + ->columns( + TaskModel::TABLE . '.id', + TaskModel::TABLE . '.reference', + ProjectModel::TABLE . '.name AS project_name', + TaskModel::TABLE . '.is_active', + CategoryModel::TABLE . '.name AS category_name', + SwimlaneModel::TABLE . '.name AS swimlane_name', + ColumnModel::TABLE . '.title AS column_title', + TaskModel::TABLE . '.position', + TaskModel::TABLE . '.color_id', + TaskModel::TABLE . '.date_due', + 'uc.username AS creator_username', + 'uc.name AS creator_name', + UserModel::TABLE . '.username AS assignee_username', + UserModel::TABLE . '.name AS assignee_name', + TaskModel::TABLE . '.score', + TaskModel::TABLE . '.title', + TaskModel::TABLE . '.date_creation', + TaskModel::TABLE . '.date_modification', + TaskModel::TABLE . '.date_completed', + TaskModel::TABLE . '.date_started', + TaskModel::TABLE . '.time_estimated', + TaskModel::TABLE . '.time_spent', + TaskModel::TABLE . '.priority' + ) + ->join(UserModel::TABLE, 'id', 'owner_id', TaskModel::TABLE) + ->left(UserModel::TABLE, 'uc', 'id', TaskModel::TABLE, 'creator_id') + ->join(CategoryModel::TABLE, 'id', 'category_id', TaskModel::TABLE) + ->join(ColumnModel::TABLE, 'id', 'column_id', TaskModel::TABLE) + ->join(SwimlaneModel::TABLE, 'id', 'swimlane_id', TaskModel::TABLE) + ->join(ProjectModel::TABLE, 'id', 'project_id', TaskModel::TABLE) + ->gte(TaskModel::TABLE . '.date_creation', $from) + ->lte(TaskModel::TABLE . '.date_creation', $to) + ->eq(TaskModel::TABLE . '.project_id', $project_id) + ->asc(TaskModel::TABLE.'.id') + ->findAll(); + } + + /** + * Format the output of a task array + * + * @access protected + * @param array $task + * @param array $colors + * @param array $tags + * @return array + */ + protected function format(array &$task, array $colors, array &$tags) + { + $task['is_active'] = $task['is_active'] == TaskModel::STATUS_OPEN ? e('Open') : e('Closed'); + $task['color_id'] = $colors[$task['color_id']]; + $task['score'] = $task['score'] ?: 0; + $task['tags'] = ''; + + $task = $this->dateParser->format( + $task, + array('date_due', 'date_modification', 'date_creation', 'date_started', 'date_completed'), + $this->dateParser->getUserDateTimeFormat() + ); + + if (isset($tags[$task['id']])) { + $taskTags = array_column($tags[$task['id']], 'name'); + $task['tags'] = implode(', ', $taskTags); + } + + return $task; + } + + /** + * Get column titles + * + * @access protected + * @return string[] + */ + protected function getColumns() + { + return array( + e('Task Id'), + e('Reference'), + e('Project'), + e('Status'), + e('Category'), + e('Swimlane'), + e('Column'), + e('Position'), + e('Color'), + e('Due date'), + e('Creator'), + e('Creator Name'), + e('Assignee Username'), + e('Assignee Name'), + e('Complexity'), + e('Title'), + e('Creation date'), + e('Modification date'), + e('Completion date'), + e('Start date'), + e('Time estimated'), + e('Time spent'), + e('Priority'), + e('Tags'), + ); + } +} diff --git a/app/Export/TransitionExport.php b/app/Export/TransitionExport.php new file mode 100644 index 0000000..35f9fe4 --- /dev/null +++ b/app/Export/TransitionExport.php @@ -0,0 +1,76 @@ +getColumns()); + $transitions = $this->transitionModel->getAllByProjectAndDate($project_id, $from, $to); + + foreach ($transitions as $transition) { + $results[] = $this->format($transition); + } + + return $results; + } + + /** + * Get column titles + * + * @access protected + * @return string[] + */ + protected 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 protected + * @param array $transition + * @return array + */ + protected function format(array $transition) + { + $values = array( + (int) $transition['id'], + $transition['title'], + $transition['src_column'], + $transition['dst_column'], + $transition['name'] ?: $transition['username'], + date($this->dateParser->getUserDateTimeFormat(), $transition['date']), + round($transition['time_spent'] / 3600, 2) + ); + + return $values; + } +} diff --git a/app/ExternalLink/AttachmentLink.php b/app/ExternalLink/AttachmentLink.php new file mode 100644 index 0000000..5a0d134 --- /dev/null +++ b/app/ExternalLink/AttachmentLink.php @@ -0,0 +1,26 @@ +url, PHP_URL_PATH); + return basename($path); + } +} diff --git a/app/ExternalLink/AttachmentLinkProvider.php b/app/ExternalLink/AttachmentLinkProvider.php new file mode 100644 index 0000000..6041645 --- /dev/null +++ b/app/ExternalLink/AttachmentLinkProvider.php @@ -0,0 +1,117 @@ + t('Related'), + ); + } + + /** + * Return true if the provider can parse correctly the user input + * + * @access public + * @return boolean + */ + public function match() + { + if (preg_match('/^https?:\/\/.*\/.*\.([^\/]+)$/', $this->userInput, $matches)) { + return $this->isValidExtension($matches[1]); + } + + return false; + } + + /** + * Get the link found with the properties + * + * @access public + * @return \Kanboard\Core\ExternalLink\ExternalLinkInterface + */ + public function getLink() + { + $link = new AttachmentLink($this->container); + $link->setUrl($this->userInput); + + return $link; + } + + /** + * Check file extension + * + * @access protected + * @param string $extension + * @return boolean + */ + protected function isValidExtension($extension) + { + $extension = strtolower($extension); + + foreach ($this->extensions as $ext) { + if ($extension === $ext) { + return false; + } + } + + return true; + } +} diff --git a/app/ExternalLink/BaseLink.php b/app/ExternalLink/BaseLink.php new file mode 100644 index 0000000..08693ae --- /dev/null +++ b/app/ExternalLink/BaseLink.php @@ -0,0 +1,44 @@ +url; + } + + /** + * Set link URL + * + * @access public + * @param string $url + */ + public function setUrl($url) + { + $this->url = $url; + } +} diff --git a/app/ExternalLink/BaseLinkProvider.php b/app/ExternalLink/BaseLinkProvider.php new file mode 100644 index 0000000..749cda9 --- /dev/null +++ b/app/ExternalLink/BaseLinkProvider.php @@ -0,0 +1,33 @@ +userInput = trim($input); + } +} diff --git a/app/ExternalLink/FileLink.php b/app/ExternalLink/FileLink.php new file mode 100644 index 0000000..ea13ece --- /dev/null +++ b/app/ExternalLink/FileLink.php @@ -0,0 +1,26 @@ +url, PHP_URL_PATH); + return basename(str_replace('\\', '/', $path)); + } +} diff --git a/app/ExternalLink/FileLinkProvider.php b/app/ExternalLink/FileLinkProvider.php new file mode 100644 index 0000000..eb8c108 --- /dev/null +++ b/app/ExternalLink/FileLinkProvider.php @@ -0,0 +1,89 @@ + t('Related'), + ); + } + + /** + * Return true if the provider can parse correctly the user input + * + * @access public + * @return boolean + */ + public function match() + { + if (strpos($this->userInput, '://') === false) { + return false; + } + + foreach ($this->excludedPrefixes as $prefix) { + if (strpos($this->userInput, $prefix) === 0) { + return false; + } + } + + return true; + } + + /** + * Get the link found with the properties + * + * @access public + * @return \Kanboard\Core\ExternalLink\ExternalLinkInterface + */ + public function getLink() + { + $link = new FileLink($this->container); + $link->setUrl($this->userInput); + + return $link; + } +} diff --git a/app/ExternalLink/WebLink.php b/app/ExternalLink/WebLink.php new file mode 100644 index 0000000..e28935f --- /dev/null +++ b/app/ExternalLink/WebLink.php @@ -0,0 +1,43 @@ +httpClient->isPrivateURL($this->url)) { + $this->logger->info('Blocked attempt to fetch URL from private network: '.$this->url); + return $this->url; + } + + // Do not follow redirects to prevent SSRF bypasses through redirect chains. + $html = $this->httpClient->get($this->url, [], false, false); + + if (preg_match('/(.*)<\/title>/siU', $html, $matches)) { + return trim($matches[1]); + } + + $components = parse_url($this->url); + + if (! empty($components['host']) && ! empty($components['path'])) { + return $components['host'].$components['path']; + } + + return $this->url; + } +} diff --git a/app/ExternalLink/WebLinkProvider.php b/app/ExternalLink/WebLinkProvider.php new file mode 100644 index 0000000..5ec1bbe --- /dev/null +++ b/app/ExternalLink/WebLinkProvider.php @@ -0,0 +1,77 @@ +<?php + +namespace Kanboard\ExternalLink; + +use Kanboard\Core\ExternalLink\ExternalLinkProviderInterface; + +/** + * Web Link Provider + * + * @package externalLink + * @author Frederic Guillot + */ +class WebLinkProvider extends BaseLinkProvider implements ExternalLinkProviderInterface +{ + /** + * Get provider name + * + * @access public + * @return string + */ + public function getName() + { + return t('Web Link'); + } + + /** + * Get link type + * + * @access public + * @return string + */ + public function getType() + { + return 'weblink'; + } + + /** + * Get a dictionary of supported dependency types by the provider + * + * @access public + * @return array + */ + public function getDependencies() + { + return array( + 'related' => t('Related'), + ); + } + + /** + * Return true if the provider can parse correctly the user input + * + * @access public + * @return boolean + */ + public function match() + { + $startWithHttp = strpos($this->userInput, 'http://') === 0 || strpos($this->userInput, 'https://') === 0; + $validUrl = filter_var($this->userInput, FILTER_VALIDATE_URL); + + return $startWithHttp && $validUrl; + } + + /** + * Get the link found with the properties + * + * @access public + * @return \Kanboard\Core\ExternalLink\ExternalLinkInterface + */ + public function getLink() + { + $link = new WebLink($this->container); + $link->setUrl($this->userInput); + + return $link; + } +} diff --git a/app/Filter/BaseComparisonFilter.php b/app/Filter/BaseComparisonFilter.php new file mode 100644 index 0000000..ba0c9c5 --- /dev/null +++ b/app/Filter/BaseComparisonFilter.php @@ -0,0 +1,48 @@ +<?php + +namespace Kanboard\Filter; + +/** + * Base comparison filter class + * + * @package filter + */ +abstract class BaseComparisonFilter extends BaseFilter +{ + /** + * Parse operator in the input string + * + * @access protected + * @return string + */ + protected function parseOperator() + { + $operators = array( + '<=' => 'lte', + '>=' => 'gte', + '<' => 'lt', + '>' => 'gt', + ); + + foreach ($operators as $operator => $method) { + if (strpos($this->value, $operator) === 0) { + $this->value = substr($this->value, strlen($operator)); + return $method; + } + } + + return 'eq'; + } + + /** + * Apply a comparison filter + * + * @access protected + * @param string $field + */ + protected function applyComparisonFilter($field) + { + $method = $this->parseOperator(); + $this->query->$method($field, $this->value); + } +} diff --git a/app/Filter/BaseDateFilter.php b/app/Filter/BaseDateFilter.php new file mode 100644 index 0000000..56fb2d7 --- /dev/null +++ b/app/Filter/BaseDateFilter.php @@ -0,0 +1,103 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\DateParser; + +/** + * Base date filter class + * + * @package filter + * @author Frederic Guillot + */ +abstract class BaseDateFilter extends BaseFilter +{ + /** + * DateParser object + * + * @access protected + * @var DateParser + */ + protected $dateParser; + + /** + * Set DateParser object + * + * @access public + * @param DateParser $dateParser + * @return $this + */ + public function setDateParser(DateParser $dateParser) + { + $this->dateParser = $dateParser; + return $this; + } + + /** + * Parse operator in the input string + * + * @access protected + * @return string + */ + protected function parseOperator() + { + $operators = array( + '<=' => 'lte', + '>=' => 'gte', + '<' => 'lt', + '>' => 'gt', + ); + + foreach ($operators as $operator => $method) { + if (strpos($this->value, $operator) === 0) { + $this->value = substr($this->value, strlen($operator)); + return $method; + } + } + + return ''; + } + + /** + * Apply a date filter + * + * @access protected + * @param string $field + */ + protected function applyDateFilter($field) + { + $method = $this->parseOperator(); + $timestamp = $this->dateParser->getTimestampFromIsoFormat($this->value); + + if ($method !== '') { + $this->query->$method($field, $this->getTimestampFromOperator($method, $timestamp)); + } else { + $this->query->gte($field, $timestamp); + $this->query->lte($field, $timestamp + 86399); + } + } + + /** + * Get timestamp from the operator + * + * @access public + * @param string $method + * @param integer $timestamp + * @return integer + */ + protected function getTimestampFromOperator($method, $timestamp) + { + switch ($method) { + case 'lte': + return $timestamp + 86399; + case 'lt': + return $timestamp; + case 'gte': + return $timestamp; + case 'gt': + return $timestamp + 86400; + } + + return $timestamp; + } +} diff --git a/app/Filter/BaseDateRangeFilter.php b/app/Filter/BaseDateRangeFilter.php new file mode 100644 index 0000000..2bd8378 --- /dev/null +++ b/app/Filter/BaseDateRangeFilter.php @@ -0,0 +1,54 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\DateParser; + +/** + * Base date filter class + * + * @package filter + * @author Kamil Ściana + */ +abstract class BaseDateRangeFilter extends BaseFilter +{ + /** + * DateParser object + * + * @access protected + * @var DateParser + */ + protected $dateParser; + + /** + * Set DateParser object + * + * @access public + * @param DateParser $dateParser + * @return $this + */ + public function setDateParser(DateParser $dateParser) + { + $this->dateParser = $dateParser; + return $this; + } + + /** + * Apply a date filter + * + * @access protected + * @param string $field + */ + protected function applyDateFilter($field) + { + $dates = explode('..', $this->value); + + if (count($dates)=== 2) { + $timestampFrom = $this->dateParser->getTimestamp($dates[0]." 00:00"); + $timestampTo = $this->dateParser->getTimestamp($dates[1]." 00:00"); + + $this->query->gte($field, $timestampFrom); + $this->query->lte($field, $timestampTo + 86399); + } + } +} diff --git a/app/Filter/BaseFilter.php b/app/Filter/BaseFilter.php new file mode 100644 index 0000000..2687d90 --- /dev/null +++ b/app/Filter/BaseFilter.php @@ -0,0 +1,74 @@ +<?php + +namespace Kanboard\Filter; + +use PicoDb\Table; + +/** + * Base filter class + * + * @package filter + * @author Frederic Guillot + */ +abstract class BaseFilter +{ + /** + * @var Table + */ + protected $query; + + /** + * @var mixed + */ + protected $value; + + /** + * BaseFilter constructor + * + * @access public + * @param mixed $value + */ + public function __construct($value = null) + { + $this->value = $value; + } + + /** + * Get object instance + * + * @static + * @access public + * @param mixed $value + * @return static + */ + public static function getInstance($value = null) + { + return new static($value); + } + + /** + * Set query + * + * @access public + * @param Table $query + * @return $this + */ + public function withQuery(Table $query) + { + $this->query = $query; + return $this; + } + + /** + * Set the value + * + * @access public + * @param string $value + * @return $this + */ + public function withValue($value) + { + $this->value = $value; + return $this; + } +} diff --git a/app/Filter/ProjectActivityCreationDateFilter.php b/app/Filter/ProjectActivityCreationDateFilter.php new file mode 100644 index 0000000..451f654 --- /dev/null +++ b/app/Filter/ProjectActivityCreationDateFilter.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\ProjectActivityModel; + +/** + * Filter activity events by creation date + * + * @package filter + * @author Frederic Guillot + */ +class ProjectActivityCreationDateFilter extends BaseDateFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('created'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->applyDateFilter(ProjectActivityModel::TABLE.'.date_creation'); + return $this; + } +} diff --git a/app/Filter/ProjectActivityCreatorFilter.php b/app/Filter/ProjectActivityCreatorFilter.php new file mode 100644 index 0000000..1cd0f9b --- /dev/null +++ b/app/Filter/ProjectActivityCreatorFilter.php @@ -0,0 +1,66 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\ProjectActivityModel; + +/** + * Filter activity events by creator + * + * @package filter + * @author Frederic Guillot + */ +class ProjectActivityCreatorFilter extends BaseFilter implements FilterInterface +{ + /** + * Current user id + * + * @access private + * @var int + */ + private $currentUserId = 0; + + /** + * Set current user id + * + * @access public + * @param integer $userId + * @return TaskAssigneeFilter + */ + public function setCurrentUserId($userId) + { + $this->currentUserId = $userId; + return $this; + } + + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('creator'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + if ($this->value === 'me') { + $this->query->eq(ProjectActivityModel::TABLE . '.creator_id', $this->currentUserId); + } else { + $this->query->beginOr(); + $this->query->ilike('uc.username', '%'.$this->value.'%'); + $this->query->ilike('uc.name', '%'.$this->value.'%'); + $this->query->closeOr(); + } + return $this; + } +} diff --git a/app/Filter/ProjectActivityProjectIdFilter.php b/app/Filter/ProjectActivityProjectIdFilter.php new file mode 100644 index 0000000..7146a05 --- /dev/null +++ b/app/Filter/ProjectActivityProjectIdFilter.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\ProjectActivityModel; + +/** + * Filter activity events by projectId + * + * @package filter + * @author Frederic Guillot + */ +class ProjectActivityProjectIdFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('project_id'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->query->eq(ProjectActivityModel::TABLE.'.project_id', $this->value); + return $this; + } +} diff --git a/app/Filter/ProjectActivityProjectIdsFilter.php b/app/Filter/ProjectActivityProjectIdsFilter.php new file mode 100644 index 0000000..70968f7 --- /dev/null +++ b/app/Filter/ProjectActivityProjectIdsFilter.php @@ -0,0 +1,43 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\ProjectActivityModel; + +/** + * Filter activity events by projectIds + * + * @package filter + * @author Frederic Guillot + */ +class ProjectActivityProjectIdsFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('projects'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + if (empty($this->value)) { + $this->query->eq(ProjectActivityModel::TABLE.'.project_id', 0); + } else { + $this->query->in(ProjectActivityModel::TABLE.'.project_id', $this->value); + } + + return $this; + } +} diff --git a/app/Filter/ProjectActivityProjectNameFilter.php b/app/Filter/ProjectActivityProjectNameFilter.php new file mode 100644 index 0000000..b487218 --- /dev/null +++ b/app/Filter/ProjectActivityProjectNameFilter.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\ProjectModel; + +/** + * Filter activity events by project name + * + * @package filter + * @author Frederic Guillot + */ +class ProjectActivityProjectNameFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('project'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->query->ilike(ProjectModel::TABLE.'.name', '%'.$this->value.'%'); + return $this; + } +} diff --git a/app/Filter/ProjectActivityTaskIdFilter.php b/app/Filter/ProjectActivityTaskIdFilter.php new file mode 100644 index 0000000..b8e074d --- /dev/null +++ b/app/Filter/ProjectActivityTaskIdFilter.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\ProjectActivityModel; + +/** + * Filter activity events by taskId + * + * @package filter + * @author Frederic Guillot + */ +class ProjectActivityTaskIdFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('task_id'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->query->eq(ProjectActivityModel::TABLE.'.task_id', $this->value); + return $this; + } +} diff --git a/app/Filter/ProjectActivityTaskStatusFilter.php b/app/Filter/ProjectActivityTaskStatusFilter.php new file mode 100644 index 0000000..2c98cab --- /dev/null +++ b/app/Filter/ProjectActivityTaskStatusFilter.php @@ -0,0 +1,43 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; + +/** + * Filter activity events by task status + * + * @package filter + * @author Frederic Guillot + */ +class ProjectActivityTaskStatusFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('status'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + if ($this->value === 'open') { + $this->query->eq(TaskModel::TABLE.'.is_active', TaskModel::STATUS_OPEN); + } elseif ($this->value === 'closed') { + $this->query->eq(TaskModel::TABLE.'.is_active', TaskModel::STATUS_CLOSED); + } + + return $this; + } +} diff --git a/app/Filter/ProjectActivityTaskTitleFilter.php b/app/Filter/ProjectActivityTaskTitleFilter.php new file mode 100644 index 0000000..bf2afa3 --- /dev/null +++ b/app/Filter/ProjectActivityTaskTitleFilter.php @@ -0,0 +1,25 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; + +/** + * Filter activity events by task title + * + * @package filter + * @author Frederic Guillot + */ +class ProjectActivityTaskTitleFilter extends TaskTitleFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('title'); + } +} diff --git a/app/Filter/ProjectGroupRoleProjectFilter.php b/app/Filter/ProjectGroupRoleProjectFilter.php new file mode 100644 index 0000000..035931b --- /dev/null +++ b/app/Filter/ProjectGroupRoleProjectFilter.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\ProjectGroupRoleModel; + +/** + * Filter ProjectGroupRole users by project + * + * @package filter + * @author Frederic Guillot + */ +class ProjectGroupRoleProjectFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array(); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->query->eq(ProjectGroupRoleModel::TABLE.'.project_id', $this->value); + return $this; + } +} diff --git a/app/Filter/ProjectGroupRoleUsernameFilter.php b/app/Filter/ProjectGroupRoleUsernameFilter.php new file mode 100644 index 0000000..9feac33 --- /dev/null +++ b/app/Filter/ProjectGroupRoleUsernameFilter.php @@ -0,0 +1,44 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\GroupMemberModel; +use Kanboard\Model\ProjectGroupRoleModel; +use Kanboard\Model\UserModel; + +/** + * Filter ProjectGroupRole users by username + * + * @package filter + * @author Frederic Guillot + */ +class ProjectGroupRoleUsernameFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array(); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->query + ->join(GroupMemberModel::TABLE, 'group_id', 'group_id', ProjectGroupRoleModel::TABLE) + ->join(UserModel::TABLE, 'id', 'user_id', GroupMemberModel::TABLE) + ->ilike(UserModel::TABLE.'.username', $this->value.'%'); + + return $this; + } +} diff --git a/app/Filter/ProjectIdsFilter.php b/app/Filter/ProjectIdsFilter.php new file mode 100644 index 0000000..9af9db5 --- /dev/null +++ b/app/Filter/ProjectIdsFilter.php @@ -0,0 +1,43 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\ProjectModel; + +/** + * Filter project by ids + * + * @package filter + * @author Frederic Guillot + */ +class ProjectIdsFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('project_ids'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + if (empty($this->value)) { + $this->query->eq(ProjectModel::TABLE.'.id', 0); + } else { + $this->query->in(ProjectModel::TABLE.'.id', $this->value); + } + + return $this; + } +} diff --git a/app/Filter/ProjectStatusFilter.php b/app/Filter/ProjectStatusFilter.php new file mode 100644 index 0000000..5379f89 --- /dev/null +++ b/app/Filter/ProjectStatusFilter.php @@ -0,0 +1,45 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\ProjectModel; + +/** + * Filter project by status + * + * @package filter + * @author Frederic Guillot + */ +class ProjectStatusFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('status'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + if (is_int($this->value) || ctype_digit((string) $this->value)) { + $this->query->eq(ProjectModel::TABLE.'.is_active', $this->value); + } elseif ($this->value === 'inactive' || $this->value === 'closed' || $this->value === 'disabled') { + $this->query->eq(ProjectModel::TABLE.'.is_active', 0); + } else { + $this->query->eq(ProjectModel::TABLE.'.is_active', 1); + } + + return $this; + } +} diff --git a/app/Filter/ProjectTypeFilter.php b/app/Filter/ProjectTypeFilter.php new file mode 100644 index 0000000..601d197 --- /dev/null +++ b/app/Filter/ProjectTypeFilter.php @@ -0,0 +1,45 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\ProjectModel; + +/** + * Filter project by type + * + * @package filter + * @author Frederic Guillot + */ +class ProjectTypeFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('type'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + if (is_int($this->value) || ctype_digit((string) $this->value)) { + $this->query->eq(ProjectModel::TABLE.'.is_private', $this->value); + } elseif ($this->value === 'private') { + $this->query->eq(ProjectModel::TABLE.'.is_private', ProjectModel::TYPE_PRIVATE); + } else { + $this->query->eq(ProjectModel::TABLE.'.is_private', ProjectModel::TYPE_TEAM); + } + + return $this; + } +} diff --git a/app/Filter/ProjectUserRoleProjectFilter.php b/app/Filter/ProjectUserRoleProjectFilter.php new file mode 100644 index 0000000..6952364 --- /dev/null +++ b/app/Filter/ProjectUserRoleProjectFilter.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\ProjectUserRoleModel; + +/** + * Filter ProjectUserRole users by project + * + * @package filter + * @author Frederic Guillot + */ +class ProjectUserRoleProjectFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array(); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->query->eq(ProjectUserRoleModel::TABLE.'.project_id', $this->value); + return $this; + } +} diff --git a/app/Filter/ProjectUserRoleUsernameFilter.php b/app/Filter/ProjectUserRoleUsernameFilter.php new file mode 100644 index 0000000..327d3d5 --- /dev/null +++ b/app/Filter/ProjectUserRoleUsernameFilter.php @@ -0,0 +1,41 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\UserModel; + +/** + * Filter ProjectUserRole users by username + * + * @package filter + * @author Frederic Guillot + */ +class ProjectUserRoleUsernameFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array(); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->query + ->join(UserModel::TABLE, 'id', 'user_id') + ->ilike(UserModel::TABLE.'.username', $this->value.'%'); + + return $this; + } +} diff --git a/app/Filter/TaskAssigneeFilter.php b/app/Filter/TaskAssigneeFilter.php new file mode 100644 index 0000000..c8c17ab --- /dev/null +++ b/app/Filter/TaskAssigneeFilter.php @@ -0,0 +1,79 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; +use Kanboard\Model\UserModel; + +/** + * Filter tasks by assignee + * + * @package filter + * @author Frederic Guillot + */ +class TaskAssigneeFilter extends BaseFilter implements FilterInterface +{ + /** + * Current user id + * + * @access private + * @var int + */ + private $currentUserId = 0; + + /** + * Set current user id + * + * @access public + * @param integer $userId + * @return TaskAssigneeFilter + */ + public function setCurrentUserId($userId) + { + $this->currentUserId = $userId; + return $this; + } + + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('assignee'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + if (is_int($this->value) || ctype_digit((string) $this->value)) { + $this->query->eq(TaskModel::TABLE.'.owner_id', $this->value); + } else { + switch ($this->value) { + case 'me': + $this->query->eq(TaskModel::TABLE.'.owner_id', $this->currentUserId); + break; + case 'nobody': + $this->query->eq(TaskModel::TABLE.'.owner_id', 0); + break; + case 'anybody': + $this->query->gt(TaskModel::TABLE.'.owner_id', 0); + break; + default: + $this->query->beginOr(); + $this->query->ilike(UserModel::TABLE.'.username', '%'.$this->value.'%'); + $this->query->ilike(UserModel::TABLE.'.name', '%'.$this->value.'%'); + $this->query->closeOr(); + } + } + return $this; + } +} diff --git a/app/Filter/TaskCategoryFilter.php b/app/Filter/TaskCategoryFilter.php new file mode 100644 index 0000000..514d7a5 --- /dev/null +++ b/app/Filter/TaskCategoryFilter.php @@ -0,0 +1,49 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\CategoryModel; +use Kanboard\Model\TaskModel; + +/** + * Filter tasks by category + * + * @package filter + * @author Frederic Guillot + */ +class TaskCategoryFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('category'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + if (is_int($this->value) || ctype_digit((string) $this->value)) { + $this->query->beginOr(); + $this->query->eq(TaskModel::TABLE.'.category_id', $this->value); + $this->query->eq(CategoryModel::TABLE.'.name', $this->value); + $this->query->closeOr(); + } elseif ($this->value === 'none') { + $this->query->eq(TaskModel::TABLE.'.category_id', 0); + } else { + $this->query->eq(CategoryModel::TABLE.'.name', $this->value); + } + + return $this; + } +} diff --git a/app/Filter/TaskColorFilter.php b/app/Filter/TaskColorFilter.php new file mode 100644 index 0000000..2ddb47c --- /dev/null +++ b/app/Filter/TaskColorFilter.php @@ -0,0 +1,60 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\ColorModel; +use Kanboard\Model\TaskModel; + +/** + * Filter tasks by color + * + * @package filter + * @author Frederic Guillot + */ +class TaskColorFilter extends BaseFilter implements FilterInterface +{ + /** + * Color object + * + * @access private + * @var ColorModel + */ + private $colorModel; + + /** + * Set color model object + * + * @access public + * @param ColorModel $colorModel + * @return TaskColorFilter + */ + public function setColorModel(ColorModel $colorModel) + { + $this->colorModel = $colorModel; + return $this; + } + + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('color', 'colour'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->query->eq(TaskModel::TABLE.'.color_id', $this->colorModel->find($this->value)); + return $this; + } +} diff --git a/app/Filter/TaskColumnFilter.php b/app/Filter/TaskColumnFilter.php new file mode 100644 index 0000000..57db133 --- /dev/null +++ b/app/Filter/TaskColumnFilter.php @@ -0,0 +1,44 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\ColumnModel; +use Kanboard\Model\TaskModel; + +/** + * Filter tasks by column + * + * @package filter + * @author Frederic Guillot + */ +class TaskColumnFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('column'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + if (is_int($this->value) || ctype_digit((string) $this->value)) { + $this->query->eq(TaskModel::TABLE.'.column_id', $this->value); + } else { + $this->query->eq(ColumnModel::TABLE.'.title', $this->value); + } + + return $this; + } +} diff --git a/app/Filter/TaskCommentFilter.php b/app/Filter/TaskCommentFilter.php new file mode 100644 index 0000000..e5d9111 --- /dev/null +++ b/app/Filter/TaskCommentFilter.php @@ -0,0 +1,76 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\CommentModel; +use Kanboard\Model\TaskModel; +use PicoDb\Database; + +/** + * Filter tasks by comment + * + * @package filter + * @author Frederic Guillot + */ +class TaskCommentFilter extends BaseFilter implements FilterInterface +{ + /** + * Database object + * + * @access private + * @var Database + */ + private $db; + + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('comment'); + } + + /** + * Set database object + * + * @access public + * @param Database $db + * @return $this + */ + public function setDatabase(Database $db) + { + $this->db = $db; + return $this; + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->query->inSubquery(TaskModel::TABLE.'.id', $this->getSubQuery()); + + return $this; + } + + /** + * Get task ids having this comment + * + * @access public + * @return array + */ + protected function getSubQuery() + { + return $this->db + ->table(CommentModel::TABLE) + ->columns(CommentModel::TABLE.'.task_id') + ->ilike(CommentModel::TABLE.'.comment', '%'.$this->value.'%'); + } +} diff --git a/app/Filter/TaskCompletionDateFilter.php b/app/Filter/TaskCompletionDateFilter.php new file mode 100644 index 0000000..79b5e7d --- /dev/null +++ b/app/Filter/TaskCompletionDateFilter.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; + +/** + * Filter tasks by completion date + * + * @package filter + * @author Frederic Guillot + */ +class TaskCompletionDateFilter extends BaseDateFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('completed'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->applyDateFilter(TaskModel::TABLE.'.date_completed'); + return $this; + } +} diff --git a/app/Filter/TaskCompletionDateRangeFilter.php b/app/Filter/TaskCompletionDateRangeFilter.php new file mode 100644 index 0000000..9272cc1 --- /dev/null +++ b/app/Filter/TaskCompletionDateRangeFilter.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; + +/** + * Filter tasks by completion date + * + * @package filter + * @author Kamil Ściana + */ +class TaskCompletionDateRangeFilter extends BaseDateRangeFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('completedRange'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->applyDateFilter(TaskModel::TABLE.'.date_completed'); + return $this; + } +} diff --git a/app/Filter/TaskCreationDateFilter.php b/app/Filter/TaskCreationDateFilter.php new file mode 100644 index 0000000..db28ac8 --- /dev/null +++ b/app/Filter/TaskCreationDateFilter.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; + +/** + * Filter tasks by creation date + * + * @package filter + * @author Frederic Guillot + */ +class TaskCreationDateFilter extends BaseDateFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('created'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->applyDateFilter(TaskModel::TABLE.'.date_creation'); + return $this; + } +} diff --git a/app/Filter/TaskCreationDateRangeFilter.php b/app/Filter/TaskCreationDateRangeFilter.php new file mode 100644 index 0000000..7696af0 --- /dev/null +++ b/app/Filter/TaskCreationDateRangeFilter.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; + +/** + * Filter tasks by creation date + * + * @package filter + * @author Kamil Ściana + */ +class TaskCreationDateRangeFilter extends BaseDateRangeFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('createdRange'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->applyDateFilter(TaskModel::TABLE.'.date_creation'); + return $this; + } +} diff --git a/app/Filter/TaskCreatorFilter.php b/app/Filter/TaskCreatorFilter.php new file mode 100644 index 0000000..8f7037c --- /dev/null +++ b/app/Filter/TaskCreatorFilter.php @@ -0,0 +1,78 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; + +/** + * Filter tasks by creator + * + * @package filter + * @author Frederic Guillot + */ +class TaskCreatorFilter extends BaseFilter implements FilterInterface +{ + /** + * Current user id + * + * @access private + * @var int + */ + private $currentUserId = 0; + + /** + * Set current user id + * + * @access public + * @param integer $userId + * @return TaskAssigneeFilter + */ + public function setCurrentUserId($userId) + { + $this->currentUserId = $userId; + return $this; + } + + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('creator'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + if (is_int($this->value) || ctype_digit((string) $this->value)) { + $this->query->eq(TaskModel::TABLE.'.creator_id', $this->value); + } else { + switch ($this->value) { + case 'me': + $this->query->eq(TaskModel::TABLE.'.creator_id', $this->currentUserId); + break; + case 'nobody': + $this->query->eq(TaskModel::TABLE.'.creator_id', 0); + break; + case 'anybody': + $this->query->gt(TaskModel::TABLE.'.creator_id', 0); + break; + default: + $this->query->beginOr(); + $this->query->ilike('uc.username', '%'.$this->value.'%'); + $this->query->ilike('uc.name', '%'.$this->value.'%'); + $this->query->closeOr(); + } + } + return $this; + } +} diff --git a/app/Filter/TaskDescriptionFilter.php b/app/Filter/TaskDescriptionFilter.php new file mode 100644 index 0000000..c73c2f5 --- /dev/null +++ b/app/Filter/TaskDescriptionFilter.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; + +/** + * Filter tasks by description + * + * @package filter + * @author Frederic Guillot + */ +class TaskDescriptionFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('description', 'desc'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->query->ilike(TaskModel::TABLE.'.description', '%'.$this->value.'%'); + return $this; + } +} diff --git a/app/Filter/TaskDueDateFilter.php b/app/Filter/TaskDueDateFilter.php new file mode 100644 index 0000000..194dd43 --- /dev/null +++ b/app/Filter/TaskDueDateFilter.php @@ -0,0 +1,45 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; + +/** + * Filter tasks by due date + * + * @package filter + * @author Frederic Guillot + */ +class TaskDueDateFilter extends BaseDateFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('due'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + if ($this->value == "none") { + $this->query->eq(TaskModel::TABLE.'.date_due', 0); + } else { + $this->query->neq(TaskModel::TABLE.'.date_due', 0); + $this->query->notNull(TaskModel::TABLE.'.date_due'); + $this->applyDateFilter(TaskModel::TABLE.'.date_due'); + } + + return $this; + } +} diff --git a/app/Filter/TaskDueDateRangeFilter.php b/app/Filter/TaskDueDateRangeFilter.php new file mode 100644 index 0000000..a4250f8 --- /dev/null +++ b/app/Filter/TaskDueDateRangeFilter.php @@ -0,0 +1,44 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; + +/** + * Filter tasks by due date range + * + * @package filter + * @author Frederic Guillot + */ +class TaskDueDateRangeFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array(); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->query->beginOr(); + $this->query->isNull(TaskModel::TABLE.'.date_started'); + $this->query->eq(TaskModel::TABLE.'.date_started', 0); + $this->query->closeOr(); + + $this->query->gte(TaskModel::TABLE.'.date_due', is_numeric($this->value[0]) ? $this->value[0] : strtotime($this->value[0])); + $this->query->lte(TaskModel::TABLE.'.date_due', is_numeric($this->value[1]) ? $this->value[1] : strtotime($this->value[1])); + return $this; + } +} diff --git a/app/Filter/TaskIdExclusionFilter.php b/app/Filter/TaskIdExclusionFilter.php new file mode 100644 index 0000000..20177b2 --- /dev/null +++ b/app/Filter/TaskIdExclusionFilter.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; + +/** + * Exclude task ids + * + * @package filter + * @author Frederic Guillot + */ +class TaskIdExclusionFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('exclude'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->query->notin(TaskModel::TABLE.'.id', $this->value); + return $this; + } +} diff --git a/app/Filter/TaskIdFilter.php b/app/Filter/TaskIdFilter.php new file mode 100644 index 0000000..fdf668b --- /dev/null +++ b/app/Filter/TaskIdFilter.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; + +/** + * Filter tasks by id + * + * @package filter + * @author Frederic Guillot + */ +class TaskIdFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('id'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->query->eq(TaskModel::TABLE.'.id', $this->value); + return $this; + } +} diff --git a/app/Filter/TaskLinkFilter.php b/app/Filter/TaskLinkFilter.php new file mode 100644 index 0000000..ee6ecd1 --- /dev/null +++ b/app/Filter/TaskLinkFilter.php @@ -0,0 +1,79 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\LinkModel; +use Kanboard\Model\TaskModel; +use Kanboard\Model\TaskLinkModel; +use PicoDb\Database; +use PicoDb\Table; + +/** + * Filter tasks by link name + * + * @package filter + * @author Frederic Guillot + */ +class TaskLinkFilter extends BaseFilter implements FilterInterface +{ + /** + * Database object + * + * @access private + * @var Database + */ + private $db; + + /** + * Set database object + * + * @access public + * @param Database $db + * @return TaskLinkFilter + */ + public function setDatabase(Database $db) + { + $this->db = $db; + return $this; + } + + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('link'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->query->inSubquery(TaskModel::TABLE.'.id', $this->getSubQuery()); + return $this; + } + + /** + * Get subquery + * + * @access protected + * @return Table + */ + protected function getSubQuery() + { + return $this->db->table(TaskLinkModel::TABLE) + ->columns( + TaskLinkModel::TABLE.'.task_id' + ) + ->join(LinkModel::TABLE, 'id', 'link_id', TaskLinkModel::TABLE) + ->ilike(LinkModel::TABLE.'.label', $this->value); + } +} diff --git a/app/Filter/TaskModificationDateFilter.php b/app/Filter/TaskModificationDateFilter.php new file mode 100644 index 0000000..316f183 --- /dev/null +++ b/app/Filter/TaskModificationDateFilter.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; + +/** + * Filter tasks by modification date + * + * @package filter + * @author Frederic Guillot + */ +class TaskModificationDateFilter extends BaseDateFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('updated', 'modified'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->applyDateFilter(TaskModel::TABLE.'.date_modification'); + return $this; + } +} diff --git a/app/Filter/TaskModificationDateRangeFilter.php b/app/Filter/TaskModificationDateRangeFilter.php new file mode 100644 index 0000000..3daa2b5 --- /dev/null +++ b/app/Filter/TaskModificationDateRangeFilter.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; + +/** + * Filter tasks by modification date + * + * @package filter + * @author Kamil Ściana + */ +class TaskModificationDateRangeFilter extends BaseDateRangeFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('updatedRange', 'modifiedRange'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->applyDateFilter(TaskModel::TABLE.'.date_modification'); + return $this; + } +} diff --git a/app/Filter/TaskMovedDateFilter.php b/app/Filter/TaskMovedDateFilter.php new file mode 100644 index 0000000..d57b7d2 --- /dev/null +++ b/app/Filter/TaskMovedDateFilter.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; + +/** + * Filter tasks by modification date + * + * @package filter + * @author Frederic Guillot + */ +class TaskMovedDateFilter extends BaseDateFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('moved'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->applyDateFilter(TaskModel::TABLE.'.date_moved'); + return $this; + } +} diff --git a/app/Filter/TaskMovedDateRangeFilter.php b/app/Filter/TaskMovedDateRangeFilter.php new file mode 100644 index 0000000..b5b826b --- /dev/null +++ b/app/Filter/TaskMovedDateRangeFilter.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; + +/** + * Filter tasks by creation date + * + * @package filter + * @author Kamil Ściana + */ +class TaskMovedDateRangeFilter extends BaseDateRangeFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('movedRange'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->applyDateFilter(TaskModel::TABLE.'.date_moved'); + return $this; + } +} diff --git a/app/Filter/TaskPriorityFilter.php b/app/Filter/TaskPriorityFilter.php new file mode 100644 index 0000000..e0c446c --- /dev/null +++ b/app/Filter/TaskPriorityFilter.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; + +/** + * Class TaskPriorityFilter + * + * @package Kanboard\Filter + * @author Frederic Guillot + */ +class TaskPriorityFilter extends BaseComparisonFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('priority'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->applyComparisonFilter(TaskModel::TABLE.'.priority'); + return $this; + } +} diff --git a/app/Filter/TaskProjectFilter.php b/app/Filter/TaskProjectFilter.php new file mode 100644 index 0000000..0acb72e --- /dev/null +++ b/app/Filter/TaskProjectFilter.php @@ -0,0 +1,46 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\ProjectModel; +use Kanboard\Model\TaskModel; + +/** + * Filter tasks by project + * + * @package filter + * @author Frederic Guillot + */ +class TaskProjectFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('project'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + // Max integer value for Postgres is +2147483647 + // See https://www.postgresql.org/docs/current/datatype-numeric.html + if (is_int($this->value) || ctype_digit((string) $this->value) && $this->value < 2147483647) { + $this->query->eq(TaskModel::TABLE.'.project_id', $this->value); + } else { + $this->query->ilike(ProjectModel::TABLE.'.name', $this->value); + } + + return $this; + } +} diff --git a/app/Filter/TaskProjectsFilter.php b/app/Filter/TaskProjectsFilter.php new file mode 100644 index 0000000..2b6b16c --- /dev/null +++ b/app/Filter/TaskProjectsFilter.php @@ -0,0 +1,43 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; + +/** + * Filter tasks by project ids + * + * @package filter + * @author Frederic Guillot + */ +class TaskProjectsFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('projects'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + if (empty($this->value)) { + $this->query->eq(TaskModel::TABLE.'.project_id', 0); + } else { + $this->query->in(TaskModel::TABLE.'.project_id', $this->value); + } + + return $this; + } +} diff --git a/app/Filter/TaskReferenceFilter.php b/app/Filter/TaskReferenceFilter.php new file mode 100644 index 0000000..612be81 --- /dev/null +++ b/app/Filter/TaskReferenceFilter.php @@ -0,0 +1,51 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; + +/** + * Filter tasks by reference + * + * @package filter + * @author Frederic Guillot + */ +class TaskReferenceFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('reference', 'ref'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + if ($this->value === 'none') { + $this->query->beginOr(); + $this->query->eq(TaskModel::TABLE.'.reference', ''); + $this->query->isNull(TaskModel::TABLE.'.reference'); + $this->query->closeOr(); + return $this; + } + + if (strpos($this->value, '*') >= 0) { + $this->query->ilike(TaskModel::TABLE.'.reference', str_replace('*', '%', $this->value)); + return $this; + } + + $this->query->eq(TaskModel::TABLE.'.reference', $this->value); + return $this; + } +} diff --git a/app/Filter/TaskScoreFilter.php b/app/Filter/TaskScoreFilter.php new file mode 100644 index 0000000..2c4067f --- /dev/null +++ b/app/Filter/TaskScoreFilter.php @@ -0,0 +1,37 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; + +/** + * Class TaskScoreFilter + * + * @package Kanboard\Filter + */ +class TaskScoreFilter extends BaseComparisonFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('score', 'complexity'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->applyComparisonFilter(TaskModel::TABLE.'.score'); + return $this; + } +} diff --git a/app/Filter/TaskStartDateFilter.php b/app/Filter/TaskStartDateFilter.php new file mode 100644 index 0000000..d5abedb --- /dev/null +++ b/app/Filter/TaskStartDateFilter.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; + +/** + * Filter tasks by start date + * + * @package filter + * @author Frederic Guillot + */ +class TaskStartDateFilter extends BaseDateFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('started'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->applyDateFilter(TaskModel::TABLE.'.date_started'); + return $this; + } +} diff --git a/app/Filter/TaskStartsWithIdFilter.php b/app/Filter/TaskStartsWithIdFilter.php new file mode 100644 index 0000000..997683a --- /dev/null +++ b/app/Filter/TaskStartsWithIdFilter.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; + +/** + * Class TaskIdSearchFilter + * + * @package Kanboard\Filter + * @author Frederic Guillot + */ +class TaskStartsWithIdFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('starts_with_id'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->query->ilike('CAST('.TaskModel::TABLE.'.id AS CHAR(8))', $this->value.'%'); + return $this; + } +} diff --git a/app/Filter/TaskStatusFilter.php b/app/Filter/TaskStatusFilter.php new file mode 100644 index 0000000..b5d55f3 --- /dev/null +++ b/app/Filter/TaskStatusFilter.php @@ -0,0 +1,43 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; + +/** + * Filter tasks by status + * + * @package filter + * @author Frederic Guillot + */ +class TaskStatusFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('status'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + if ($this->value === 'open' || $this->value === 'closed') { + $this->query->eq(TaskModel::TABLE.'.is_active', $this->value === 'open' ? TaskModel::STATUS_OPEN : TaskModel::STATUS_CLOSED); + } elseif (is_int($this->value) || ctype_digit((string) $this->value)) { + $this->query->eq(TaskModel::TABLE.'.is_active', $this->value); + } + + return $this; + } +} diff --git a/app/Filter/TaskSubtaskAssigneeFilter.php b/app/Filter/TaskSubtaskAssigneeFilter.php new file mode 100644 index 0000000..743483c --- /dev/null +++ b/app/Filter/TaskSubtaskAssigneeFilter.php @@ -0,0 +1,133 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\SubtaskModel; +use Kanboard\Model\TaskModel; +use Kanboard\Model\UserModel; +use PicoDb\Database; +use PicoDb\Table; + +/** + * Filter tasks by subtasks assignee + * + * @package filter + * @author Frederic Guillot + */ +class TaskSubtaskAssigneeFilter extends BaseFilter implements FilterInterface +{ + /** + * Database object + * + * @access private + * @var Database + */ + private $db; + + /** + * Current user id + * + * @access private + * @var int + */ + private $currentUserId = 0; + + /** + * Set current user id + * + * @access public + * @param integer $userId + * @return TaskSubtaskAssigneeFilter + */ + public function setCurrentUserId($userId) + { + $this->currentUserId = $userId; + return $this; + } + + /** + * Set database object + * + * @access public + * @param Database $db + * @return TaskSubtaskAssigneeFilter + */ + public function setDatabase(Database $db) + { + $this->db = $db; + return $this; + } + + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('subtask:assignee'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->query->inSubquery(TaskModel::TABLE.'.id', $this->getSubQuery()); + return $this; + } + + /** + * Get subquery + * + * @access protected + * @return Table + */ + protected function getSubQuery() + { + $subquery = $this->db->table(SubtaskModel::TABLE) + ->columns(SubtaskModel::TABLE.'.task_id') + ->join(UserModel::TABLE, 'id', 'user_id', SubtaskModel::TABLE) + ->neq(SubtaskModel::TABLE.'.status', SubtaskModel::STATUS_DONE); + + return $this->applySubQueryFilter($subquery); + } + + /** + * Apply subquery filter + * + * @access protected + * @param Table $subquery + * @return Table + */ + protected function applySubQueryFilter(Table $subquery) + { + if (is_int($this->value) || ctype_digit((string) $this->value)) { + $subquery->eq(SubtaskModel::TABLE.'.user_id', $this->value); + } else { + switch ($this->value) { + case 'me': + $subquery->eq(SubtaskModel::TABLE.'.user_id', $this->currentUserId); + break; + case 'nobody': + $subquery->eq(SubtaskModel::TABLE.'.user_id', 0); + break; + case 'anybody': + $subquery->gt(SubtaskModel::TABLE.'.user_id', 0); + break; + default: + $subquery->beginOr(); + $subquery->ilike(UserModel::TABLE.'.username', $this->value.'%'); + $subquery->ilike(UserModel::TABLE.'.name', '%'.$this->value.'%'); + $subquery->closeOr(); + } + } + + return $subquery; + } +} diff --git a/app/Filter/TaskSwimlaneFilter.php b/app/Filter/TaskSwimlaneFilter.php new file mode 100644 index 0000000..a339ec8 --- /dev/null +++ b/app/Filter/TaskSwimlaneFilter.php @@ -0,0 +1,40 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\ProjectModel; +use Kanboard\Model\SwimlaneModel; +use Kanboard\Model\TaskModel; + +/** + * Filter tasks by swimlane + * + * @package filter + * @author Frederic Guillot + */ +class TaskSwimlaneFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('swimlane'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->query->ilike(SwimlaneModel::TABLE.'.name', $this->value); + return $this; + } +} diff --git a/app/Filter/TaskTagFilter.php b/app/Filter/TaskTagFilter.php new file mode 100644 index 0000000..ea76717 --- /dev/null +++ b/app/Filter/TaskTagFilter.php @@ -0,0 +1,87 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TagModel; +use Kanboard\Model\TaskModel; +use Kanboard\Model\TaskTagModel; +use PicoDb\Database; + +/** + * Class TaskTagFilter + * + * @package Kanboard\Filter + * @author Frederic Guillot + */ +class TaskTagFilter extends BaseFilter implements FilterInterface +{ + /** + * Database object + * + * @access private + * @var Database + */ + private $db; + + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('tag'); + } + + /** + * Set database object + * + * @access public + * @param Database $db + * @return $this + */ + public function setDatabase(Database $db) + { + $this->db = $db; + return $this; + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + if ($this->value === 'none') { + $sub_query = $this->getQueryOfTaskIdsWithoutTags(); + } else { + $sub_query = $this->getQueryOfTaskIdsWithGivenTag(); + } + + $this->query->inSubquery(TaskModel::TABLE.'.id', $sub_query); + + return $this; + } + + protected function getQueryOfTaskIdsWithoutTags() + { + return $this->db + ->table(TaskModel::TABLE) + ->columns(TaskModel::TABLE . '.id') + ->left(TaskTagModel::TABLE, 'tg', 'task_id', TaskModel::TABLE, 'id') + ->isNull('tg.tag_id'); + } + + protected function getQueryOfTaskIdsWithGivenTag() + { + return $this->db + ->table(TagModel::TABLE) + ->columns(TaskTagModel::TABLE.'.task_id') + ->ilike(TagModel::TABLE.'.name', '%'.$this->value.'%') + ->join(TaskTagModel::TABLE, 'tag_id', 'id'); + } +} diff --git a/app/Filter/TaskTitleFilter.php b/app/Filter/TaskTitleFilter.php new file mode 100644 index 0000000..475ed60 --- /dev/null +++ b/app/Filter/TaskTitleFilter.php @@ -0,0 +1,46 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; + +/** + * Filter tasks by title + * + * @package filter + * @author Frederic Guillot + */ +class TaskTitleFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('title'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + if (ctype_digit((string) $this->value) || (strlen($this->value) > 1 && $this->value[0] === '#' && ctype_digit(substr($this->value, 1)))) { + $this->query->beginOr(); + $this->query->eq(TaskModel::TABLE.'.id', str_replace('#', '', $this->value)); + $this->query->ilike(TaskModel::TABLE.'.title', '%'.$this->value.'%'); + $this->query->closeOr(); + } else { + $this->query->ilike(TaskModel::TABLE.'.title', '%'.$this->value.'%'); + } + + return $this; + } +} diff --git a/app/Filter/UserNameFilter.php b/app/Filter/UserNameFilter.php new file mode 100644 index 0000000..dfb07fd --- /dev/null +++ b/app/Filter/UserNameFilter.php @@ -0,0 +1,35 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; + +class UserNameFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('name'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->query->beginOr() + ->ilike('username', '%'.$this->value.'%') + ->ilike('name', '%'.$this->value.'%') + ->closeOr(); + + return $this; + } +} diff --git a/app/Formatter/BaseFormatter.php b/app/Formatter/BaseFormatter.php new file mode 100644 index 0000000..0d62628 --- /dev/null +++ b/app/Formatter/BaseFormatter.php @@ -0,0 +1,36 @@ +<?php + +namespace Kanboard\Formatter; + +use Kanboard\Core\Base; +use PicoDb\Table; + +/** + * Class BaseFormatter + * + * @package formatter + * @author Frederic Guillot + */ +abstract class BaseFormatter extends Base +{ + /** + * Query object + * + * @access protected + * @var Table + */ + protected $query; + + /** + * Set query + * + * @access public + * @param Table $query + * @return $this + */ + public function withQuery(Table $query) + { + $this->query = $query; + return $this; + } +} diff --git a/app/Formatter/BoardColumnFormatter.php b/app/Formatter/BoardColumnFormatter.php new file mode 100644 index 0000000..0bfc210 --- /dev/null +++ b/app/Formatter/BoardColumnFormatter.php @@ -0,0 +1,115 @@ +<?php + +namespace Kanboard\Formatter; + +use Kanboard\Core\Filter\FormatterInterface; + +/** + * Board Column Formatter + * + * @package formatter + * @author Frederic Guillot + */ +class BoardColumnFormatter extends BaseFormatter implements FormatterInterface +{ + protected $swimlaneId = 0; + protected $columns = array(); + protected $tasks = array(); + protected $tags = array(); + protected $taskCountBySwimlaneAndColumn = array(); + + /** + * Set swimlaneId + * + * @access public + * @param integer $swimlaneId + * @return $this + */ + public function withSwimlaneId($swimlaneId) + { + $this->swimlaneId = $swimlaneId; + return $this; + } + + /** + * Set columns + * + * @access public + * @param array $columns + * @return $this + */ + public function withColumns(array $columns) + { + $this->columns = $columns; + return $this; + } + + /** + * Set tasks + * + * @access public + * @param array $tasks + * @return $this + */ + public function withTasks(array $tasks) + { + $this->tasks = $tasks; + return $this; + } + + /** + * Set tags + * + * @access public + * @param array $tags + * @return $this + */ + public function withTags(array $tags) + { + $this->tags = $tags; + return $this; + } + + /** + * Set task count by swimlane and column + * + * @access public + * @param array $taskCountBySwimlaneAndColumn + * @return $this + */ + public function withTaskCountBySwimlaneAndColumn(array $taskCountBySwimlaneAndColumn) + { + $this->taskCountBySwimlaneAndColumn = $taskCountBySwimlaneAndColumn; + return $this; + } + + /** + * Apply formatter + * + * @access public + * @return array + */ + public function format() + { + foreach ($this->columns as &$column) { + $column['id'] = (int) $column['id']; + $column['tasks'] = $this->boardTaskFormatter + ->withTasks($this->tasks) + ->withTags($this->tags) + ->withSwimlaneId($this->swimlaneId) + ->withColumnId($column['id']) + ->format(); + + $column['nb_tasks'] = count($column['tasks']); + $column['score'] = (int) array_column_sum($column['tasks'], 'score'); + + if (empty($this->taskCountBySwimlaneAndColumn)) { + $column['column_nb_open_tasks'] = $column['nb_open_tasks']; + } else { + $column['column_nb_open_tasks'] = isset($this->taskCountBySwimlaneAndColumn[$this->swimlaneId][$column['id']]) ? $this->taskCountBySwimlaneAndColumn[$this->swimlaneId][$column['id']] : 0; + } + } + + return $this->columns; + } +} diff --git a/app/Formatter/BoardFormatter.php b/app/Formatter/BoardFormatter.php new file mode 100644 index 0000000..53b149e --- /dev/null +++ b/app/Formatter/BoardFormatter.php @@ -0,0 +1,80 @@ +<?php + +namespace Kanboard\Formatter; + +use Kanboard\Core\Filter\FormatterInterface; +use Kanboard\Model\ProjectModel; +use Kanboard\Model\SwimlaneModel; +use Kanboard\Model\TaskModel; + +/** + * Board Formatter + * + * @package formatter + * @author Frederic Guillot + */ +class BoardFormatter extends BaseFormatter implements FormatterInterface +{ + /** + * Project id + * + * @access protected + * @var integer + */ + protected $projectId; + + /** + * Set ProjectId + * + * @access public + * @param integer $projectId + * @return $this + */ + public function withProjectId($projectId) + { + $this->projectId = $projectId; + return $this; + } + + /** + * Apply formatter + * + * @access public + * @return array + */ + public function format() + { + $project = $this->projectModel->getById($this->projectId); + $swimlanes = $this->swimlaneModel->getAllByStatus($this->projectId, SwimlaneModel::ACTIVE); + $columns = $this->columnModel->getAllWithOpenedTaskCount($this->projectId); + $task_count_by_swimlanes_and_columns = []; + + if ($project['per_swimlane_task_limits']) { + foreach ($this->taskModel->getOpenTaskCountBySwimlaneAndColumn($this->projectId) as $task_count) { + $task_count_by_swimlanes_and_columns[$task_count['swimlane_id']][$task_count['column_id']] = $task_count['nb_open_tasks']; + } + } + + if (empty($swimlanes) || empty($columns)) { + return array(); + } + + $this->hook->reference('formatter:board:query', $this->query); + + $tasks = $this->query + ->eq(TaskModel::TABLE.'.project_id', $this->projectId) + ->asc(TaskModel::TABLE.'.position') + ->findAll(); + + $task_ids = array_column($tasks, 'id'); + $tags = $this->taskTagModel->getTagsByTaskIds($task_ids); + + return $this->boardSwimlaneFormatter + ->withSwimlanes($swimlanes) + ->withColumns($columns) + ->withTasks($tasks) + ->withTags($tags) + ->withTaskCountBySwimlaneAndColumn($task_count_by_swimlanes_and_columns) + ->format(); + } +} diff --git a/app/Formatter/BoardSwimlaneFormatter.php b/app/Formatter/BoardSwimlaneFormatter.php new file mode 100644 index 0000000..3046d69 --- /dev/null +++ b/app/Formatter/BoardSwimlaneFormatter.php @@ -0,0 +1,138 @@ +<?php + +namespace Kanboard\Formatter; + +use Kanboard\Core\Filter\FormatterInterface; + +/** + * Board Swimlane Formatter + * + * @package formatter + * @author Frederic Guillot + */ +class BoardSwimlaneFormatter extends BaseFormatter implements FormatterInterface +{ + protected $swimlanes = array(); + protected $columns = array(); + protected $tasks = array(); + protected $tags = array(); + protected $taskCountBySwimlaneAndColumn = array(); + + /** + * Set swimlanes + * + * @access public + * @param array $swimlanes + * @return $this + */ + public function withSwimlanes(array $swimlanes) + { + $this->swimlanes = $swimlanes; + return $this; + } + + /** + * Set columns + * + * @access public + * @param array $columns + * @return $this + */ + public function withColumns(array $columns) + { + $this->columns = $columns; + return $this; + } + + /** + * Set tasks + * + * @access public + * @param array $tasks + * @return $this + */ + public function withTasks(array $tasks) + { + $this->tasks = $tasks; + return $this; + } + + /** + * Set tags + * + * @access public + * @param array $tags + * @return $this + */ + public function withTags(array $tags) + { + $this->tags = $tags; + return $this; + } + + /** + * Set task count by swimlane and column + * + * @access public + * @param array $taskCountBySwimlaneAndColumn + * @return $this + */ + public function withTaskCountBySwimlaneAndColumn(array $taskCountBySwimlaneAndColumn) + { + $this->taskCountBySwimlaneAndColumn = $taskCountBySwimlaneAndColumn; + return $this; + } + + /** + * Apply formatter + * + * @access public + * @return array + */ + public function format() + { + $nb_swimlanes = count($this->swimlanes); + $nb_columns = count($this->columns); + $tasks_stats_by_column_across_swimlanes = []; + + foreach ($this->columns as $column) { + $tasks_stats_by_column_across_swimlanes[$column['id']] = [ + 'nb_visible_tasks_across_swimlane' => 0, + 'nb_unfiltered_tasks_across_swimlane' => 0, + 'cumulative_score_across_swimlane' => 0, + ]; + } + + foreach ($this->swimlanes as &$swimlane) { + $swimlane['id'] = (int) $swimlane['id']; + $swimlane['columns'] = $this->boardColumnFormatter + ->withSwimlaneId($swimlane['id']) + ->withColumns($this->columns) + ->withTasks($this->tasks) + ->withTags($this->tags) + ->withTaskCountBySwimlaneAndColumn($this->taskCountBySwimlaneAndColumn) + ->format(); + + $swimlane['nb_swimlanes'] = $nb_swimlanes; + $swimlane['nb_columns'] = $nb_columns; + $swimlane['nb_tasks'] = array_column_sum($swimlane['columns'], 'nb_tasks'); + $swimlane['score'] = array_column_sum($swimlane['columns'], 'score'); + + foreach ($swimlane['columns'] as &$column) { + $tasks_stats_by_column_across_swimlanes[$column['id']]['nb_visible_tasks_across_swimlane'] += count($column['tasks']); + $tasks_stats_by_column_across_swimlanes[$column['id']]['nb_unfiltered_tasks_across_swimlane'] = $column['nb_open_tasks']; + $tasks_stats_by_column_across_swimlanes[$column['id']]['cumulative_score_across_swimlane'] += $column['score']; + } + } + + foreach ($this->swimlanes as &$swimlane) { + foreach ($swimlane['columns'] as &$column) { + $column['nb_visible_tasks_across_swimlane'] = $tasks_stats_by_column_across_swimlanes[$column['id']]['nb_visible_tasks_across_swimlane']; + $column['nb_unfiltered_tasks_across_swimlane'] = $tasks_stats_by_column_across_swimlanes[$column['id']]['nb_unfiltered_tasks_across_swimlane']; + $column['cumulative_score_across_swimlane'] = $tasks_stats_by_column_across_swimlanes[$column['id']]['cumulative_score_across_swimlane']; + } + } + + return $this->swimlanes; + } +} diff --git a/app/Formatter/BoardTaskFormatter.php b/app/Formatter/BoardTaskFormatter.php new file mode 100644 index 0000000..cd10f77 --- /dev/null +++ b/app/Formatter/BoardTaskFormatter.php @@ -0,0 +1,101 @@ +<?php + +namespace Kanboard\Formatter; + +use Kanboard\Core\Filter\FormatterInterface; + +/** + * Board Task Formatter + * + * @package formatter + * @author Frederic Guillot + */ +class BoardTaskFormatter extends BaseFormatter implements FormatterInterface +{ + protected $tasks = array(); + protected $tags = array(); + protected $columnId = 0; + protected $swimlaneId = 0; + + /** + * Set tags + * + * @access public + * @param array $tags + * @return $this + */ + public function withTags(array $tags) + { + $this->tags = $tags; + return $this; + } + + /** + * Set tasks + * + * @access public + * @param array $tasks + * @return $this + */ + public function withTasks(array $tasks) + { + $this->tasks = $tasks; + return $this; + } + + /** + * Set columnId + * + * @access public + * @param integer $columnId + * @return $this + */ + public function withColumnId($columnId) + { + $this->columnId = $columnId; + return $this; + } + + /** + * Set swimlaneId + * + * @access public + * @param integer $swimlaneId + * @return $this + */ + public function withSwimlaneId($swimlaneId) + { + $this->swimlaneId = $swimlaneId; + return $this; + } + + /** + * Apply formatter + * + * @access public + * @return array + */ + public function format() + { + $tasks = array_values(array_filter($this->tasks, array($this, 'filterTasks'))); + array_merge_relation($tasks, $this->tags, 'tags', 'id'); + + foreach ($tasks as &$task) { + $task['is_draggable'] = $this->helper->projectRole->isDraggable($task); + } + + return $tasks; + } + + /** + * Keep only tasks of the given column and swimlane + * + * @access protected + * @param array $task + * @return bool + */ + protected function filterTasks(array $task) + { + return $task['column_id'] == $this->columnId && $task['swimlane_id'] == $this->swimlaneId; + } +} diff --git a/app/Formatter/GroupAutoCompleteFormatter.php b/app/Formatter/GroupAutoCompleteFormatter.php new file mode 100644 index 0000000..9d740b7 --- /dev/null +++ b/app/Formatter/GroupAutoCompleteFormatter.php @@ -0,0 +1,58 @@ +<?php + +namespace Kanboard\Formatter; + +use Kanboard\Core\Filter\FormatterInterface; +use Kanboard\Core\Group\GroupProviderInterface; + +/** + * Auto-complete formatter for groups + * + * @package Kanboard\Formatter + * @author Frederic Guillot + */ +class GroupAutoCompleteFormatter extends BaseFormatter implements FormatterInterface +{ + /** + * Groups found + * + * @access protected + * @var GroupProviderInterface[] + */ + protected $groups; + + /** + * Set groups + * + * @access public + * @param GroupProviderInterface[] $groups + * @return $this + */ + public function withGroups(array $groups) + { + $this->groups = $groups; + return $this; + } + + /** + * Format groups for the ajax auto-completion + * + * @access public + * @return array + */ + public function format() + { + $result = array(); + + foreach ($this->groups as $group) { + $result[] = array( + 'id' => $group->getInternalId(), + 'external_id' => $group->getExternalId(), + 'value' => $group->getName(), + 'label' => $group->getName(), + ); + } + + return $result; + } +} diff --git a/app/Formatter/ProjectActivityEventFormatter.php b/app/Formatter/ProjectActivityEventFormatter.php new file mode 100644 index 0000000..3741c2a --- /dev/null +++ b/app/Formatter/ProjectActivityEventFormatter.php @@ -0,0 +1,79 @@ +<?php + +namespace Kanboard\Formatter; + +use Kanboard\Core\Filter\FormatterInterface; +use Kanboard\Core\Security\Role; + +class ProjectActivityEventFormatter extends BaseFormatter implements FormatterInterface +{ + /** + * Apply formatter + * + * @access public + * @return array + */ + public function format() + { + $events = $this->query->findAll(); + $res = array(); + + foreach ($events as &$event) { + $eventData = $this->unserializeEvent($event['data']); + if (empty($eventData)) { + continue; + } + + $event += $eventData; + unset($event['data']); + + if (isset($event['comment'])) { + if ($this->userSession->getRole() === Role::APP_USER && $event['comment']['visibility'] !== Role::APP_USER) { + continue; + } + if ($this->userSession->getRole() === Role::APP_MANAGER && $event['comment']['visibility'] === Role::APP_ADMIN) { + continue; + } + } + + $event['author'] = $event['author_name'] ?: $event['author_username']; + $event['event_title'] = $this->notificationModel->getTitleWithAuthor($event['author'], $event['event_name'], $event); + $event['event_content'] = $this->renderEvent($event); + $res[] = $event; + } + + return $res; + } + + /** + * Decode event data, supports unserialize() and json_decode() + * + * @access protected + * @param string $data Serialized data + * @return array + */ + protected function unserializeEvent($data) + { + // Ignore legacy events serialized with PHP due potential security issues. + if ($data[0] === 'a') { + return []; + } + + return json_decode($data, true) ?: []; + } + + /** + * Get the event html content + * + * @access protected + * @param array $params Event properties + * @return string + */ + protected function renderEvent(array $params) + { + return $this->template->render( + 'event/'.str_replace('.', '_', $params['event_name']), + $params + ); + } +} diff --git a/app/Formatter/ProjectApiFormatter.php b/app/Formatter/ProjectApiFormatter.php new file mode 100644 index 0000000..fbb6cdf --- /dev/null +++ b/app/Formatter/ProjectApiFormatter.php @@ -0,0 +1,46 @@ +<?php + +namespace Kanboard\Formatter; + +use Kanboard\Core\Filter\FormatterInterface; + +/** + * Class ProjectApiFormatter + * + * @package Kanboard\Formatter + */ +class ProjectApiFormatter extends BaseFormatter implements FormatterInterface +{ + protected $project = null; + + public function withProject($project) + { + $this->project = $project; + return $this; + } + + /** + * Apply formatter + * + * @access public + * @return mixed + */ + public function format() + { + if (! empty($this->project)) { + $this->project['url'] = array( + 'board' => $this->helper->url->to('BoardViewController', 'show', array('project_id' => $this->project['id']), '', true), + 'list' => $this->helper->url->to('TaskListController', 'show', array('project_id' => $this->project['id']), '', true), + ); + + // Add public board URL if public access is enabled + if (!empty($this->project['is_public']) && !empty($this->project['token'])) { + $this->project['url']['public_board'] = $this->helper->url->to('BoardViewController', 'readonly', array('token' => $this->project['token']), '', true); + $this->project['url']['rss_feed'] = $this->helper->url->to('FeedController', 'project', array('token' => $this->project['token']), '', true); + $this->project['url']['ical_feed'] = $this->helper->url->to('ICalendarController', 'project', array('token' => $this->project['token']), '', true); + } + } + + return $this->project; + } +} diff --git a/app/Formatter/ProjectsApiFormatter.php b/app/Formatter/ProjectsApiFormatter.php new file mode 100644 index 0000000..0bf97da --- /dev/null +++ b/app/Formatter/ProjectsApiFormatter.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Formatter; + +use Kanboard\Core\Filter\FormatterInterface; + +/** + * Class ProjectsApiFormatter + * + * @package Kanboard\Formatter + */ +class ProjectsApiFormatter extends BaseFormatter implements FormatterInterface +{ + protected $projects = array(); + + public function withProjects($projects) + { + $this->projects = $projects; + return $this; + } + + /** + * Apply formatter + * + * @access public + * @return mixed + */ + public function format() + { + if (! empty($this->projects)) { + foreach ($this->projects as &$project) { + $project = $this->projectApiFormatter->withProject($project)->format(); + } + } + + return $this->projects; + } +} diff --git a/app/Formatter/SubtaskListFormatter.php b/app/Formatter/SubtaskListFormatter.php new file mode 100644 index 0000000..8af5b1f --- /dev/null +++ b/app/Formatter/SubtaskListFormatter.php @@ -0,0 +1,34 @@ +<?php + +namespace Kanboard\Formatter; + +use Kanboard\Core\Filter\FormatterInterface; + +/** + * Class SubtaskListFormatter + * + * @package Kanboard\Formatter + * @author Frederic Guillot + */ +class SubtaskListFormatter extends BaseFormatter implements FormatterInterface +{ + /** + * Apply formatter + * + * @access public + * @return array + */ + public function format() + { + $status = $this->subtaskModel->getStatusList(); + $subtasks = $this->query->findAll(); + + foreach ($subtasks as &$subtask) { + $subtask['status_name'] = $status[$subtask['status']]; + $subtask['timer_start_date'] = isset($subtask['timer_start_date']) ? $subtask['timer_start_date'] : 0; + $subtask['is_timer_started'] = ! empty($subtask['timer_start_date']); + } + + return $subtasks; + } +} diff --git a/app/Formatter/SubtaskTimeTrackingCalendarFormatter.php b/app/Formatter/SubtaskTimeTrackingCalendarFormatter.php new file mode 100644 index 0000000..0346172 --- /dev/null +++ b/app/Formatter/SubtaskTimeTrackingCalendarFormatter.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Formatter; + +use Kanboard\Core\Filter\FormatterInterface; + +class SubtaskTimeTrackingCalendarFormatter extends BaseFormatter implements FormatterInterface +{ + /** + * Format calendar events + * + * @access public + * @return array + */ + public function format() + { + $events = array(); + + foreach ($this->query->findAll() as $row) { + $user = isset($row['username']) ? ' ('.($row['user_fullname'] ?: $row['username']).')' : ''; + + $events[] = array( + 'id' => $row['id'], + 'subtask_id' => $row['subtask_id'], + 'title' => t('#%d', $row['task_id']).' '.$row['subtask_title'].$user, + 'start' => date('Y-m-d\TH:i:s', $row['start']), + 'end' => date('Y-m-d\TH:i:s', $row['end'] ?: time()), + 'backgroundColor' => $this->colorModel->getBackgroundColor($row['color_id']), + 'borderColor' => $this->colorModel->getBorderColor($row['color_id']), + 'textColor' => 'black', + 'url' => $this->helper->url->to('TaskViewController', 'show', array('task_id' => $row['task_id'])), + 'editable' => false, + ); + } + + return $events; + } +} diff --git a/app/Formatter/TaskApiFormatter.php b/app/Formatter/TaskApiFormatter.php new file mode 100644 index 0000000..f2e25d0 --- /dev/null +++ b/app/Formatter/TaskApiFormatter.php @@ -0,0 +1,37 @@ +<?php + +namespace Kanboard\Formatter; + +use Kanboard\Core\Filter\FormatterInterface; + +/** + * Class TaskApiFormatter + * + * @package Kanboard\Formatter + */ +class TaskApiFormatter extends BaseFormatter implements FormatterInterface +{ + protected $task = null; + + public function withTask($task) + { + $this->task = $task; + return $this; + } + + /** + * Apply formatter + * + * @access public + * @return mixed + */ + public function format() + { + if (! empty($this->task)) { + $this->task['url'] = $this->helper->url->to('TaskViewController', 'show', array('task_id' => $this->task['id']), '', true); + $this->task['color'] = $this->colorModel->getColorProperties($this->task['color_id']); + } + + return $this->task; + } +} diff --git a/app/Formatter/TaskAutoCompleteFormatter.php b/app/Formatter/TaskAutoCompleteFormatter.php new file mode 100644 index 0000000..3a4f1e1 --- /dev/null +++ b/app/Formatter/TaskAutoCompleteFormatter.php @@ -0,0 +1,56 @@ +<?php + +namespace Kanboard\Formatter; + +use Kanboard\Core\Filter\FormatterInterface; +use Kanboard\Model\ProjectModel; +use Kanboard\Model\TaskModel; + +/** + * Task AutoComplete Formatter + * + * @package formatter + * @author Frederic Guillot + */ +class TaskAutoCompleteFormatter extends BaseFormatter implements FormatterInterface +{ + protected $limit = 25; + + /** + * Limit number of results + * + * @param $limit + * @return $this + */ + public function withLimit($limit) + { + $this->limit = $limit; + return $this; + } + + /** + * Apply formatter + * + * @access public + * @return array + */ + public function format() + { + $tasks = $this->query + ->columns( + TaskModel::TABLE.'.id', + TaskModel::TABLE.'.title', + ProjectModel::TABLE.'.name AS project_name' + ) + ->asc(TaskModel::TABLE.'.id') + ->limit($this->limit) + ->findAll(); + + foreach ($tasks as &$task) { + $task['value'] = $task['title']; + $task['label'] = $task['project_name'].' > #'.$task['id'].' '.$task['title']; + } + + return $tasks; + } +} diff --git a/app/Formatter/TaskICalFormatter.php b/app/Formatter/TaskICalFormatter.php new file mode 100644 index 0000000..abe595f --- /dev/null +++ b/app/Formatter/TaskICalFormatter.php @@ -0,0 +1,151 @@ +<?php + +namespace Kanboard\Formatter; + +use DateTime; +use Eluceo\iCal\Component\Calendar; +use Eluceo\iCal\Component\Event; +use Eluceo\iCal\Property\Event\Attendees; +use Eluceo\iCal\Property\Event\Organizer; +use Kanboard\Core\Filter\FormatterInterface; +use PicoDb\Table; + +/** + * iCal event formatter for tasks + * + * @package formatter + * @author Frederic Guillot + */ +class TaskICalFormatter extends BaseFormatter implements FormatterInterface +{ + /** + * Calendar object + * + * @access protected + * @var \Eluceo\iCal\Component\Calendar + */ + protected $vCalendar; + + /** + * Get Ical events + * + * @access public + * @return string + */ + public function format() + { + return $this->vCalendar->render(); + } + + /** + * Set calendar object + * + * @access public + * @param \Eluceo\iCal\Component\Calendar $vCalendar + * @return $this + */ + public function setCalendar(Calendar $vCalendar) + { + $this->vCalendar = $vCalendar; + return $this; + } + + /** + * Transform results to iCal events + * + * @access public + * @param Table $query + * @param string $startColumn + * @param string $endColumn + * @return $this + */ + public function addTasksWithStartAndDueDate(Table $query, $startColumn, $endColumn) + { + foreach ($query->findAll() as $task) { + $start = new DateTime; + $start->setTimestamp($task[$startColumn]); + + $end = new DateTime; + $end->setTimestamp($task[$endColumn] ?: time()); + + $vEvent = $this->getTaskIcalEvent($task, 'task-#'.$task['id'].'-'.$startColumn.'-'.$endColumn); + $vEvent->setDtStart($start); + $vEvent->setDtEnd($end); + + $this->vCalendar->addComponent($vEvent); + } + + return $this; + } + + /** + * Transform results to all day iCal events + * + * @access public + * @param Table $query + * @return $this + */ + public function addTasksWithDueDateOnly(Table $query) + { + foreach ($query->findAll() as $task) { + $date = new DateTime; + $date->setTimestamp($task['date_due']); + + $vEvent = $this->getTaskIcalEvent($task, 'task-#'.$task['id'].'-date_due'); + $vEvent->setDtStart($date); + $vEvent->setDtEnd($date); + + if ($date->format('Hi') === '0000') { + $vEvent->setNoTime(true); + } + + $this->vCalendar->addComponent($vEvent); + } + + return $this; + } + + /** + * Get common events for task iCal events + * + * @access protected + * @param array $task + * @param string $uid + * @return Event + */ + protected function getTaskIcalEvent(array &$task, $uid) + { + $dateCreation = new DateTime; + $dateCreation->setTimestamp($task['date_creation']); + + $dateModif = new DateTime; + $dateModif->setTimestamp($task['date_modification']); + + $vEvent = new Event($uid); + $vEvent->setCreated($dateCreation); + $vEvent->setModified($dateModif); + $vEvent->setUseTimezone(true); + $vEvent->setSummary(t('#%d', $task['id']).' '.$task['title']); + $vEvent->setDescription($task['description']); + $vEvent->setDescriptionHTML($this->helper->text->markdown($task['description'])); + $vEvent->setUrl($this->helper->url->base().$this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id']))); + + if (! empty($task['owner_id'])) { + $attendees = new Attendees; + $attendees->add( + 'MAILTO:'.($task['assignee_email'] ?: $task['assignee_username'].'@kanboard.local'), + array('CN' => $task['assignee_name'] ?: $task['assignee_username']) + ); + $vEvent->setAttendees($attendees); + } + + if (! empty($task['creator_id'])) { + $vEvent->setOrganizer(new Organizer( + 'MAILTO:' . $task['creator_email'] ?: $task['creator_username'].'@kanboard.local', + array('CN' => $task['creator_name'] ?: $task['creator_username']) + )); + } + + return $vEvent; + } +} diff --git a/app/Formatter/TaskListFormatter.php b/app/Formatter/TaskListFormatter.php new file mode 100644 index 0000000..2f83469 --- /dev/null +++ b/app/Formatter/TaskListFormatter.php @@ -0,0 +1,30 @@ +<?php + +namespace Kanboard\Formatter; + +use Kanboard\Core\Filter\FormatterInterface; + +/** + * Class TaskListFormatter + * + * @package Kanboard\Formatter + * @author Frederic Guillot + */ +class TaskListFormatter extends BaseFormatter implements FormatterInterface +{ + /** + * Apply formatter + * + * @access public + * @return array + */ + public function format() + { + $tasks = $this->query->findAll(); + $taskIds = array_column($tasks, 'id'); + $tags = $this->taskTagModel->getTagsByTaskIds($taskIds); + array_merge_relation($tasks, $tags, 'tags', 'id'); + + return $tasks; + } +} diff --git a/app/Formatter/TaskListSubtaskAssigneeFormatter.php b/app/Formatter/TaskListSubtaskAssigneeFormatter.php new file mode 100644 index 0000000..7339176 --- /dev/null +++ b/app/Formatter/TaskListSubtaskAssigneeFormatter.php @@ -0,0 +1,56 @@ +<?php + +namespace Kanboard\Formatter; + +/** + * Class TaskListSubtaskAssigneeFormatter + * + * @package Kanboard\Formatter + * @author Frederic Guillot + */ +class TaskListSubtaskAssigneeFormatter extends TaskListFormatter +{ + protected $userId = 0; + protected $withoutEmptyTasks = false; + + /** + * Set assignee + * + * @param integer $userId + * @return $this + */ + public function withUserId($userId) + { + $this->userId = $userId; + return $this; + } + + public function withoutEmptyTasks() + { + $this->withoutEmptyTasks = true; + return $this; + } + + /** + * Apply formatter + * + * @access public + * @return array + */ + public function format() + { + $tasks = parent::format(); + $taskIds = array_column($tasks, 'id'); + $subtasks = $this->subtaskModel->getAllByTaskIdsAndAssignee($taskIds, $this->userId); + $subtasks = array_column_index($subtasks, 'task_id'); + array_merge_relation($tasks, $subtasks, 'subtasks', 'id'); + + if ($this->withoutEmptyTasks) { + $tasks = array_filter($tasks, function (array $task) { + return count($task['subtasks']) > 0; + }); + } + + return $tasks; + } +} diff --git a/app/Formatter/TaskListSubtaskFormatter.php b/app/Formatter/TaskListSubtaskFormatter.php new file mode 100644 index 0000000..4f88967 --- /dev/null +++ b/app/Formatter/TaskListSubtaskFormatter.php @@ -0,0 +1,29 @@ +<?php + +namespace Kanboard\Formatter; + +/** + * Class TaskListSubtaskFormatter + * + * @package Kanboard\Formatter + * @author Frederic Guillot + */ +class TaskListSubtaskFormatter extends TaskListFormatter +{ + /** + * Apply formatter + * + * @access public + * @return array + */ + public function format() + { + $tasks = parent::format(); + $taskIds = array_column($tasks, 'id'); + $subtasks = $this->subtaskModel->getAllByTaskIds($taskIds); + $subtasks = array_column_index($subtasks, 'task_id'); + array_merge_relation($tasks, $subtasks, 'subtasks', 'id'); + + return $tasks; + } +} diff --git a/app/Formatter/TaskSuggestMenuFormatter.php b/app/Formatter/TaskSuggestMenuFormatter.php new file mode 100644 index 0000000..518f99e --- /dev/null +++ b/app/Formatter/TaskSuggestMenuFormatter.php @@ -0,0 +1,63 @@ +<?php + +namespace Kanboard\Formatter; + +use Kanboard\Core\Filter\FormatterInterface; +use Kanboard\Model\ProjectModel; +use Kanboard\Model\TaskModel; + +/** + * Class TaskSuggestMenuFormatter + * + * @package Kanboard\Formatter + * @author Frederic Guillot + */ +class TaskSuggestMenuFormatter extends BaseFormatter implements FormatterInterface +{ + protected $limit = 25; + + /** + * Limit number of results + * + * @param $limit + * @return $this + */ + public function withLimit($limit) + { + $this->limit = $limit; + return $this; + } + + /** + * Apply formatter + * + * @access public + * @return mixed + */ + public function format() + { + $result = array(); + $tasks = $this->query + ->columns( + TaskModel::TABLE.'.id', + TaskModel::TABLE.'.title', + ProjectModel::TABLE.'.name AS project_name' + ) + ->asc(TaskModel::TABLE.'.id') + ->limit($this->limit) + ->findAll(); + + foreach ($tasks as $task) { + $html = '#'.$task['id'].' '; + $html .= $this->helper->text->e($task['title']).' '; + $html .= '<small>'.$this->helper->text->e($task['project_name']).'</small>'; + + $result[] = array( + 'value' => (string) $task['id'], + 'html' => $html, + ); + } + + return $result; + } +} diff --git a/app/Formatter/TasksApiFormatter.php b/app/Formatter/TasksApiFormatter.php new file mode 100644 index 0000000..95b1409 --- /dev/null +++ b/app/Formatter/TasksApiFormatter.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Formatter; + +use Kanboard\Core\Filter\FormatterInterface; + +/** + * Class TasksApiFormatter + * + * @package Kanboard\Formatter + */ +class TasksApiFormatter extends BaseFormatter implements FormatterInterface +{ + protected $tasks = array(); + + public function withTasks($tasks) + { + $this->tasks = $tasks; + return $this; + } + + /** + * Apply formatter + * + * @access public + * @return mixed + */ + public function format() + { + if (! empty($this->tasks)) { + foreach ($this->tasks as &$task) { + $task = $this->taskApiFormatter->withTask($task)->format(); + } + } + + return $this->tasks; + } +} diff --git a/app/Formatter/UserAutoCompleteFormatter.php b/app/Formatter/UserAutoCompleteFormatter.php new file mode 100644 index 0000000..aa02cd2 --- /dev/null +++ b/app/Formatter/UserAutoCompleteFormatter.php @@ -0,0 +1,60 @@ +<?php + +namespace Kanboard\Formatter; + +use Kanboard\Core\User\UserProviderInterface; +use Kanboard\Core\Filter\FormatterInterface; + +/** + * Auto-complete formatter for users + * + * @package Kanboard\Formatter + * @author Frederic Guillot + */ +class UserAutoCompleteFormatter extends BaseFormatter implements FormatterInterface +{ + /** + * Users found + * + * @access protected + * @var UserProviderInterface[] + */ + protected $users; + + /** + * Set users + * + * @access public + * @param UserProviderInterface[] $users + * @return $this + */ + public function withUsers(array $users) + { + $this->users = $users; + return $this; + } + + /** + * Format the users for the ajax auto-completion + * + * @access public + * @return array + */ + public function format() + { + $result = array(); + + foreach ($this->users as $user) { + $result[] = array( + 'id' => $user->getInternalId(), + 'username' => $user->getUsername(), + 'external_id' => $user->getExternalId(), + 'external_id_column' => $user->getExternalIdColumn(), + 'value' => $user->getName() === '' ? $user->getUsername() : $user->getName(), + 'label' => $user->getName() === '' ? $user->getUsername() : $user->getName().' ('.$user->getUsername().')', + ); + } + + return $result; + } +} diff --git a/app/Formatter/UserMentionFormatter.php b/app/Formatter/UserMentionFormatter.php new file mode 100644 index 0000000..615e2b4 --- /dev/null +++ b/app/Formatter/UserMentionFormatter.php @@ -0,0 +1,63 @@ +<?php + +namespace Kanboard\Formatter; + +use Kanboard\Core\Filter\FormatterInterface; + +/** + * Class UserMentionFormatter + * + * @package Kanboard\Formatter + * @author Frederic Guillot + */ +class UserMentionFormatter extends BaseFormatter implements FormatterInterface +{ + protected $users = array(); + + /** + * Set users + * + * @param array $users + * @return $this + */ + public function withUsers(array $users) + { + $this->users = $users; + return $this; + } + + /** + * Apply formatter + * + * @access public + * @return array + */ + public function format() + { + $result = array(); + + foreach ($this->users as $user) { + $html = $this->helper->avatar->small( + $user['id'], + $user['username'], + $user['name'], + $user['email'], + $user['avatar_path'], + 'avatar-inline' + ); + + $html .= ' '.$this->helper->text->e($user['username']); + + if (! empty($user['name'])) { + $html .= ' <small aria-hidden="true">'.$this->helper->text->e($user['name']).'</small>'; + } + + $result[] = array( + 'value' => $user['username'], + 'html' => $html, + ); + } + + return $result; + } +} diff --git a/app/Group/DatabaseBackendGroupProvider.php b/app/Group/DatabaseBackendGroupProvider.php new file mode 100644 index 0000000..29d04d5 --- /dev/null +++ b/app/Group/DatabaseBackendGroupProvider.php @@ -0,0 +1,34 @@ +<?php + +namespace Kanboard\Group; + +use Kanboard\Core\Base; +use Kanboard\Core\Group\GroupBackendProviderInterface; + +/** + * Database Backend Group Provider + * + * @package group + * @author Frederic Guillot + */ +class DatabaseBackendGroupProvider extends Base implements GroupBackendProviderInterface +{ + /** + * Find a group from a search query + * + * @access public + * @param string $input + * @return DatabaseGroupProvider[] + */ + public function find($input) + { + $result = array(); + $groups = $this->groupModel->search($input); + + foreach ($groups as $group) { + $result[] = new DatabaseGroupProvider($group); + } + + return $result; + } +} diff --git a/app/Group/DatabaseGroupProvider.php b/app/Group/DatabaseGroupProvider.php new file mode 100644 index 0000000..430121a --- /dev/null +++ b/app/Group/DatabaseGroupProvider.php @@ -0,0 +1,66 @@ +<?php + +namespace Kanboard\Group; + +use Kanboard\Core\Group\GroupProviderInterface; + +/** + * Database Group Provider + * + * @package group + * @author Frederic Guillot + */ +class DatabaseGroupProvider implements GroupProviderInterface +{ + /** + * Group properties + * + * @access private + * @var array + */ + private $group = array(); + + /** + * Constructor + * + * @access public + * @param array $group + */ + public function __construct(array $group) + { + $this->group = $group; + } + + /** + * Get internal id + * + * @access public + * @return integer + */ + public function getInternalId() + { + return (int) $this->group['id']; + } + + /** + * Get external id + * + * @access public + * @return string + */ + public function getExternalId() + { + return ''; + } + + /** + * Get group name + * + * @access public + * @return string + */ + public function getName() + { + return $this->group['name']; + } +} diff --git a/app/Group/LdapBackendGroupProvider.php b/app/Group/LdapBackendGroupProvider.php new file mode 100644 index 0000000..411d431 --- /dev/null +++ b/app/Group/LdapBackendGroupProvider.php @@ -0,0 +1,56 @@ +<?php + +namespace Kanboard\Group; + +use LogicException; +use Kanboard\Core\Base; +use Kanboard\Core\Group\GroupBackendProviderInterface; +use Kanboard\Core\Ldap\Client as LdapClient; +use Kanboard\Core\Ldap\ClientException as LdapException; +use Kanboard\Core\Ldap\Group as LdapGroup; + +/** + * LDAP Backend Group Provider + * + * @package group + * @author Frederic Guillot + */ +class LdapBackendGroupProvider extends Base implements GroupBackendProviderInterface +{ + /** + * Find a group from a search query + * + * @access public + * @param string $input + * @return LdapGroupProvider[] + */ + public function find($input) + { + try { + $ldap = LdapClient::connect(); + return LdapGroup::getGroups($ldap, $this->getLdapGroupPattern($input)); + + } catch (LdapException $e) { + $this->logger->error($e->getMessage()); + return array(); + } + } + + /** + * Get LDAP group pattern + * + * @access public + * @param string $input + * @param string $filter + * @return string + */ + public function getLdapGroupPattern($input, $filter = LDAP_GROUP_FILTER) + { + if ($filter === '') { + throw new LogicException('LDAP group filter is empty. Please configure the LDAP_GROUP_FILTER parameter in your configuration file'); + } + + $escapedInput = ldap_escape($input, '', LDAP_ESCAPE_FILTER); + return sprintf($filter, $escapedInput); + } +} diff --git a/app/Group/LdapGroupProvider.php b/app/Group/LdapGroupProvider.php new file mode 100644 index 0000000..b497d48 --- /dev/null +++ b/app/Group/LdapGroupProvider.php @@ -0,0 +1,76 @@ +<?php + +namespace Kanboard\Group; + +use Kanboard\Core\Group\GroupProviderInterface; + +/** + * LDAP Group Provider + * + * @package group + * @author Frederic Guillot + */ +class LdapGroupProvider implements GroupProviderInterface +{ + /** + * Group DN + * + * @access private + * @var string + */ + private $dn = ''; + + /** + * Group Name + * + * @access private + * @var string + */ + private $name = ''; + + /** + * Constructor + * + * @access public + * @param string $dn + * @param string $name + */ + public function __construct($dn, $name) + { + $this->dn = $dn; + $this->name = $name; + } + + /** + * Get internal id + * + * @access public + * @return integer + */ + public function getInternalId() + { + return ''; + } + + /** + * Get external id + * + * @access public + * @return string + */ + public function getExternalId() + { + return $this->dn; + } + + /** + * Get group name + * + * @access public + * @return string + */ + public function getName() + { + return $this->name; + } +} diff --git a/app/Helper/AppHelper.php b/app/Helper/AppHelper.php new file mode 100644 index 0000000..4562f25 --- /dev/null +++ b/app/Helper/AppHelper.php @@ -0,0 +1,209 @@ +<?php + +namespace Kanboard\Helper; + +use Kanboard\Core\Base; + +/** + * Application Helper + * + * @package helper + * @author Frederic Guillot + */ +class AppHelper extends Base +{ + public function tooltipMarkdown($markdownText, $icon = 'fa-info-circle') + { + return '<span class="tooltip"><i class="fa '.$icon.'"></i><script type="text/template"><div class="markdown">'.$this->helper->text->markdown($markdownText).'</div></script></span>'; + } + + public function tooltipHTML($htmlText, $icon = 'fa-info-circle') + { + return '<span class="tooltip"><i class="fa '.$icon.'"></i><script type="text/template"><div class="markdown">'.$htmlText.'</div></script></span>'; + } + + public function tooltipLink($label, $link) + { + return '<span class="tooltip" data-href="'.$link.'">'.$label.'</span>'; + } + + public function getToken() + { + return $this->token; + } + + public function isAjax() + { + return $this->request->isAjax(); + } + + /** + * Render Javascript component + * + * @param string $name + * @param array $params + * @return string + */ + public function component($name, array $params = array()) + { + return '<div class="js-'.$name.'" data-params=\''.json_encode($params, JSON_HEX_APOS).'\'></div>'; + } + + /** + * Get config variable + * + * @access public + * @param string $param + * @param mixed $default + * @return mixed + */ + public function config($param, $default = '') + { + return $this->configModel->get($param, $default); + } + + /** + * Make sidebar menu active + * + * @access public + * @param string $controller + * @param string $action + * @param string $plugin + * @return string + */ + public function checkMenuSelection($controller, $action = '', $plugin = '') + { + $result = strtolower($this->getRouterController()) === strtolower($controller); + + if ($result && $action !== '') { + $result = strtolower($this->getRouterAction()) === strtolower($action); + } + + if ($result && $plugin !== '') { + $result = strtolower($this->getPluginName()) === strtolower($plugin); + } + + return $result ? 'class="active"' : ''; + } + + /** + * Get plugin name from route + * + * @access public + * @return string + */ + public function getPluginName() + { + return $this->router->getPlugin(); + } + + /** + * Get router controller + * + * @access public + * @return string + */ + public function getRouterController() + { + return $this->router->getController(); + } + + /** + * Get router action + * + * @access public + * @return string + */ + public function getRouterAction() + { + return $this->router->getAction(); + } + + /** + * Get javascript language code + * + * @access public + * @return string + */ + public function jsLang() + { + return $this->languageModel->getJsLanguageCode(); + } + + /** + * Check if current language requires RTL direction + * + * @access public + * @return bool + */ + public function isRtlLanguage() + { + return $this->languageModel->isRtlLanguage(); + } + + /** + * Get date format for Jquery DatePicker + * + * @access public + * @return string + */ + public function getJsDateFormat() + { + $format = $this->dateParser->getUserDateFormat(); + $format = str_replace('m', 'mm', $format); + $format = str_replace('Y', 'yy', $format); + $format = str_replace('d', 'dd', $format); + + return $format; + } + + /** + * Get time format for Jquery Plugin DateTimePicker + * + * @access public + * @return string + */ + public function getJsTimeFormat() + { + $format = $this->dateParser->getUserTimeFormat(); + $format = str_replace('H', 'HH', $format); + $format = str_replace('i', 'mm', $format); + $format = str_replace('g', 'h', $format); + $format = str_replace('a', 'tt', $format); + + return $format; + } + + /** + * Get current timezone + * + * @access public + * @return string + */ + public function getTimezone() + { + return $this->timezoneModel->getCurrentTimezone(); + } + + /** + * Get session flash message + * + * @access public + * @return string + */ + public function flashMessage() + { + $success_message = $this->flash->getMessage('success'); + $failure_message = $this->flash->getMessage('failure'); + + if (! empty($success_message)) { + return '<div class="alert alert-success alert-fade-out">'.$this->helper->text->e($success_message).'</div>'; + } + + if (! empty($failure_message)) { + return '<div class="alert alert-error">'.$this->helper->text->e($failure_message).'</div>'; + } + + return ''; + } +} diff --git a/app/Helper/AssetHelper.php b/app/Helper/AssetHelper.php new file mode 100644 index 0000000..e2223fc --- /dev/null +++ b/app/Helper/AssetHelper.php @@ -0,0 +1,69 @@ +<?php + +namespace Kanboard\Helper; + +use Kanboard\Core\Base; + +/** + * Asset Helper + * + * @package helper + * @author Frederic Guillot + */ +class AssetHelper extends Base +{ + /** + * Add a Javascript asset + * + * @param string $filename Filename + * @param bool $async + * @return string + */ + public function js($filename, $async = false) + { + $dir = dirname(__DIR__, 2); + $filepath = $dir.'/'.$filename; + return '<script '.($async ? 'async' : '').' defer type="text/javascript" src="'.$this->helper->url->dir().$filename.'?'.filemtime($filepath).'"></script>'; + } + + /** + * Add a stylesheet asset + * + * @param string $filename Filename + * @param boolean $is_file Add file timestamp + * @param string $media Media + * @return string + */ + public function css($filename, $is_file = true, $media = 'screen') + { + $dir = dirname(__DIR__, 2); + $filepath = $dir.'/'.$filename; + return '<link rel="stylesheet" href="'.$this->helper->url->dir().$filename.($is_file ? '?'.filemtime($filepath) : '').'" media="'.$media.'">'; + } + + /** + * Get custom css + * + * @access public + * @return string + */ + public function customCss() + { + if ($this->configModel->get('application_stylesheet')) { + return '<style>'.$this->configModel->get('application_stylesheet').'</style>'; + } + + return ''; + } + + /** + * Get CSS for task colors + * + * @access public + * @return string + */ + public function colorCss() + { + return '<style>'.$this->colorModel->getCss().'</style>'; + } +} diff --git a/app/Helper/AvatarHelper.php b/app/Helper/AvatarHelper.php new file mode 100644 index 0000000..a36d9b4 --- /dev/null +++ b/app/Helper/AvatarHelper.php @@ -0,0 +1,68 @@ +<?php + +namespace Kanboard\Helper; + +use Kanboard\Core\Base; + +/** + * Avatar Helper + * + * @package helper + * @author Frederic Guillot + */ +class AvatarHelper extends Base +{ + /** + * Render user avatar + * + * @access public + * @param string $user_id + * @param string $username + * @param string $name + * @param string $email + * @param string $avatar_path + * @param string $css + * @param int $size + * @return string + */ + public function render($user_id, $username, $name, $email, $avatar_path, $css = 'avatar-left', $size = 48) + { + if (empty($user_id) && empty($username)) { + $html = $this->avatarManager->renderDefault($size); + } else { + $html = $this->avatarManager->render($user_id, $username, $name, $email, $avatar_path, $size); + } + + return '<div class="avatar avatar-'.$size.' '.$css.'">'.$html.'</div>'; + } + + /** + * Render small user avatar + * + * @access public + * @param string $user_id + * @param string $username + * @param string $name + * @param string $email + * @param string $avatar_path + * @param string $css + * @return string + */ + public function small($user_id, $username, $name, $email, $avatar_path, $css = '') + { + return $this->render($user_id, $username, $name, $email, $avatar_path, $css, 20); + } + + /** + * Get a small avatar for the current user + * + * @access public + * @param string $css + * @return string + */ + public function currentUserSmall($css = '') + { + $user = $this->userSession->getAll(); + return $this->small($user['id'], $user['username'], $user['name'], $user['email'], $user['avatar_path'], $css); + } +} diff --git a/app/Helper/BoardHelper.php b/app/Helper/BoardHelper.php new file mode 100644 index 0000000..f5df3db --- /dev/null +++ b/app/Helper/BoardHelper.php @@ -0,0 +1,27 @@ +<?php + +namespace Kanboard\Helper; + +use Kanboard\Core\Base; +use Kanboard\Model\UserMetadataModel; + +/** + * Board Helper + * + * @package helper + * @author Frederic Guillot + */ +class BoardHelper extends Base +{ + /** + * Return true if tasks are collapsed + * + * @access public + * @param integer $project_id + * @return boolean + */ + public function isCollapsed($project_id) + { + return $this->userMetadataCacheDecorator->get(UserMetadataModel::KEY_BOARD_COLLAPSED.$project_id, 0) == 1; + } +} diff --git a/app/Helper/CommentHelper.php b/app/Helper/CommentHelper.php new file mode 100644 index 0000000..d6426d1 --- /dev/null +++ b/app/Helper/CommentHelper.php @@ -0,0 +1,23 @@ +<?php + +namespace Kanboard\Helper; + +use Kanboard\Core\Base; +use Kanboard\Model\UserMetadataModel; + +/** + * Class CommentHelper + * + * @package Kanboard\Helper + * @author Frederic Guillot + */ +class CommentHelper extends Base +{ + public function toggleSorting() + { + $oldDirection = $this->userMetadataCacheDecorator->get(UserMetadataModel::KEY_COMMENT_SORTING_DIRECTION, 'ASC'); + $newDirection = $oldDirection === 'ASC' ? 'DESC' : 'ASC'; + + $this->userMetadataCacheDecorator->set(UserMetadataModel::KEY_COMMENT_SORTING_DIRECTION, $newDirection); + } +} diff --git a/app/Helper/DateHelper.php b/app/Helper/DateHelper.php new file mode 100644 index 0000000..6eb73ee --- /dev/null +++ b/app/Helper/DateHelper.php @@ -0,0 +1,171 @@ +<?php + +namespace Kanboard\Helper; + +use DateTime; +use Kanboard\Core\Base; + +/** + * DateTime helpers + * + * @package helper + * @author Frederic Guillot + */ +class DateHelper extends Base +{ + /** + * Get formatted time + * + * @access public + * @param integer $value + * @return string + */ + public function time($value) + { + return date($this->configModel->get('application_time_format', 'H:i'), $value); + } + + /** + * Get formatted date + * + * @access public + * @param integer $value + * @return string + */ + public function date($value) + { + if (empty($value)) { + return ''; + } + + if (! ctype_digit((string) $value)) { + $value = strtotime($value); + } + + return date($this->configModel->get('application_date_format', 'm/d/Y'), $value); + } + + /** + * Get formatted datetime + * + * @access public + * @param integer $value + * @return string + */ + public function datetime($value) + { + return date($this->dateParser->getUserDateTimeFormat(), $value); + } + + /** + * Get duration in seconds into human format + * + * @access public + * @param integer $seconds + * @return string + */ + public function duration($seconds) + { + if ($seconds == 0) { + return 0; + } + + $dtF = new DateTime("@0"); + $dtT = new DateTime("@$seconds"); + + $format = sprintf("%%a %s, %%h %s, %%i %s, %%s %s", t('days'), t('hours'), t('minutes'), t('seconds')); + return $dtF->diff($dtT)->format($format); + } + + /** + * Get duration in hours into human format + * + * @access public + * @param float $hours + * @return string + */ + public function durationHours($hours) + { + return sprintf('%0.2f %s', round($hours, 2), t('hours')); + } + + /** + * Get the age of an item in quasi human readable format. + * It's in this format: <1h , NNh, NNd + * + * @access public + * @param integer $timestamp Unix timestamp of the artifact for which age will be calculated + * @param integer $now Compare with this timestamp (Default value is the current unix timestamp) + * @return string + */ + public function age($timestamp, $now = null) + { + if ($now === null) { + $now = time(); + } + + $diff = $now - $timestamp; + + if ($diff < 900) { + return t('<15m'); + } + if ($diff < 1200) { + return t('<30m'); + } elseif ($diff < 3600) { + return t('<1h'); + } elseif ($diff < 86400) { + return '~'.t('%dh', $diff / 3600); + } + + return t('%dd', ($now - $timestamp) / 86400); + } + + /** + * 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 date('l', strtotime('next Monday +'.($day - 1).' days')); + } +} diff --git a/app/Helper/FileHelper.php b/app/Helper/FileHelper.php new file mode 100644 index 0000000..d006271 --- /dev/null +++ b/app/Helper/FileHelper.php @@ -0,0 +1,152 @@ +<?php + +namespace Kanboard\Helper; + +use Kanboard\Core\Base; + +/** + * File helpers + * + * @package helper + * @author Frederic Guillot + */ +class FileHelper extends Base +{ + /** + * Get file icon + * + * @access public + * @param string $filename Filename + * @return string Font-Awesome-Icon-Name + */ + public function icon($filename) + { + switch (get_file_extension($filename)) { + case 'jpeg': + case 'jpg': + case 'png': + case 'gif': + case 'svg': + return 'fa-file-image-o'; + case 'xls': + case 'xlsx': + case 'xlsm': + 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': + case 'tar': + case 'bz2': + case 'xz': + case 'gz': + return 'fa-file-archive-o'; + case 'mp3': + case 'amr': + case 'flac': + case 'm4a': + case 'ogg': + case 'opus': + case 'wav': + case 'wma': + case 'midi': + case 'mid': + return 'fa-file-audio-o'; + case 'avi': + case 'mov': + case 'mp4': + case 'mkv': + case 'webm': + return 'fa-file-video-o'; + case 'php': + case 'html': + case 'css': + case 'js': + return 'fa-file-code-o'; + case 'pdf': + return 'fa-file-pdf-o'; + } + + return 'fa-file-o'; + } + + /** + * Return the image mimetype based on the file extension + * + * @access public + * @param $filename + * @return string + */ + public function getImageMimeType($filename) + { + switch (get_file_extension($filename)) { + case 'jpeg': + case 'jpg': + return 'image/jpeg'; + case 'png': + return 'image/png'; + case 'gif': + return 'image/gif'; + default: + return 'image/jpeg'; + } + } + + /** + * Get the preview type + * + * @access public + * @param string $filename + * @return string + */ + public function getPreviewType($filename) + { + switch (get_file_extension($filename)) { + case 'md': + case 'markdown': + return 'markdown'; + case 'txt': + return 'text'; + } + + return null; + } + + /** + * Return the browser view mime-type based on the file extension. + * + * @access public + * @param $filename + * @return string + */ + public function getBrowserViewType($filename) + { + switch (get_file_extension($filename)) { + case 'pdf': + return 'application/pdf'; + case 'mp3': + case 'ogg': + case 'flac': + case 'wav': + return 'audio/mpeg'; + case 'avi': + return 'video/x-msvideo'; + case 'webm': + return 'video/webm'; + case 'mov': + return 'video/quicktime'; + case 'm4v': + return 'video/x-m4v'; + case 'mp4': + return 'video/mp4'; + case 'svg': + return 'image/svg+xml'; + } + + return null; + } +} diff --git a/app/Helper/FormHelper.php b/app/Helper/FormHelper.php new file mode 100644 index 0000000..f4f5667 --- /dev/null +++ b/app/Helper/FormHelper.php @@ -0,0 +1,482 @@ +<?php + +namespace Kanboard\Helper; + +use Kanboard\Core\Base; + +/** + * Form helpers + * + * @package helper + * @author Frederic Guillot + */ +class FormHelper extends Base +{ + /** + * Hidden CSRF token field + * + * @access public + * @return string + */ + public function csrf() + { + return '<input type="hidden" name="csrf_token" value="'.$this->token->getCSRFToken().'"/>'; + } + + /** + * Display a hidden form field + * + * @access public + * @param string $name Field name + * @param array $values Form values + * @return string + */ + public function hidden($name, array $values = array()) + { + return '<input type="hidden" name="'.$name.'" id="form-'.$name.'" '.$this->formValue($values, $name).'/>'; + } + + /** + * Display a select field + * + * @access public + * @param string $name Field name + * @param array $options Options + * @param array $values Form values + * @param array $errors Form errors + * @param array $attributes + * @param string $class CSS class + * @return string + */ + public function select($name, array $options, array $values = array(), array $errors = array(), array $attributes = array(), $class = '') + { + $html = '<select name="'.$name.'" id="form-'.$name.'" class="'.$class.'" '.implode(' ', $attributes).'>'; + + foreach ($options as $id => $value) { + $html .= '<option value="'.$this->helper->text->e($id).'"'; + + if (isset($values->$name) && $id == $values->$name) { + $html .= ' selected="selected"'; + } + if (isset($values[$name]) && $id == $values[$name]) { + $html .= ' selected="selected"'; + } + + $html .= '>'.$this->helper->text->e($value).'</option>'; + } + + $html .= '</select>'; + $html .= $this->errorList($errors, $name); + + return $html; + } + + /** + * Display a color select field + * + * @access public + * @param string $name Field name + * @param array $values Form values + * @return string + */ + public function colorSelect($name, array $values) + { + $colors = $this->colorModel->getList(); + $html = $this->label(t('Color'), $name); + $html .= $this->select($name, $colors, $values, array(), array('tabindex="4"'), 'color-picker'); + return $html; + } + + /** + * Display a radio field group + * + * @access public + * @param string $name Field name + * @param array $options Options + * @param array $values Form values + * @return string + */ + public function radios($name, array $options, array $values = array()) + { + $html = ''; + + foreach ($options as $value => $label) { + $html .= $this->radio($name, $label, $value, isset($values[$name]) && $values[$name] == $value); + } + + return $html; + } + + /** + * Display a radio field + * + * @access public + * @param string $name Field name + * @param string $label Form label + * @param string $value Form value + * @param boolean $selected Field selected or not + * @param string $class CSS class + * @return string + */ + public function radio($name, $label, $value, $selected = false, $class = '') + { + return '<label><input type="radio" name="'.$name.'" class="'.$class.'" value="'.$this->helper->text->e($value).'" '.($selected ? 'checked="checked"' : '').'> '.$this->helper->text->e($label).'</label>'; + } + + /** + * Display a checkboxes group + * + * @access public + * @param string $name Field name + * @param array $options Options + * @param array $values Form values + * @return string + */ + public function checkboxes($name, array $options, array $values = array()) + { + $html = ''; + + foreach ($options as $value => $label) { + $html .= $this->checkbox($name.'['.$value.']', $label, $value, isset($values[$name]) && in_array($value, $values[$name])); + } + + return $html; + } + + /** + * Display a checkbox field + * + * @access public + * @param string $name Field name + * @param string $label Form label + * @param string $value Form value + * @param boolean $checked Field selected or not + * @param string $class CSS class + * @param array $attributes + * @return string + */ + public function checkbox($name, $label, $value, $checked = false, $class = '', array $attributes = array()) + { + $htmlAttributes = ''; + + if ($checked) { + $attributes['checked'] = 'checked'; + } + + foreach ($attributes as $attribute => $attributeValue) { + $htmlAttributes .= sprintf('%s="%s"', $attribute, $this->helper->text->e($attributeValue)); + } + + return sprintf( + '<label><input type="checkbox" name="%s" class="%s" value="%s" %s> %s</label>', + $name, + $class, + $this->helper->text->e($value), + $htmlAttributes, + $this->helper->text->e($label) + ); + } + + /** + * Display a form label + * + * @access public + * @param string $name Field name + * @param string $label Form label + * @param array $attributes HTML attributes + * @return string + */ + public function label($label, $name, array $attributes = array()) + { + return '<label for="form-'.$name.'" '.implode(' ', $attributes).'>'.$this->helper->text->e($label).'</label>'; + } + + /** + * Display a textarea + * + * @access public + * @param string $name Field name + * @param array $values Form values + * @param array $errors Form errors + * @param array $attributes HTML attributes + * @param string $class CSS class + * @return string + */ + public function textarea($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '') + { + $class .= $this->errorClass($errors, $name); + + $html = '<textarea name="'.$name.'" id="form-'.$name.'" class="'.$class.'" '; + $html .= implode(' ', $attributes).'>'; + $html .= isset($values[$name]) ? $this->helper->text->e($values[$name]) : ''; + $html .= '</textarea>'; + $html .= $this->errorList($errors, $name); + + return $html; + } + + /** + * Display a markdown editor + * + * @access public + * @param string $name Field name + * @param array $values Form values + * @param array $errors Form errors + * @param array $attributes + * @return string + */ + public function textEditor($name, $values = array(), array $errors = array(), array $attributes = array()) + { + $params = array( + 'name' => $name, + 'css' => $this->errorClass($errors, $name), + 'required' => isset($attributes['required']) && $attributes['required'], + 'tabindex' => isset($attributes['tabindex']) ? $attributes['tabindex'] : '-1', + 'labelPreview' => t('Preview'), + 'previewUrl' => $this->helper->url->to('TaskAjaxController', 'preview'), + 'labelWrite' => t('Write'), + 'labelTitle' => t('Title'), + 'placeholder' => t('Write your text in Markdown'), + 'ariaLabel' => isset($attributes['aria-label']) ? $attributes['aria-label'] : '', + 'autofocus' => isset($attributes['autofocus']) && $attributes['autofocus'], + 'suggestOptions' => array( + 'triggers' => array( + '#' => $this->helper->url->to('TaskAjaxController', 'suggest', array('search' => 'SEARCH_TERM')), + ) + ), + ); + + if (isset($values['project_id'])) { + $params['suggestOptions']['triggers']['@'] = $this->helper->url->to('UserAjaxController', 'mention', array('project_id' => $values['project_id'], 'search' => 'SEARCH_TERM')); + } + + $html = '<div class="js-text-editor" data-params=\''.json_encode($params, JSON_HEX_APOS).'\'>'; + $html .= '<script type="text/template">'.(isset($values[$name]) ? htmlspecialchars($values[$name], ENT_QUOTES, 'UTF-8', true) : '').'</script>'; + $html .= '</div>'; + $html .= $this->errorList($errors, $name); + + return $html; + } + + /** + * Display file field + * + * @access public + * @param string $name + * @param array $errors + * @param boolean $multiple + * @return string + */ + public function file($name, array $errors = array(), $multiple = false) + { + $html = '<input type="file" name="'.$name.'" id="form-'.$name.'" '.($multiple ? 'multiple' : '').'>'; + $html .= $this->errorList($errors, $name); + + return $html; + } + + /** + * Display a input field + * + * @access public + * @param string $type HMTL input tag type + * @param string $name Field name + * @param array $values Form values + * @param array $errors Form errors + * @param array $attributes HTML attributes + * @param string $class CSS class + * @return string + */ + public function input($type, $name, $values = array(), array $errors = array(), array $attributes = array(), $class = '') + { + $class .= $this->errorClass($errors, $name); + + $html = '<input type="'.$type.'" name="'.$name.'" id="form-'.$name.'" '.$this->formValue($values, $name).' class="'.$class.'" '; + $html .= implode(' ', $attributes).'>'; + + if (in_array('required', $attributes)) { + $html .= '<span class="form-required">*</span>'; + } + + $html .= $this->errorList($errors, $name); + + return $html; + } + + /** + * Display a text field + * + * @access public + * @param string $name Field name + * @param array $values Form values + * @param array $errors Form errors + * @param array $attributes HTML attributes + * @param string $class CSS class + * @return string + */ + public function text($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '') + { + return $this->input('text', $name, $values, $errors, $attributes, $class); + } + + /** + * Display a password field + * + * @access public + * @param string $name Field name + * @param array $values Form values + * @param array $errors Form errors + * @param array $attributes HTML attributes + * @param string $class CSS class + * @return string + */ + public function password($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '') + { + return $this->input('password', $name, $values, $errors, $attributes, $class); + } + + /** + * Display an email field + * + * @access public + * @param string $name Field name + * @param array $values Form values + * @param array $errors Form errors + * @param array $attributes HTML attributes + * @param string $class CSS class + * @return string + */ + public function email($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '') + { + return $this->input('email', $name, $values, $errors, $attributes, $class); + } + + /** + * Display a number field + * + * @access public + * @param string $name Field name + * @param array $values Form values + * @param array $errors Form errors + * @param array $attributes HTML attributes + * @param string $class CSS class + * @return string + */ + public function number($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '') + { + return $this->input('number', $name, $values, $errors, $attributes, $class); + } + + /** + * Display a numeric field (allow decimal number) + * + * @access public + * @param string $name Field name + * @param array $values Form values + * @param array $errors Form errors + * @param array $attributes HTML attributes + * @param string $class CSS class + * @return string + */ + public function numeric($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '') + { + return $this->input('text', $name, $values, $errors, $attributes, $class.' form-numeric'); + } + + /** + * Date field + * + * @access public + * @param string $label + * @param string $name + * @param array $values + * @param array $errors + * @param array $attributes + * @return string + */ + public function date($label, $name, array $values, array $errors = array(), array $attributes = array()) + { + $userFormat = $this->dateParser->getUserDateFormat(); + $values = $this->dateParser->format($values, array($name), $userFormat); + $attributes = array_merge(array('placeholder="'.date($userFormat).'"'), $attributes); + + return $this->helper->form->label($label, $name) . + $this->helper->form->text($name, $values, $errors, $attributes, 'form-date'); + } + + /** + * Datetime field + * + * @access public + * @param string $label + * @param string $name + * @param array $values + * @param array $errors + * @param array $attributes + * @return string + */ + public function datetime($label, $name, array $values, array $errors = array(), array $attributes = array()) + { + $userFormat = $this->dateParser->getUserDateTimeFormat(); + $values = $this->dateParser->format($values, array($name), $userFormat); + $attributes = array_merge(array('placeholder="'.date($userFormat).'"'), $attributes); + + return $this->helper->form->label($label, $name) . + $this->helper->form->text($name, $values, $errors, $attributes, 'form-datetime'); + } + + /** + * Display the form error class + * + * @access private + * @param array $errors Error list + * @param string $name Field name + * @return string + */ + private function errorClass(array $errors, $name) + { + return ! isset($errors[$name]) ? '' : ' form-error'; + } + + /** + * Display a list of form errors + * + * @access private + * @param array $errors List of errors + * @param string $name Field name + * @return string + */ + private function errorList(array $errors, $name) + { + $html = ''; + + if (isset($errors[$name])) { + $html .= '<ul class="form-errors">'; + + foreach ($errors[$name] as $error) { + $html .= '<li>'.$this->helper->text->e($error).'</li>'; + } + + $html .= '</ul>'; + } + + return $html; + } + + /** + * Get an escaped form value + * + * @access private + * @param mixed $values Values + * @param string $name Field name + * @return string + */ + private function formValue($values, $name) + { + if (isset($values->$name)) { + return 'value="'.$this->helper->text->e($values->$name).'"'; + } + + return isset($values[$name]) ? 'value="'.$this->helper->text->e($values[$name]).'"' : ''; + } +} diff --git a/app/Helper/HookHelper.php b/app/Helper/HookHelper.php new file mode 100644 index 0000000..9a89daf --- /dev/null +++ b/app/Helper/HookHelper.php @@ -0,0 +1,104 @@ +<?php + +namespace Kanboard\Helper; + +use Closure; +use Kanboard\Core\Base; + +/** + * Template Hook helpers + * + * @package helper + * @author Frederic Guillot + */ +class HookHelper extends Base +{ + /** + * Add assets JS or CSS + * + * @access public + * @param string $type + * @param string $hook + * @return string + */ + public function asset($type, $hook) + { + $buffer = ''; + + foreach ($this->hook->getListeners($hook) as $params) { + $buffer .= $this->helper->asset->$type($params['template']); + } + + return $buffer; + } + + /** + * Render all attached hooks + * + * @access public + * @param string $hook + * @param array $variables + * @return string + */ + public function render($hook, array $variables = array()) + { + $buffer = ''; + + foreach ($this->hook->getListeners($hook) as $params) { + $currentVariables = $variables; + if (! empty($params['variables'])) { + $currentVariables = array_merge($variables, $params['variables']); + } elseif (! empty($params['callable'])) { + $result = call_user_func_array($params['callable'], array_values($variables)); + + if (is_array($result)) { + $currentVariables = array_merge($variables, $result); + } + } + + $buffer .= $this->template->render($params['template'], $currentVariables); + } + + return $buffer; + } + + /** + * Attach a template to a hook + * + * @access public + * @param string $hook + * @param string $template + * @param array $variables + * @return $this + */ + public function attach($hook, $template, array $variables = array()) + { + $this->hook->on($hook, array( + 'template' => $template, + 'variables' => $variables, + )); + + return $this; + } + + /** + * Attach a template to a hook with a callable + * + * Arguments passed to the callback are the one passed to the hook + * + * @access public + * @param string $hook + * @param string $template + * @param Closure $callable + * @return $this + */ + public function attachCallable($hook, $template, Closure $callable) + { + $this->hook->on($hook, array( + 'template' => $template, + 'callable' => $callable, + )); + + return $this; + } +} diff --git a/app/Helper/LayoutHelper.php b/app/Helper/LayoutHelper.php new file mode 100644 index 0000000..6afdb2d --- /dev/null +++ b/app/Helper/LayoutHelper.php @@ -0,0 +1,218 @@ +<?php + +namespace Kanboard\Helper; + +use Kanboard\Core\Base; + +/** + * Layout Helper + * + * @package helper + * @author Frederic Guillot + */ +class LayoutHelper extends Base +{ + /** + * Render a template without the layout if Ajax request + * + * @access public + * @param string $template Template name + * @param array $params Template parameters + * @return string + */ + public function app($template, array $params = array()) + { + $isAjax = $this->request->isAjax(); + $params['is_ajax'] = $isAjax; + + if ($isAjax) { + return $this->template->render($template, $params); + } + + if (! isset($params['no_layout']) && ! isset($params['board_selector'])) { + $params['board_selector'] = $this->projectUserRoleModel->getActiveProjectsByUser($this->userSession->getId()); + + if (isset($params['project']['id'])) { + unset($params['board_selector'][$params['project']['id']]); + } + + $this->hook->reference('helper:layout:board-selector:list', $params['board_selector']); + } + + return $this->pageLayout($template, $params); + } + + /** + * Common layout for user views + * + * @access public + * @param string $template Template name + * @param array $params Template parameters + * @return string + */ + public function user($template, array $params) + { + if (isset($params['user'])) { + $params['title'] = '#'.$params['user']['id'].' '.($params['user']['name'] ?: $params['user']['username']); + } + + return $this->subLayout('user_view/layout', 'user_view/sidebar', $template, $params); + } + + /** + * Common layout for task views + * + * @access public + * @param string $template Template name + * @param array $params Template parameters + * @return string + */ + public function task($template, array $params) + { + $params['page_title'] = $params['task']['project_name'].', #'.$params['task']['id'].' - '.$params['task']['title']; + $params['title'] = $params['task']['project_name']; + return $this->subLayout('task/layout', 'task/sidebar', $template, $params); + } + + /** + * Common layout for project views + * + * @access public + * @param string $template + * @param array $params + * @param string $sidebar + * @return string + */ + public function project($template, array $params, $sidebar = 'project/sidebar') + { + if (empty($params['title'])) { + $params['title'] = $params['project']['name']; + } elseif ($params['project']['name'] !== $params['title']) { + $params['title'] = $params['project']['name'].' > '.$params['title']; + } + + return $this->subLayout('project/layout', $sidebar, $template, $params); + } + + /** + * Common layout for project user views + * + * @access public + * @param string $template + * @param array $params + * @return string + */ + public function projectUser($template, array $params) + { + $params['filter'] = array('user_id' => $params['user_id']); + return $this->subLayout('project_user_overview/layout', 'project_user_overview/sidebar', $template, $params); + } + + /** + * Common layout for config views + * + * @access public + * @param string $template + * @param array $params + * @return string + */ + public function config($template, array $params) + { + if (empty($params['values'])) { + $params['values'] = $this->configModel->getAll(); + } + + if (! isset($params['errors'])) { + $params['errors'] = array(); + } + + return $this->subLayout('config/layout', 'config/sidebar', $template, $params); + } + + /** + * Common layout for plugin views + * + * @access public + * @param string $template + * @param array $params + * @return string + */ + public function plugin($template, array $params) + { + return $this->subLayout('plugin/layout', 'plugin/sidebar', $template, $params); + } + + /** + * Common layout for dashboard views + * + * @access public + * @param string $template + * @param array $params + * @return string + */ + public function dashboard($template, array $params) + { + return $this->subLayout('dashboard/layout', 'dashboard/sidebar', $template, $params); + } + + /** + * Common layout for analytic views + * + * @access public + * @param string $template + * @param array $params + * @return string + */ + public function analytic($template, array $params) + { + if (isset($params['project']['name'])) { + $params['title'] = $params['project']['name'].' > '.$params['title']; + } + + return $this->subLayout('analytic/layout', 'analytic/sidebar', $template, $params, true); + } + + /** + * Render page layout + * + * @access public + * @param string $template Template name + * @param array $params Key/value dictionary + * @param string $layout Layout name + * @return string + */ + public function pageLayout($template, array $params = array(), $layout = 'layout') + { + return $this->template->render( + $layout, + $params + array('content_for_layout' => $this->template->render($template, $params)) + ); + } + + /** + * Common method to generate a sub-layout + * + * @access public + * @param string $sublayout + * @param string $sidebar + * @param string $template + * @param array $params + * @param bool $ignoreAjax + * @return string + */ + public function subLayout($sublayout, $sidebar, $template, array $params = array(), $ignoreAjax = false) + { + $isAjax = $this->request->isAjax(); + $params['is_ajax'] = $isAjax; + $content = $this->template->render($template, $params); + + if (!$ignoreAjax && $isAjax) { + return $content; + } + + $params['content_for_sublayout'] = $content; + $params['sidebar_template'] = $sidebar; + + return $this->app($sublayout, $params); + } +} diff --git a/app/Helper/MailHelper.php b/app/Helper/MailHelper.php new file mode 100644 index 0000000..e0d28df --- /dev/null +++ b/app/Helper/MailHelper.php @@ -0,0 +1,68 @@ +<?php + +namespace Kanboard\Helper; + +use Kanboard\Core\Base; + +/** + * Class MailHelper + * + * @package Kanboard\Helper + * @author Frederic Guillot + */ +class MailHelper extends Base +{ + /** + * Filter mail subject + * + * @access public + * @param string $subject + * @return string + */ + public function filterSubject($subject) + { + $subject = str_ireplace('RE: ', '', $subject); + $subject = str_ireplace('FW: ', '', $subject); + $subject = str_ireplace('Fwd: ', '', $subject); + + return $subject; + } + + /** + * Get mail sender address + * + * @access public + * @return string + */ + public function getMailSenderAddress() + { + if (MAIL_CONFIGURATION) { + $email = $this->configModel->get('mail_sender_address'); + + if (! empty($email)) { + return $email; + } + } + + return MAIL_FROM; + } + + /** + * Get mail transport + * + * @access public + * @return string + */ + public function getMailTransport() + { + if (MAIL_CONFIGURATION) { + $transport = $this->configModel->get('mail_transport'); + + if (! empty($transport)) { + return $transport; + } + } + + return MAIL_TRANSPORT; + } +} diff --git a/app/Helper/ModalHelper.php b/app/Helper/ModalHelper.php new file mode 100644 index 0000000..b528f4a --- /dev/null +++ b/app/Helper/ModalHelper.php @@ -0,0 +1,96 @@ +<?php + +namespace Kanboard\Helper; + +use Kanboard\Core\Base; + +/** + * Class ModalHelper + * + * @package Kanboard\Helper + * @author Frederic Guillot + */ +class ModalHelper extends Base +{ + public function submitButtons(array $params = array()) + { + return $this->helper->app->component('submit-buttons', array( + 'submitLabel' => isset($params['submitLabel']) ? $params['submitLabel'] : t('Save'), + 'orLabel' => t('or'), + 'cancelLabel' => t('cancel'), + 'color' => isset($params['color']) ? $params['color'] : 'blue', + 'tabindex' => isset($params['tabindex']) ? $params['tabindex'] : null, + 'disabled' => isset($params['disabled']) ? true : false, + )); + } + + public function confirmButtons($controller, $action, array $params = array(), $submitLabel = '', $tabindex = null) + { + return $this->helper->app->component('confirm-buttons', array( + 'url' => $this->helper->url->href($controller, $action, $params, true), + 'submitLabel' => $submitLabel ?: t('Yes'), + 'orLabel' => t('or'), + 'cancelLabel' => t('cancel'), + 'tabindex' => $tabindex, + )); + } + + public function largeIcon($icon, $label, $controller, $action, array $params = array()) + { + $html = '<i class="fa fa-'.$icon.' fa-fw js-modal-large" aria-hidden="true"></i>'; + return $this->helper->url->link($html, $controller, $action, $params, false, 'js-modal-large', $label); + } + + public function large($icon, $label, $controller, $action, array $params = array()) + { + $html = '<i class="fa fa-'.$icon.' fa-fw js-modal-large" aria-hidden="true"></i>'.$label; + return $this->helper->url->link($html, $controller, $action, $params, false, 'js-modal-large'); + } + + public function medium($icon, $label, $controller, $action, array $params = array(), $title = '') + { + $ariaLabel = (empty($title) ? 'aria-hidden="true"' : 'role="img" aria-label="'.$title.'"'); + $html = '<i class="fa fa-'.$icon.' fa-fw js-modal-medium" '.$ariaLabel.'></i>'.$label; + return $this->helper->url->link($html, $controller, $action, $params, false, 'js-modal-medium', $title); + } + + public function small($icon, $label, $controller, $action, array $params = array()) + { + $html = '<i class="fa fa-'.$icon.' fa-fw js-modal-small" aria-hidden="true"></i>'.$label; + return $this->helper->url->link($html, $controller, $action, $params, false, 'js-modal-small'); + } + + public function mediumButton($icon, $label, $controller, $action, array $params = array()) + { + $html = '<i class="fa fa-'.$icon.' fa-fw js-modal-medium" aria-hidden="true"></i>'.$label; + return $this->helper->url->link($html, $controller, $action, $params, false, 'js-modal-medium btn'); + } + + public function mediumIcon($icon, $label, $controller, $action, array $params = array()) + { + $html = '<i class="fa fa-'.$icon.' fa-fw js-modal-medium" aria-hidden="true"></i>'; + return $this->helper->url->link($html, $controller, $action, $params, false, 'js-modal-medium', $label); + } + + public function confirm($icon, $label, $controller, $action, array $params = array()) + { + $html = '<i class="fa fa-'.$icon.' fa-fw js-modal-confirm" aria-hidden="true"></i>'.$label; + return $this->helper->url->link($html, $controller, $action, $params, false, 'js-modal-confirm'); + } + + public function confirmLink($label, $controller, $action, array $params = array()) + { + return $this->helper->url->link($label, $controller, $action, $params, false, 'js-modal-confirm'); + } + + public function replaceLink($label, $controller, $action, array $params = array()) + { + return $this->helper->url->link($label, $controller, $action, $params, false, 'js-modal-replace'); + } + + public function replaceIconLink($icon, $label, $controller, $action, array $params = array()) + { + $html = '<i class="fa fa-'.$icon.' fa-fw js-modal-replace" aria-hidden="true"></i>'.$label; + return $this->helper->url->link($html, $controller, $action, $params, false, 'js-modal-replace'); + } +} diff --git a/app/Helper/ModelHelper.php b/app/Helper/ModelHelper.php new file mode 100644 index 0000000..d49637c --- /dev/null +++ b/app/Helper/ModelHelper.php @@ -0,0 +1,94 @@ +<?php + +namespace Kanboard\Helper; + +use Kanboard\Core\Base; + +/** + * Model Helper + * + * @package helper + * @author Frederic Guillot + */ +class ModelHelper extends Base +{ + /** + * Remove keys from an array + * + * @access public + * @param array $values Input array + * @param string[] $keys List of keys to remove + */ + public function removeFields(array &$values, array $keys) + { + foreach ($keys as $key) { + if (array_key_exists($key, $values)) { + unset($values[$key]); + } + } + } + + /** + * Remove keys from an array if empty + * + * @access public + * @param array $values Input array + * @param string[] $keys List of keys to remove + */ + public function removeEmptyFields(array &$values, array $keys) + { + foreach ($keys as $key) { + if (array_key_exists($key, $values) && empty($values[$key])) { + unset($values[$key]); + } + } + } + + /** + * Force fields to be at 0 if empty + * + * @access public + * @param array $values Input array + * @param string[] $keys List of keys + */ + public function resetFields(array &$values, array $keys) + { + foreach ($keys as $key) { + if (isset($values[$key]) && empty($values[$key])) { + $values[$key] = 0; + } + } + } + + /** + * Force some fields to be integer + * + * @access public + * @param array $values Input array + * @param string[] $keys List of keys + */ + public function convertIntegerFields(array &$values, array $keys) + { + foreach ($keys as $key) { + if (isset($values[$key])) { + $values[$key] = (int) $values[$key]; + } + } + } + + /** + * Force some fields to be null if empty + * + * @access public + * @param array $values Input array + * @param string[] $keys List of keys + */ + public function convertNullFields(array &$values, array $keys) + { + foreach ($keys as $key) { + if (array_key_exists($key, $values) && empty($values[$key])) { + $values[$key] = null; + } + } + } +} diff --git a/app/Helper/ProjectActivityHelper.php b/app/Helper/ProjectActivityHelper.php new file mode 100644 index 0000000..480db3d --- /dev/null +++ b/app/Helper/ProjectActivityHelper.php @@ -0,0 +1,104 @@ +<?php + +namespace Kanboard\Helper; + +use Kanboard\Core\Base; +use Kanboard\Filter\ProjectActivityProjectIdFilter; +use Kanboard\Filter\ProjectActivityProjectIdsFilter; +use Kanboard\Filter\ProjectActivityTaskIdFilter; +use Kanboard\Model\ProjectActivityModel; + +/** + * Project Activity Helper + * + * @package helper + * @author Frederic Guillot + */ +class ProjectActivityHelper extends Base +{ + /** + * Search events + * + * @access public + * @param string $search + * @return array + */ + public function searchEvents($search) + { + $projects = $this->projectUserRoleModel->getActiveProjectsByUser($this->userSession->getId()); + $events = array(); + + if ($search !== '') { + $queryBuilder = $this->projectActivityLexer->build($search); + $queryBuilder + ->withFilter(new ProjectActivityProjectIdsFilter(array_keys($projects))) + ->getQuery() + ->desc(ProjectActivityModel::TABLE.'.id') + ->limit(500) + ; + + $events = $queryBuilder->format($this->projectActivityEventFormatter); + } + + return $events; + } + + /** + * Get project activity events + * + * @access public + * @param integer $project_id + * @param int $limit + * @return array + */ + public function getProjectEvents($project_id, $limit = 50) + { + $queryBuilder = $this->projectActivityQuery + ->withFilter(new ProjectActivityProjectIdFilter($project_id)); + + $queryBuilder->getQuery() + ->desc(ProjectActivityModel::TABLE.'.id') + ->limit($limit) + ; + + return $queryBuilder->format($this->projectActivityEventFormatter); + } + + /** + * Get projects activity events + * + * @access public + * @param int[] $project_ids + * @param int $limit + * @return array + */ + public function getProjectsEvents(array $project_ids, $limit = 50) + { + $queryBuilder = $this->projectActivityQuery + ->withFilter(new ProjectActivityProjectIdsFilter($project_ids)); + + $queryBuilder->getQuery() + ->desc(ProjectActivityModel::TABLE.'.id') + ->limit($limit) + ; + + return $queryBuilder->format($this->projectActivityEventFormatter); + } + + /** + * Get task activity events + * + * @access public + * @param integer $task_id + * @return array + */ + public function getTaskEvents($task_id) + { + $queryBuilder = $this->projectActivityQuery + ->withFilter(new ProjectActivityTaskIdFilter($task_id)); + + $queryBuilder->getQuery()->desc(ProjectActivityModel::TABLE.'.id'); + + return $queryBuilder->format($this->projectActivityEventFormatter); + } +} diff --git a/app/Helper/ProjectHeaderHelper.php b/app/Helper/ProjectHeaderHelper.php new file mode 100644 index 0000000..d95cfc2 --- /dev/null +++ b/app/Helper/ProjectHeaderHelper.php @@ -0,0 +1,87 @@ +<?php + +namespace Kanboard\Helper; + +use Kanboard\Core\Base; + +/** + * Project Header Helper + * + * @package helper + * @author Frederic Guillot + */ +class ProjectHeaderHelper extends Base +{ + public $hookBefore = ''; + public $dropdownHtml = ''; + public $viewsHtml = ''; + public $searchHtml = ''; + public $hookAfter = ''; + /** + * Get current search query + * + * @access public + * @param array $project + * @return string + */ + public function getSearchQuery(array $project) + { + $search = $this->request->getStringParam('search', $this->userSession->getFilters($project['id'])); + $this->userSession->setFilters($project['id'], $search); + return rawurldecode($search); + } + + /** + * Render project header (views switcher and search box) + * + * @access public + * @param array $project + * @param string $controller + * @param string $action + * @param bool $boardView + * @param string $plugin + * @return string + */ + public function render(array $project, $controller, $action, $boardView = false, $plugin = '') + { + $filters = array( + 'controller' => $controller, + 'action' => $action, + 'project_id' => $project['id'], + 'search' => $this->getSearchQuery($project), + 'plugin' => $plugin, + ); + + return $this->template->render('project_header/header', array( + 'project' => $project, + 'filters' => $filters, + 'categories_list' => $this->categoryModel->getList($project['id'], false), + 'users_list' => $this->projectUserRoleModel->getAssignableUsersList($project['id'], false), + 'custom_filters_list' => $this->customFilterModel->getAll($project['id'], $this->userSession->getId()), + 'board_view' => $boardView, + )); + } + + /** + * Get project description + * + * @access public + * @param array &$project + * @return string + */ + public function getDescription(array &$project) + { + if ($project['owner_id'] > 0) { + $description = t('Project owner: ').'<strong>'.$this->helper->text->e($project['owner_name'] ?: $project['owner_username']).'</strong>'.PHP_EOL.PHP_EOL; + + if (! empty($project['description'])) { + $description .= '<hr>'.PHP_EOL.PHP_EOL; + $description .= $this->helper->text->markdown($project['description'] ?: ''); + } + } else { + $description = $this->helper->text->markdown($project['description'] ?: ''); + } + + return $description; + } +} diff --git a/app/Helper/ProjectRoleHelper.php b/app/Helper/ProjectRoleHelper.php new file mode 100644 index 0000000..6965083 --- /dev/null +++ b/app/Helper/ProjectRoleHelper.php @@ -0,0 +1,338 @@ +<?php + +namespace Kanboard\Helper; + +use Kanboard\Core\Base; +use Kanboard\Core\Security\Role; +use Kanboard\Model\ColumnRestrictionModel; +use Kanboard\Model\ProjectRoleRestrictionModel; + +/** + * Class ProjectRoleHelper + * + * @package Kanboard\Helper + * @author Frederic Guillot + */ +class ProjectRoleHelper extends Base +{ + /** + * Get project role for the current user + * + * @access public + * @param integer $projectId + * @return string + */ + public function getProjectUserRole($projectId) + { + return $this->memoryCache->proxy($this->projectUserRoleModel, 'getUserRole', $projectId, $this->userSession->getId()); + } + + /** + * Return true if the task can be moved by the logged user + * + * @param array $task + * @return bool + */ + public function isDraggable(array &$task) + { + if ($task['is_active'] == 1 && $this->helper->user->hasProjectAccess('BoardAjaxController', 'save', $task['project_id'])) { + return $this->isSortableColumn($task['project_id'], $task['column_id'], $task['owner_id']); + } + + return false; + } + + /** + * Return true is the column is sortable + * + * @param int $projectId + * @param int $columnId + * @param int $assigneeId + * @return bool + */ + public function isSortableColumn($projectId, $columnId, $assigneeId = null) + { + $role = $this->getProjectUserRole($projectId); + + if ($this->role->isCustomProjectRole($role)) { + $sortableColumns = $this->columnMoveRestrictionCacheDecorator->getSortableColumns($projectId, $role); + + foreach ($sortableColumns as $column) { + if ($column['src_column_id'] == $columnId || $column['dst_column_id'] == $columnId) { + if ($column['only_assigned'] == 1 && $assigneeId !== null && $assigneeId != $this->userSession->getId()) { + return false; + } + + return true; + } + } + + return empty($sortableColumns) && $this->isAllowedToMoveTask($projectId, $role); + } + + return true; + } + + /** + * Check if the user can move a task + * + * @param int $projectId + * @param int $srcColumnId + * @param int $dstColumnId + * @return bool|int + */ + public function canMoveTask($projectId, $srcColumnId, $dstColumnId) + { + $role = $this->getProjectUserRole($projectId); + + if ($this->role->isCustomProjectRole($role)) { + if ($srcColumnId == $dstColumnId) { + return true; + } + + $sortableColumns = $this->columnMoveRestrictionCacheDecorator->getSortableColumns($projectId, $role); + + foreach ($sortableColumns as $column) { + if ($column['src_column_id'] == $srcColumnId && $column['dst_column_id'] == $dstColumnId) { + return true; + } + + if ($column['dst_column_id'] == $srcColumnId && $column['src_column_id'] == $dstColumnId) { + return true; + } + } + + return empty($sortableColumns) && $this->isAllowedToMoveTask($projectId, $role); + } + + return true; + } + + /** + * Return true if the user can create a task for the given column + * + * @param int $projectId + * @param int $columnId + * @return bool + */ + public function canCreateTaskInColumn($projectId, $columnId) + { + $role = $this->getProjectUserRole($projectId); + + if ($this->role->isCustomProjectRole($role)) { + if (! $this->isAllowedToCreateTask($projectId, $columnId, $role)) { + return false; + } + } + + return $this->helper->user->hasProjectAccess('TaskCreationController', 'show', $projectId); + } + + /** + * Return true if the user can create a task for the given column + * + * @param int $projectId + * @param int $columnId + * @return bool + */ + public function canChangeTaskStatusInColumn($projectId, $columnId) + { + $role = $this->getProjectUserRole($projectId); + + if ($this->role->isCustomProjectRole($role)) { + if (! $this->isAllowedToChangeTaskStatus($projectId, $columnId, $role)) { + return false; + } + } + + return $this->helper->user->hasProjectAccess('TaskStatusController', 'close', $projectId); + } + + /** + * Return true if the user can remove a task + * + * Regular users can't remove tasks from other people + * + * @public + * @param array $task + * @return bool + */ + public function canRemoveTask(array $task) + { + $role = $this->getProjectUserRole($task['project_id']); + + if ($this->hasRestriction($task['project_id'], $role, ProjectRoleRestrictionModel::RULE_TASK_SUPPRESSION)) { + return false; + } + + if (isset($task['creator_id']) && $task['creator_id'] == $this->userSession->getId()) { + return true; + } + + if ($this->userSession->isAdmin() || $this->getProjectUserRole($task['project_id']) === Role::PROJECT_MANAGER) { + return true; + } + + return false; + } + + /** + * Return true if the user can change assignee + * + * @public + * @param array $task + * @return bool + */ + public function canChangeAssignee(array $task) + { + $role = $this->getProjectUserRole($task['project_id']); + + if ($this->role->isCustomProjectRole($role) && $this->hasRestriction($task['project_id'], $role, ProjectRoleRestrictionModel::RULE_TASK_CHANGE_ASSIGNEE)) { + return false; + } + + return true; + } + + /** + * Return true if the user can update a task + * + * @public + * @param array $task + * @return bool + */ + public function canUpdateTask(array $task) + { + $role = $this->getProjectUserRole($task['project_id']); + + if ($this->role->isCustomProjectRole($role) && $task['owner_id'] != $this->userSession->getId() && $this->hasRestriction($task['project_id'], $role, ProjectRoleRestrictionModel::RULE_TASK_UPDATE_ASSIGNED)) { + return false; + } + + return true; + } + + /** + * Check project access + * + * @param string $controller + * @param string $action + * @param integer $projectId + * @return bool + */ + public function checkProjectAccess($controller, $action, $projectId) + { + if (! $this->userSession->isLogged()) { + return false; + } + + if ($this->userSession->isAdmin()) { + return true; + } + + if (! $this->helper->user->hasAccess($controller, $action)) { + return false; + } + + $role = $this->getProjectUserRole($projectId); + + if ($this->role->isCustomProjectRole($role)) { + $result = $this->projectAuthorization->isAllowed($controller, $action, Role::PROJECT_MEMBER); + } else { + $result = $this->projectAuthorization->isAllowed($controller, $action, $role); + } + + return $result; + } + + /** + * Check authorization for a custom project role to change the task status + * + * @param int $projectId + * @param int $columnId + * @param string $role + * @return bool + */ + protected function isAllowedToChangeTaskStatus($projectId, $columnId, $role) + { + $columnRestrictions = $this->columnRestrictionCacheDecorator->getAllByRole($projectId, $role); + + foreach ($columnRestrictions as $restriction) { + if ($restriction['column_id'] == $columnId) { + if ($restriction['rule'] == ColumnRestrictionModel::RULE_ALLOW_TASK_OPEN_CLOSE) { + return true; + } elseif ($restriction['rule'] == ColumnRestrictionModel::RULE_BLOCK_TASK_OPEN_CLOSE) { + return false; + } + } + } + + return ! $this->hasRestriction($projectId, $role, ProjectRoleRestrictionModel::RULE_TASK_OPEN_CLOSE); + } + + /** + * Check authorization for a custom project role to create a task + * + * @param int $projectId + * @param int $columnId + * @param string $role + * @return bool + */ + protected function isAllowedToCreateTask($projectId, $columnId, $role) + { + $columnRestrictions = $this->columnRestrictionCacheDecorator->getAllByRole($projectId, $role); + + foreach ($columnRestrictions as $restriction) { + if ($restriction['column_id'] == $columnId) { + if ($restriction['rule'] == ColumnRestrictionModel::RULE_ALLOW_TASK_CREATION) { + return true; + } elseif ($restriction['rule'] == ColumnRestrictionModel::RULE_BLOCK_TASK_CREATION) { + return false; + } + } + } + + return ! $this->hasRestriction($projectId, $role, ProjectRoleRestrictionModel::RULE_TASK_CREATION); + } + + /** + * Check if the role can move task in the given project + * + * @param int $projectId + * @param string $role + * @return bool + */ + protected function isAllowedToMoveTask($projectId, $role) + { + $projectRestrictions = $this->projectRoleRestrictionCacheDecorator->getAllByRole($projectId, $role); + + foreach ($projectRestrictions as $restriction) { + if ($restriction['rule'] == ProjectRoleRestrictionModel::RULE_TASK_MOVE) { + return false; + } + } + + return true; + } + + /** + * Check if given role has a restriction + * + * @param integer $projectId + * @param string $role + * @param string $rule + * @return bool + */ + protected function hasRestriction($projectId, $role, $rule) + { + $projectRestrictions = $this->projectRoleRestrictionCacheDecorator->getAllByRole($projectId, $role); + + foreach ($projectRestrictions as $restriction) { + if ($restriction['rule'] == $rule) { + return true; + } + } + + return false; + } +} diff --git a/app/Helper/SubtaskHelper.php b/app/Helper/SubtaskHelper.php new file mode 100644 index 0000000..318ab1e --- /dev/null +++ b/app/Helper/SubtaskHelper.php @@ -0,0 +1,174 @@ +<?php + +namespace Kanboard\Helper; + +use Kanboard\Core\Base; +use Kanboard\Model\SubtaskModel; + +/** + * Subtask helpers + * + * @package helper + * @author Frederic Guillot + */ +class SubtaskHelper extends Base +{ + /** + * Return if the current user has a subtask in progress + * + * @return bool + */ + public function hasSubtaskInProgress() + { + return session_is_true('hasSubtaskInProgress'); + } + + /** + * Render subtask title + * + * @param array $subtask + * @return string + */ + public function renderTitle(array $subtask) + { + if ($subtask['status'] == 0) { + $html = '<i class="fa fa-square-o fa-fw ' . ($this->hasSubtaskInProgress() ? 'js-modal-confirm' : '') . '"></i>'; + } elseif ($subtask['status'] == 1) { + $html = '<i class="fa fa-gears fa-fw"></i>'; + } else { + $html = '<i class="fa fa-check-square-o fa-fw"></i>'; + } + + return $html.$this->helper->text->e($subtask['title']); + } + + /** + * Get the link to toggle subtask status + * + * @access public + * @param array $task + * @param array $subtask + * @param string $fragment + * @param int $userId + * @return string + */ + public function renderToggleStatus(array $task, array $subtask, $fragment = '', $userId = 0) + { + if (! $this->helper->user->hasProjectAccess('SubtaskController', 'edit', $task['project_id'])) { + $html = $this->renderTitle($subtask); + } else { + $title = $this->renderTitle($subtask); + $params = array( + 'project_id' => $task['project_id'], + 'task_id' => $subtask['task_id'], + 'subtask_id' => $subtask['id'], + 'user_id' => $userId, + 'fragment' => $fragment, + 'csrf_token' => $this->token->getReusableCSRFToken(), + ); + + if ($subtask['status'] == 0 && $this->hasSubtaskInProgress()) { + $html = $this->helper->url->link($title, 'SubtaskRestrictionController', 'show', $params, false, 'js-modal-confirm', $this->getSubtaskTooltip($subtask)); + } else { + $html = $this->helper->url->link($title, 'SubtaskStatusController', 'change', $params, false, 'js-subtask-toggle-status', $this->getSubtaskTooltip($subtask)); + } + } + + return '<span class="subtask-title">'.$html.'</span>'; + } + + public function renderTimer(array $task, array $subtask) + { + $html = '<span class="subtask-timer-toggle">'; + $params = array( + 'task_id' => $subtask['task_id'], + 'subtask_id' => $subtask['id'], + 'timer' => '', + 'csrf_token' => $this->token->getReusableCSRFToken(), + ); + + if ($subtask['is_timer_started']) { + $params['timer'] = 'stop'; + $html .= $this->helper->url->icon('pause', t('Stop timer'), 'SubtaskStatusController', 'timer', $params, false, 'js-subtask-toggle-timer'); + $html .= ' (' . $this->helper->dt->age($subtask['timer_start_date']) .')'; + } else { + $params['timer'] = 'start'; + $html .= $this->helper->url->icon('play-circle-o', t('Start timer'), 'SubtaskStatusController', 'timer', $params, false, 'js-subtask-toggle-timer'); + } + + $html .= '</span>'; + + return $html; + } + + public function renderBulkTitleField(array $values, array $errors = array(), array $attributes = array()) + { + $attributes = array_merge(array('tabindex="1"', 'required'), $attributes); + + $html = $this->helper->form->label(t('Title'), 'title'); + $html .= $this->helper->form->textarea('title', $values, $errors, $attributes); + $html .= '<p class="form-help">'.t('Enter one subtask by line.').'</p>'; + + return $html; + } + + public function renderTitleField(array $values, array $errors = array(), array $attributes = array()) + { + $attributes = array_merge(array('tabindex="1"', 'required'), $attributes); + + $html = $this->helper->form->label(t('Title'), 'title'); + $html .= $this->helper->form->text('title', $values, $errors, $attributes, 'form-max-width'); + + return $html; + } + + public function renderAssigneeField(array $users, array $values, array $errors = array(), array $attributes = array()) + { + $attributes = array_merge(array('tabindex="2"'), $attributes); + + $html = $this->helper->form->label(t('Assignee'), 'user_id'); + $html .= $this->helper->form->select('user_id', $users, $values, $errors, $attributes); + $html .= ' '; + $html .= '<small>'; + $html .= '<a href="#" class="assign-me" data-target-id="form-user_id" data-current-id="'.$this->userSession->getId().'" title="'.t('Assign to me').'" aria-label="'.t('Assign to me').'">'.t('Me').'</a>'; + $html .= '</small>'; + + return $html; + } + + public function renderTimeEstimatedField(array $values, array $errors = array(), array $attributes = array()) + { + $attributes = array_merge(array('tabindex="3"'), $attributes); + + $html = $this->helper->form->label(t('Original estimate'), 'time_estimated'); + $html .= $this->helper->form->numeric('time_estimated', $values, $errors, $attributes); + $html .= ' '.t('hours'); + + return $html; + } + + public function renderTimeSpentField(array $values, array $errors = array(), array $attributes = array()) + { + $attributes = array_merge(array('tabindex="4"'), $attributes); + + $html = $this->helper->form->label(t('Time spent'), 'time_spent'); + $html .= $this->helper->form->numeric('time_spent', $values, $errors, $attributes); + $html .= ' '.t('hours'); + + return $html; + } + + public function getSubtaskTooltip(array $subtask) + { + switch ($subtask['status']) { + case SubtaskModel::STATUS_TODO: + return t('Subtask not started'); + case SubtaskModel::STATUS_INPROGRESS: + return t('Subtask currently in progress'); + case SubtaskModel::STATUS_DONE: + return t('Subtask completed'); + } + + return ''; + } +} diff --git a/app/Helper/TaskHelper.php b/app/Helper/TaskHelper.php new file mode 100644 index 0000000..6c6c823 --- /dev/null +++ b/app/Helper/TaskHelper.php @@ -0,0 +1,380 @@ +<?php + +namespace Kanboard\Helper; + +use Kanboard\Core\Base; + +/** + * Task helpers + * + * @package helper + * @author Frederic Guillot + */ +class TaskHelper extends Base +{ + /** + * Local cache for project columns + * + * @access private + * @var array + */ + private $columns = array(); + + public function getColors() + { + return $this->colorModel->getList(); + } + + public function recurrenceTriggers() + { + return $this->taskRecurrenceModel->getRecurrenceTriggerList(); + } + + public function recurrenceTimeframes() + { + return $this->taskRecurrenceModel->getRecurrenceTimeframeList(); + } + + public function recurrenceBasedates() + { + return $this->taskRecurrenceModel->getRecurrenceBasedateList(); + } + + public function renderTitleField(array $values, array $errors) + { + $html = $this->helper->form->label(t('Title'), 'title', ['class="ui-helper-hidden-accessible"']); + $html .= $this->helper->form->text( + 'title', + $values, + $errors, + array( + 'autofocus', + 'required', + 'tabindex="1"', + 'placeholder="'.t('Title').'"' + ) + ); + + return $html; + } + + public function renderDescriptionField(array $values, array $errors) + { + return $this->helper->form->textEditor('description', $values, $errors, array('tabindex' => 2, 'aria-label' => t('Description'))); + } + + public function renderDescriptionTemplateDropdown($projectId) + { + $templates = $this->predefinedTaskDescriptionModel->getAll($projectId); + + if (! empty($templates)) { + $html = '<div class="dropdown dropdown-smaller">'; + $html .= '<a href="#" class="dropdown-menu dropdown-menu-link-icon"><i class="fa fa-floppy-o fa-fw" aria-hidden="true"></i>'.t('Template for the task description').' <i class="fa fa-caret-down" aria-hidden="true"></i></a>'; + $html .= '<ul>'; + + foreach ($templates as $template) { + $html .= '<li>'; + $html .= '<a href="#" data-template-target="textarea[name=description]" data-template="'.$this->helper->text->e($template['description']).'" class="js-template">'; + $html .= $this->helper->text->e($template['title']); + $html .= '</a>'; + $html .= '</li>'; + } + + $html .= '</ul></div>'; + return $html; + } + + return ''; + } + + public function renderTagField(array $project, array $tags = array()) + { + $options = $this->tagModel->getAssignableList($project['id'], $project['enable_global_tags']); + + $html = $this->helper->form->label(t('Tags'), 'tags[]'); + $html .= '<input type="hidden" name="tags[]" value="">'; + $html .= '<select name="tags[]" id="form-tags" class="tag-autocomplete" multiple tabindex="3">'; + + foreach ($options as $tag) { + $html .= sprintf( + '<option value="%s" %s>%s</option>', + $this->helper->text->e($tag), + in_array($tag, $tags) ? 'selected="selected"' : '', + $this->helper->text->e($tag) + ); + } + + $html .= '</select>'; + + return $html; + } + + public function renderColorField(array $values) + { + $html = $this->helper->form->colorSelect('color_id', $values); + return $html; + } + + public function renderAssigneeField(array $users, array $values, array $errors = array(), array $attributes = array()) + { + if (isset($values['project_id']) && ! $this->helper->projectRole->canChangeAssignee($values)) { + return ''; + } + + $attributes = array_merge(array('tabindex="5"'), $attributes); + + $html = $this->helper->form->label(t('Assignee'), 'owner_id'); + $html .= $this->helper->form->select('owner_id', $users, $values, $errors, $attributes); + $html .= ' '; + $html .= '<small>'; + $html .= '<a href="#" class="assign-me" data-target-id="form-owner_id" data-current-id="'.$this->userSession->getId().'" title="'.t('Assign to me').'" aria-label="'.t('Assign to me').'">'.t('Me').'</a>'; + $html .= '</small>'; + + return $html; + } + + public function renderCategoryField(array $categories, array $values, array $errors = array(), array $attributes = array(), $allow_one_item = false) + { + $attributes = array_merge(array('tabindex="6"'), $attributes); + $html = ''; + + if (! (! $allow_one_item && count($categories) === 1 && key($categories) == 0)) { + $html .= $this->helper->form->label(t('Category'), 'category_id'); + $html .= $this->helper->form->select('category_id', $categories, $values, $errors, $attributes); + } + + return $html; + } + + public function renderSwimlaneField(array $swimlanes, array $values, array $errors = array(), array $attributes = array()) + { + $attributes = array_merge(array('tabindex="7"'), $attributes); + $html = ''; + + if (count($swimlanes) > 1) { + $html .= $this->helper->form->label(t('Swimlane'), 'swimlane_id'); + $html .= $this->helper->form->select('swimlane_id', $swimlanes, $values, $errors, $attributes); + } + + return $html; + } + + public function renderColumnField(array $columns, array $values, array $errors = array(), array $attributes = array()) + { + $attributes = array_merge(array('tabindex="8"'), $attributes); + + $html = $this->helper->form->label(t('Column'), 'column_id'); + $html .= $this->helper->form->select('column_id', $columns, $values, $errors, $attributes); + + return $html; + } + + public function renderPriorityField(array $project, array $values) + { + $range = range($project['priority_start'], $project['priority_end']); + $options = array_combine($range, $range); + $values += array('priority' => $project['priority_default']); + + $html = $this->helper->form->label(t('Priority'), 'priority'); + $html .= $this->helper->form->select('priority', $options, $values, array(), array('tabindex="9"')); + + return $html; + } + + public function renderScoreField(array $values, array $errors = array(), array $attributes = array()) + { + $attributes = array_merge(array('tabindex="14"'), $attributes); + + $html = $this->helper->form->label(t('Complexity'), 'score'); + $html .= $this->helper->form->number('score', $values, $errors, $attributes); + + return $html; + } + + public function renderReferenceField(array $values, array $errors = array(), array $attributes = array()) + { + $attributes = array_merge(array('tabindex="15"'), $attributes); + + $html = $this->helper->form->label(t('Reference'), 'reference'); + $html .= $this->helper->form->text('reference', $values, $errors, $attributes, 'form-input-small'); + + return $html; + } + + public function renderTimeEstimatedField(array $values, array $errors = array(), array $attributes = array()) + { + $attributes = array_merge(array('tabindex="12"'), $attributes); + + $html = $this->helper->form->label(t('Original estimate'), 'time_estimated'); + $html .= $this->helper->form->numeric('time_estimated', $values, $errors, $attributes); + $html .= ' '.t('hours'); + + return $html; + } + + public function renderTimeSpentField(array $values, array $errors = array(), array $attributes = array()) + { + $attributes = array_merge(array('tabindex="13"'), $attributes); + + $html = $this->helper->form->label(t('Time spent'), 'time_spent'); + $html .= $this->helper->form->numeric('time_spent', $values, $errors, $attributes); + $html .= ' '.t('hours'); + + return $html; + } + + public function renderStartDateField(array $values, array $errors = array(), array $attributes = array()) + { + $attributes = array_merge(array('tabindex="11"'), $attributes); + return $this->helper->form->datetime(t('Start Date'), 'date_started', $values, $errors, $attributes); + } + + public function renderDueDateField(array $values, array $errors = array(), array $attributes = array()) + { + $attributes = array_merge(array('tabindex="10"'), $attributes); + return $this->helper->form->datetime(t('Due Date'), 'date_due', $values, $errors, $attributes); + } + + public function renderPriority($priority) + { + $html = '<span class="task-priority" title="'.t('Task priority').'">'; + $html .= '<span class="ui-helper-hidden-accessible">'.t('Task priority').' </span>'; + $html .= $this->helper->text->e($priority >= 0 ? 'P'.$priority : '-P'.abs($priority)); + $html .= '</span>'; + + return $html; + } + + public function renderReference(array $task) + { + if (! empty($task['reference'])) { + $reference = $this->helper->text->e($task['reference']); + + if (filter_var($task['reference'], FILTER_VALIDATE_URL) !== false) { + return sprintf('<a href="%s" target=_blank">%s</a>', $reference, $reference); + } + + return $reference; + } + + return ''; + } + + public function renderInternalLinkField(array $internallinks, array $values, array $errors = array(), array $attributes = array()) + { + $html = ''; + + $html .= $this->helper->form->hidden('opposite_task_id', $values); + $html .= '<span class="task-internallink" title="'.t('Add internal link').'">'.t('Add internal link').'</span>'; + $html .= '<br><br>'; + $html .= $this->helper->form->label(t('Label'), 'link_id'); + $html .= $this->helper->form->select('link_id', $internallinks, $values, $errors, $attributes); + + $html .= $this->helper->form->label(t('Task'), 'title'); + $html .= $this->helper->form->text( + 'title', + $values, + $errors, + array( + 'required', + 'placeholder="'.t('Start to type task title...').'"', + 'title="'.t('Start to type task title...').'"', + 'data-dst-field="opposite_task_id"', + 'data-search-url="'.$this->helper->url->href('TaskAjaxController', 'autocomplete', array('exclude_task_ids' => $values['task_ids'])).'"', + ), + 'autocomplete' + ); + + return $html; + } + + public function renderFileUpload($screenshot = '', array $files = array()) + { + $upload_max_size = get_upload_max_size()*0.90; // 10% margin for the orther part of request + conversion in base64 + $html = '<div class="task-form-bottom-column">'; + $html .= ' <div id="screenshot-zone">'; + $html .= ' <p id="screenshot-inner">'.t('Take a screenshot and press CTRL+V or ⌘+V to paste here.').'</p>'; + $html .= ' </div>'; + $html .= '</div>'; + $html .= '<div class="task-form-bottom-column">'; + $html .= $this->helper->app->component('file-upload-task-create', array( + 'maxSize' => $upload_max_size, + 'labelDropzone' => t('Drag and drop your files here'), + 'labelOr' => t('or'), + 'labelChooseFiles' => t('choose files'), + 'labelOversize' => t('The total maximum allowed attachments size is %sB.', $this->helper->text->bytes($upload_max_size)), + 'labelSuccess' => t('All files have been uploaded successfully.'), + 'labelCloseSuccess' => t('Close this window'), + 'labelUploadError' => t('Unable to upload this file.'), + 'screenshot' => $screenshot, + 'files' => $files, + )); + $html .= '</div>'; + return $html; + } + + public function getProgress($task) + { + if (! isset($this->columns[$task['project_id']])) { + $this->columns[$task['project_id']] = $this->columnModel->getList($task['project_id']); + } + + return $this->taskModel->getProgress($task, $this->columns[$task['project_id']]); + } + + public function getNewBoardTaskButton(array $swimlane, array $column) + { + $html = '<div class="board-add-icon">'; + $providers = $this->externalTaskManager->getProviders(); + + if (empty($providers)) { + $html .= $this->helper->modal->largeIcon( + 'plus', + t('Add a new task'), + 'TaskCreationController', + 'show', + array( + 'project_id' => $column['project_id'], + 'column_id' => $column['id'], + 'swimlane_id' => $swimlane['id'], + ) + ); + } else { + $html .= '<div class="dropdown">'; + $html .= '<a href="#" class="dropdown-menu"><i class="fa fa-plus" aria-hidden="true"></i></a><ul>'; + + $link = $this->helper->modal->large( + 'plus', + t('Add a new Kanboard task'), + 'TaskCreationController', + 'show', + array( + 'project_id' => $column['project_id'], + 'column_id' => $column['id'], + 'swimlane_id' => $swimlane['id'], + ) + ); + + $html .= '<li>'.$link.'</li>'; + + foreach ($providers as $provider) { + $link = $this->helper->url->link( + $provider->getMenuAddLabel(), + 'ExternalTaskCreationController', + 'step1', + array('project_id' => $column['project_id'], 'swimlane_id' => $swimlane['id'], 'column_id' => $column['id'], 'provider_name' => $provider->getName()), + false, + 'js-modal-large' + ); + + $html .= '<li>'.$provider->getIcon().' '.$link.'</li>'; + } + + $html .= '</ul></div>'; + } + + $html .= '</div>'; + + return $html; + } +} diff --git a/app/Helper/TextHelper.php b/app/Helper/TextHelper.php new file mode 100644 index 0000000..f84c4c2 --- /dev/null +++ b/app/Helper/TextHelper.php @@ -0,0 +1,121 @@ +<?php + +namespace Kanboard\Helper; + +use Kanboard\Core\Markdown; +use Kanboard\Core\Base; + +/** + * Text Helpers + * + * @package helper + * @author Frederic Guillot + */ +class TextHelper extends Base +{ + /** + * HTML escaping + * + * @param string $value Value to escape + * @return string + */ + public function e($value) + { + return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8', false); + } + + /** + * Join with HTML escaping + * + * @param $glue + * @param array $list + * @return string + */ + public function implode($glue, array $list) + { + array_walk($list, function (&$value) { + $value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8', false); + }); + return implode($glue, $list); + } + + /** + * Markdown transformation + * + * @param string $text + * @param boolean $isPublicLink + * @return string + */ + public function markdown($text, $isPublicLink = false) + { + $parser = new Markdown($this->container, $isPublicLink); + $parser->setSafeMode(true); + $parser->setMarkupEscaped(MARKDOWN_ESCAPE_HTML); + $parser->setBreaksEnabled(true); + return $parser->text($text ?: ''); + } + + /** + * Reply transformation + * + * @param string $username + * @param string $text + * @return string + */ + public function reply($username, $text) + { + $res = t('%s wrote: ', $username).PHP_EOL.'> '; + + $lines = preg_split("/\r\n|\n|\r/", $text); + + return $res.implode(PHP_EOL.'> ', $lines); + } + + /** + * Format a file size + * + * @param integer $size Size in bytes + * @param integer $precision Precision + * @return string + */ + public function bytes($size, $precision = 2) + { + if ($size == 0) { + return 0; + } + + $base = log($size) / log(1024); + $suffixes = array('', 'k', 'M', 'G', 'T'); + + return round(pow(1024, $base - floor($base)), $precision).$suffixes[(int)floor($base)]; + } + + /** + * Return true if needle is contained in the haystack + * + * @param string $haystack Haystack + * @param string $needle Needle + * @return boolean + */ + public function contains($haystack, $needle) + { + return strpos($haystack, $needle) !== false; + } + + /** + * Return a value from a dictionary + * + * @param mixed $id Key + * @param array $listing Dictionary + * @param string $default_value Value displayed when the key doesn't exists + * @return string + */ + public function in($id, array $listing, $default_value = '?') + { + if (isset($listing[$id])) { + return $this->helper->text->e($listing[$id]); + } + + return $default_value; + } +} diff --git a/app/Helper/UrlHelper.php b/app/Helper/UrlHelper.php new file mode 100644 index 0000000..08bd2b9 --- /dev/null +++ b/app/Helper/UrlHelper.php @@ -0,0 +1,214 @@ +<?php + +namespace Kanboard\Helper; + +use Kanboard\Core\Base; + +/** + * Url Helper + * + * @package helper + * @author Frederic Guillot + */ +class UrlHelper extends Base +{ + private $base = ''; + private $directory = ''; + + /** + * Helper to generate a link to the documentation + * + * @access public + * @param string $label + * @param string $file + * @return string + */ + public function doc($label, $file = '') + { + $url = sprintf(DOCUMENTATION_URL_PATTERN, $file); + return sprintf('<a href="%s" target="_blank">%s</a>', $url, $label); + } + + /** + * Button Link Element + * + * @access public + * @param string $icon Font-Awesome icon + * @param string $label Link label + * @param string $controller Controller name + * @param string $action Action name + * @param array $params Url parameters + * @param string $class CSS class attribute + * @return string + */ + public function button($icon, $label, $controller, $action, array $params = array(), $class = '') + { + $html = '<i class="fa fa-'.$icon.' fa-fw"></i> '.$label; + $class = 'btn '.$class; + return $this->link($html, $controller, $action, $params, false, $class); + } + + /** + * Link element with icon + * + * @access public + * @param string $icon Icon name + * @param string $label Link label + * @param string $controller Controller name + * @param string $action Action name + * @param array $params Url parameters + * @param boolean $csrf Add a CSRF token + * @param string $class CSS class attribute + * @param string $title Link title + * @param boolean $newTab Open the link in a new tab + * @param string $anchor Link Anchor + * @param bool $absolute + * @return string + */ + public function icon($icon, $label, $controller, $action, array $params = array(), $csrf = false, $class = '', $title = '', $newTab = false, $anchor = '', $absolute = false) + { + $html = '<i class="fa fa-fw fa-'.$icon.'" aria-hidden="true"></i>'.$label; + return $this->helper->url->link($html, $controller, $action, $params, $csrf, $class, $title, $newTab, $anchor, $absolute); + } + + /** + * Link element + * + * @access public + * @param string $label Link label + * @param string $controller Controller name + * @param string $action Action name + * @param array $params Url parameters + * @param boolean $csrf Add a CSRF token + * @param string $class CSS class attribute + * @param string $title Link title + * @param boolean $newTab Open the link in a new tab + * @param string $anchor Link Anchor + * @param bool $absolute + * @return string + */ + public function link($label, $controller, $action, array $params = array(), $csrf = false, $class = '', $title = '', $newTab = false, $anchor = '', $absolute = false) + { + return '<a href="'.$this->href($controller, $action, $params, $csrf, $anchor, $absolute).'" class="'.$class.'" title=\''.$title.'\' '.($newTab ? 'target="_blank"' : '').'>'.$label.'</a>'; + } + + /** + * Absolute link + * + * @param string $label + * @param string $controller + * @param string $action + * @param array $params + * @return string + */ + public function absoluteLink($label, $controller, $action, array $params = array()) + { + return $this->link($label, $controller, $action, $params, false, '', '', true, '', true); + } + + /** + * HTML Hyperlink + * + * @access public + * @param string $controller Controller name + * @param string $action Action name + * @param array $params Url parameters + * @param boolean $csrf Add a CSRF token + * @param string $anchor Link Anchor + * @param boolean $absolute Absolute or relative link + * @return string + */ + public function href($controller, $action, array $params = array(), $csrf = false, $anchor = '', $absolute = false) + { + return $this->build('&', $controller, $action, $params, $csrf, $anchor, $absolute); + } + + /** + * Generate controller/action url + * + * @access public + * @param string $controller Controller name + * @param string $action Action name + * @param array $params Url parameters + * @param string $anchor Link Anchor + * @param boolean $absolute Absolute or relative link + * @return string + */ + public function to($controller, $action, array $params = array(), $anchor = '', $absolute = false) + { + return $this->build('&', $controller, $action, $params, false, $anchor, $absolute); + } + + /** + * Get application base url + * + * @access public + * @return string + */ + public function base() + { + if (empty($this->base)) { + $this->base = $this->configModel->get('application_url') ?: 'http://localhost/'; + } + + return $this->base; + } + + /** + * Get application base directory + * + * @access public + * @return string + */ + public function dir() + { + if ($this->directory === '' && $this->request->getMethod() !== '') { + if (defined('KANBOARD_URL') && strlen(KANBOARD_URL) > 0) { + $this->directory = parse_url(KANBOARD_URL, PHP_URL_PATH); + } else { + $this->directory = str_replace('\\', '/', dirname($this->request->getServerVariable('PHP_SELF'))); + $this->directory = $this->directory !== '/' ? $this->directory.'/' : '/'; + $this->directory = str_replace('//', '/', $this->directory); + } + } + + return $this->directory; + } + + /** + * Build relative url + * + * @access protected + * @param string $separator Querystring argument separator + * @param string $controller Controller name + * @param string $action Action name + * @param array $params Url parameters + * @param boolean $csrf Add a CSRF token + * @param string $anchor Link Anchor + * @param boolean $absolute Absolute or relative link + * @return string + */ + protected function build($separator, $controller, $action, array $params = array(), $csrf = false, $anchor = '', $absolute = false) + { + $path = $this->route->findUrl($controller, $action, $params); + $qs = array(); + + if (empty($path)) { + $qs['controller'] = $controller; + $qs['action'] = $action; + $qs += $params; + } else { + unset($params['plugin']); + } + + if ($csrf) { + $qs['csrf_token'] = $this->token->getCSRFToken(); + } + + if (! empty($qs)) { + $path .= '?'.http_build_query($qs, '', $separator); + } + + return ($absolute ? $this->base() : $this->dir()).$path.(empty($anchor) ? '' : '#'.$anchor); + } +} diff --git a/app/Helper/UserHelper.php b/app/Helper/UserHelper.php new file mode 100644 index 0000000..fbf1235 --- /dev/null +++ b/app/Helper/UserHelper.php @@ -0,0 +1,198 @@ +<?php + +namespace Kanboard\Helper; + +use Kanboard\Core\Base; + +/** + * User helpers + * + * @package helper + * @author Frederic Guillot + */ +class UserHelper extends Base +{ + public function getTheme() + { + return $this->userSession->getTheme(); + } + + /** + * Return subtask list toggle value + * + * @access public + * @return boolean + */ + public function hasSubtaskListActivated() + { + return $this->userSession->hasSubtaskListActivated(); + } + + /** + * Return true if the logged user has unread notifications + * + * @access public + * @return boolean + */ + public function hasNotifications() + { + return $this->userUnreadNotificationModel->hasNotifications($this->userSession->getId()); + } + + /** + * Get initials from a user + * + * @access public + * @param string $name + * @return string + */ + public function getInitials($name) + { + $initials = ''; + + foreach (explode(' ', $name, 2) as $string) { + $initials .= mb_substr($string, 0, 1, 'UTF-8'); + } + + return mb_strtoupper($initials, 'UTF-8'); + } + + /** + * Return the user full name + * + * @param array $user User properties + * @return string + */ + public function getFullname(array $user = array()) + { + $user = empty($user) ? $this->userSession->getAll() : $user; + return $user['name'] ?: $user['username']; + } + + /** + * Get user id + * + * @access public + * @return integer + */ + public function getId() + { + return $this->userSession->getId(); + } + + /** + * Check if the given user_id is the connected user + * + * @param integer $user_id User id + * @return boolean + */ + public function isCurrentUser($user_id) + { + return $this->userSession->getId() == $user_id; + } + + /** + * Return if the logged user is admin + * + * @access public + * @return boolean + */ + public function isAdmin() + { + return $this->userSession->isAdmin(); + } + + /** + * Get role + * + * @access public + * @return string + */ + public function getRole() + { + return $this->userSession->getRole(); + } + + /** + * Get role name + * + * @access public + * @param string $role + * @return string + */ + public function getRoleName($role = '') + { + return $this->role->getRoleName($role ?: $this->userSession->getRole()); + } + + /** + * Get group names for a given user and return an associative array: + * + * @access public + * @param integer $userID User id + * @return array + */ + public function getUsersGroupNames($userID) + { + $groupsList = array_column($this->groupMemberModel->getGroups($userID), 'name'); + $limitedList = $groupsList; + $total = count($groupsList); + + if ($total > 0 && SHOW_GROUP_MEMBERSHIPS_IN_USERLIST_WITH_LIMIT > 0) { + $limitedList = array_slice($groupsList, 0, SHOW_GROUP_MEMBERSHIPS_IN_USERLIST_WITH_LIMIT); + } + + return [ + 'full_list' => $groupsList, + 'limited_list' => $limitedList, + 'has_groups' => $total > 0, + 'total' => $total, + 'shown' => count($limitedList), + ]; + } + + /** + * Check application access + * + * @param string $controller + * @param string $action + * @return bool + */ + public function hasAccess($controller, $action) + { + if (! $this->userSession->isLogged()) { + return false; + } + + $key = 'app_access:'.$controller.$action; + $result = $this->memoryCache->get($key); + + if ($result === null) { + $result = $this->applicationAuthorization->isAllowed($controller, $action, $this->userSession->getRole()); + $this->memoryCache->set($key, $result); + } + + return $result; + } + + /** + * Check project access + * + * @param string $controller + * @param string $action + * @param integer $project_id + * @return bool + */ + public function hasProjectAccess($controller, $action, $project_id) + { + $key = 'project_access:'.$controller.$action.$project_id; + $result = $this->memoryCache->get($key); + + if ($result === null) { + $result = $this->helper->projectRole->checkProjectAccess($controller, $action, $project_id); + $this->memoryCache->set($key, $result); + } + + return $result; + } +} diff --git a/app/Import/TaskImport.php b/app/Import/TaskImport.php new file mode 100644 index 0000000..9ed351c --- /dev/null +++ b/app/Import/TaskImport.php @@ -0,0 +1,179 @@ +<?php + +namespace Kanboard\Import; + +use Kanboard\Core\Base; +use Kanboard\Core\Csv; +use Kanboard\Core\ExternalLink\ExternalLinkManager; +use Kanboard\Core\ExternalLink\ExternalLinkProviderNotFound; +use SimpleValidator\Validator; +use SimpleValidator\Validators; + +/** + * Task CSV Import + * + * @package Kanboard\Import + * @author Frederic Guillot + */ +class TaskImport extends Base +{ + protected $nbImportedTasks = 0; + protected $projectId = 0; + + public function setProjectId($projectId) + { + $this->projectId = $projectId; + return $this; + } + + public function getNumberOfImportedTasks() + { + return $this->nbImportedTasks; + } + + public function getColumnMapping() + { + return array( + 'reference' => e('Reference'), + 'title' => e('Title'), + 'description' => e('Description'), + 'assignee' => e('Assignee Username'), + 'creator' => e('Creator Username'), + 'color' => e('Color Name'), + 'column' => e('Column Name'), + 'category' => e('Category Name'), + 'swimlane' => e('Swimlane Name'), + 'score' => e('Complexity'), + 'time_estimated' => e('Time Estimated'), + 'time_spent' => e('Time Spent'), + 'date_started' => e('Start Date'), + 'date_due' => e('Due Date'), + 'priority' => e('Priority'), + 'is_active' => e('Status'), + 'tags' => e('Tags'), + 'external_link' => e('External Link'), + ); + } + + public function importTask(array $row, $lineNumber) + { + $task = $this->prepareTask($row); + + if ($this->validateCreation($task)) { + $taskId = $this->taskCreationModel->create($task); + + if ($taskId > 0) { + $this->logger->debug(__METHOD__.': imported successfully line '.$lineNumber); + $this->nbImportedTasks++; + + if (! empty($row['tags'])) { + $this->taskTagModel->save($this->projectId, $taskId, explode_csv_field($row['tags'])); + } + + if (! empty($row['external_link'])) { + $this->createExternalLink($taskId, $row['external_link']); + } + } else { + $this->logger->error(__METHOD__.': creation error at line '.$lineNumber); + } + } else { + $this->logger->error(__METHOD__.': validation error at line '.$lineNumber); + } + } + + public function prepareTask(array $row) + { + $values = array(); + $values['project_id'] = $this->projectId; + $values['reference'] = $row['reference']; + $values['title'] = $row['title']; + $values['description'] = $row['description']; + $values['is_active'] = Csv::getBooleanValue($row['is_active']) == 1 ? 0 : 1; + $values['score'] = (int) $row['score']; + $values['priority'] = (int) $row['priority']; + $values['time_estimated'] = (float) $row['time_estimated']; + $values['time_spent'] = (float) $row['time_spent']; + + if (! empty($row['assignee'])) { + $values['owner_id'] = $this->userModel->getIdByUsername($row['assignee']); + } + + if (! empty($row['creator'])) { + $values['creator_id'] = $this->userModel->getIdByUsername($row['creator']); + } + + if (! empty($row['color'])) { + $values['color_id'] = $this->colorModel->find($row['color']); + } + + if (! empty($row['column'])) { + $values['column_id'] = $this->columnModel->getColumnIdByTitle($this->projectId, $row['column']); + } + + if (! empty($row['category'])) { + $values['category_id'] = $this->categoryModel->getIdByName($this->projectId, $row['category']); + } + + if (! empty($row['swimlane'])) { + $values['swimlane_id'] = $this->swimlaneModel->getIdByName($this->projectId, $row['swimlane']); + } + + if (! empty($row['date_due'])) { + $values['date_due'] = $this->dateParser->getTimestamp($row['date_due']); + } + + if (! empty($row['date_started'])) { + $values['date_started'] = $this->dateParser->getTimestamp($row['date_started']); + } + + $this->helper->model->removeEmptyFields( + $values, + array('owner_id', 'creator_id', 'color_id', 'column_id', 'category_id', 'swimlane_id', 'date_due', 'date_started', 'priority') + ); + + return $values; + } + + protected function validateCreation(array $values) + { + $v = new Validator($values, array( + new Validators\Integer('project_id', t('This value must be an integer')), + new Validators\Required('project_id', t('The project is required')), + new Validators\Required('title', t('The title is required')), + new Validators\MaxLength('title', t('The maximum length is %d characters', 65535), 65535), + new Validators\MaxLength('reference', t('The maximum length is %d characters', 255), 255), + )); + + return $v->execute(); + } + + protected function createExternalLink($taskId, $externalLink) + { + try { + $provider = $this->externalLinkManager + ->setUserInputText($externalLink) + ->setUserInputType(ExternalLinkManager::TYPE_AUTO) + ->find(); + + $link = $provider->getLink(); + $dependencies = $provider->getDependencies(); + $values = array( + 'task_id' => $taskId, + 'title' => $link->getTitle() ?: $link->getUrl(), + 'url' => $link->getUrl(), + 'link_type' => $provider->getType(), + 'dependency' => key($dependencies), + ); + + list($valid, $errors) = $this->externalLinkValidator->validateCreation($values); + + if ($valid) { + $this->taskExternalLinkModel->create($values); + } else { + $this->logger->error(__METHOD__.': '.var_export($errors, true)); + } + } catch (ExternalLinkProviderNotFound $e) { + $this->logger->error(__METHOD__.': '.$e->getMessage()); + } + } +} diff --git a/app/Import/UserImport.php b/app/Import/UserImport.php new file mode 100644 index 0000000..004d20b --- /dev/null +++ b/app/Import/UserImport.php @@ -0,0 +1,126 @@ +<?php + +namespace Kanboard\Import; + +use Kanboard\Model\UserModel; +use SimpleValidator\Validator; +use SimpleValidator\Validators; +use Kanboard\Core\Security\Role; +use Kanboard\Core\Base; +use Kanboard\Core\Csv; +use Kanboard\Notification\MailNotification; +use Kanboard\Notification\WebNotification; + +/** + * User Import + * + * @package import + * @author Frederic Guillot + */ +class UserImport extends Base +{ + /** + * Number of successful import + * + * @access public + * @var integer + */ + public $counter = 0; + + /** + * Get mapping between CSV header and SQL columns + * + * @access public + * @return array + */ + public function getColumnMapping() + { + return array( + 'username' => 'Username', + 'password' => 'Password', + 'email' => 'Email', + 'name' => 'Full Name', + 'is_admin' => 'Administrator', + 'is_manager' => 'Manager', + 'is_ldap_user' => 'Remote User', + ); + } + + /** + * Import a single row + * + * @access public + * @param array $row + * @param integer $line_number + */ + public function import(array $row, $line_number) + { + $row = $this->prepare($row); + + if ($this->validateCreation($row)) { + if (($user_id = $this->userModel->create($row)) !== false) { + $this->logger->debug('UserImport: imported successfully line '.$line_number); + $this->counter++; + + if ($this->configModel->get('notifications_enabled', 0) == 1) { + $this->userNotificationTypeModel->saveSelectedTypes($user_id, [MailNotification::TYPE, WebNotification::TYPE]); + } + } else { + $this->logger->error('UserImport: creation error at line '.$line_number); + } + } else { + $this->logger->error('UserImport: validation error at line '.$line_number); + } + } + + /** + * Format row before validation + * + * @access public + * @param array $row + * @return array + */ + public function prepare(array $row) + { + $row['username'] = strtolower($row['username']); + + foreach (array('is_admin', 'is_manager', 'is_ldap_user') as $field) { + $row[$field] = Csv::getBooleanValue($row[$field]); + } + + if ($row['is_admin'] == 1) { + $row['role'] = Role::APP_ADMIN; + } elseif ($row['is_manager'] == 1) { + $row['role'] = Role::APP_MANAGER; + } else { + $row['role'] = Role::APP_USER; + } + + unset($row['is_admin']); + unset($row['is_manager']); + + $this->helper->model->removeEmptyFields($row, array('password', 'email', 'name')); + + return $row; + } + + /** + * Validate user creation + * + * @access public + * @param array $values + * @return boolean + */ + public function validateCreation(array $values) + { + $v = new Validator($values, array( + new Validators\MaxLength('username', t('The maximum length is %d characters', 255), 255), + new Validators\Unique('username', t('The username must be unique'), $this->db->getConnection(), UserModel::TABLE, 'id'), + new Validators\MinLength('password', t('The minimum length is %d characters', 6), 6), + new Validators\Email('email', t('Email address invalid')), + new Validators\Integer('is_ldap_user', t('This value must be an integer')), + )); + + return $v->execute(); + } +} diff --git a/app/Job/BaseJob.php b/app/Job/BaseJob.php new file mode 100644 index 0000000..60522ac --- /dev/null +++ b/app/Job/BaseJob.php @@ -0,0 +1,33 @@ +<?php + +namespace Kanboard\Job; + +use Kanboard\Core\Base; + +/** + * Class BaseJob + * + * @package Kanboard\Job + * @author Frederic Guillot + */ +abstract class BaseJob extends Base +{ + /** + * Job parameters + * + * @access protected + * @var array + */ + protected $jobParams = array(); + + /** + * Get job parameters + * + * @access public + * @return array + */ + public function getJobParams() + { + return $this->jobParams; + } +} diff --git a/app/Job/CommentEventJob.php b/app/Job/CommentEventJob.php new file mode 100644 index 0000000..4faec20 --- /dev/null +++ b/app/Job/CommentEventJob.php @@ -0,0 +1,50 @@ +<?php + +namespace Kanboard\Job; + +use Kanboard\EventBuilder\CommentEventBuilder; +use Kanboard\Model\CommentModel; + +/** + * Class CommentEventJob + * + * @package Kanboard\Job + * @author Frederic Guillot + */ +class CommentEventJob extends BaseJob +{ + /** + * Set job params + * + * @param int $commentId + * @param string $eventName + * @return $this + */ + public function withParams($commentId, $eventName) + { + $this->jobParams = array($commentId, $eventName); + return $this; + } + + /** + * Execute job + * + * @param int $commentId + * @param string $eventName + */ + public function execute($commentId, $eventName) + { + $event = CommentEventBuilder::getInstance($this->container) + ->withCommentId($commentId) + ->buildEvent(); + + if ($event !== null) { + $this->dispatcher->dispatch($event, $eventName); + + if ($eventName === CommentModel::EVENT_CREATE) { + $userMentionJob = $this->userMentionJob->withParams($event['comment']['comment'], CommentModel::EVENT_USER_MENTION, $event); + $this->queueManager->push($userMentionJob); + } + } + } +} diff --git a/app/Job/EmailJob.php b/app/Job/EmailJob.php new file mode 100644 index 0000000..c6eb500 --- /dev/null +++ b/app/Job/EmailJob.php @@ -0,0 +1,55 @@ +<?php + +namespace Kanboard\Job; + +/** + * Class EmailJob + * + * @package Kanboard\Job + * @author Frederic Guillot + */ +class EmailJob extends BaseJob +{ + /** + * Set job parameters + * + * @access public + * @param string $recipientEmail + * @param string $recipientName + * @param string $subject + * @param string $html + * @param string $authorName + * @param string $authorEmail + * @return $this + */ + public function withParams($recipientEmail, $recipientName, $subject, $html, $authorName, $authorEmail) + { + $this->jobParams = array($recipientEmail, $recipientName, $subject, $html, $authorName, $authorEmail); + return $this; + } + + /** + * Execute job + * + * @access public + * @param string $recipientEmail + * @param string $recipientName + * @param string $subject + * @param string $html + * @param string $authorName + * @param string $authorEmail + */ + public function execute($recipientEmail, $recipientName, $subject, $html, $authorName, $authorEmail) + { + $transport = $this->helper->mail->getMailTransport(); + $startTime = microtime(true); + + $this->logger->debug(__METHOD__.' Sending email to: '.$recipientEmail.' using transport: '.$transport); + + $this->emailClient + ->getTransport($transport) + ->sendEmail($recipientEmail, $recipientName, $subject, $html, $authorName, $authorEmail); + + $this->logger->debug(__METHOD__.' Email sent in '.round(microtime(true) - $startTime, 6).' seconds'); + } +} diff --git a/app/Job/HttpAsyncJob.php b/app/Job/HttpAsyncJob.php new file mode 100644 index 0000000..9e1ffb6 --- /dev/null +++ b/app/Job/HttpAsyncJob.php @@ -0,0 +1,44 @@ +<?php + +namespace Kanboard\Job; + +/** + * Async HTTP Client (fire and forget) + * + * @package Kanboard\Job + * @author Frederic Guillot + */ +class HttpAsyncJob extends BaseJob +{ + /** + * Set job parameters + * + * @access public + * @param string $method + * @param string $url + * @param string $content + * @param array $headers + * @param bool $raiseForErrors + * @return $this + */ + public function withParams($method, $url, $content, array $headers, $raiseForErrors = false) + { + $this->jobParams = array($method, $url, $content, $headers, $raiseForErrors); + return $this; + } + + /** + * Set job parameters + * + * @access public + * @param string $method + * @param string $url + * @param string $content + * @param array $headers + * @param bool $raiseForErrors + */ + public function execute($method, $url, $content, array $headers, $raiseForErrors = false) + { + $this->httpClient->doRequest($method, $url, $content, $headers, $raiseForErrors); + } +} diff --git a/app/Job/NotificationJob.php b/app/Job/NotificationJob.php new file mode 100644 index 0000000..8fb260e --- /dev/null +++ b/app/Job/NotificationJob.php @@ -0,0 +1,43 @@ +<?php + +namespace Kanboard\Job; + +use Kanboard\Event\GenericEvent; + +/** + * Class NotificationJob + * + * @package Kanboard\Job + * @author Frederic Guillot + */ +class NotificationJob extends BaseJob +{ + /** + * Set job parameters + * + * @param GenericEvent $event + * @param string $eventName + * @return $this + */ + public function withParams(GenericEvent $event, $eventName) + { + $this->jobParams = array($event->getAll(), $eventName); + return $this; + } + + /** + * Execute job + * + * @param array $eventData + * @param string $eventName + */ + public function execute(array $eventData, $eventName) + { + if (! empty($eventData['mention'])) { + $this->userNotificationModel->sendUserNotification($eventData['mention'], $eventName, $eventData); + } else { + $this->userNotificationModel->sendNotifications($eventName, $eventData); + $this->projectNotificationModel->sendNotifications($eventData['task']['project_id'], $eventName, $eventData); + } + } +} diff --git a/app/Job/ProjectFileEventJob.php b/app/Job/ProjectFileEventJob.php new file mode 100644 index 0000000..c75dc98 --- /dev/null +++ b/app/Job/ProjectFileEventJob.php @@ -0,0 +1,44 @@ +<?php + +namespace Kanboard\Job; + +use Kanboard\EventBuilder\ProjectFileEventBuilder; + +/** + * Class ProjectFileEventJob + * + * @package Kanboard\Job + * @author Frederic Guillot + */ +class ProjectFileEventJob extends BaseJob +{ + /** + * Set job params + * + * @param int $fileId + * @param string $eventName + * @return $this + */ + public function withParams($fileId, $eventName) + { + $this->jobParams = array($fileId, $eventName); + return $this; + } + + /** + * Execute job + * + * @param int $fileId + * @param string $eventName + */ + public function execute($fileId, $eventName) + { + $event = ProjectFileEventBuilder::getInstance($this->container) + ->withFileId($fileId) + ->buildEvent(); + + if ($event !== null) { + $this->dispatcher->dispatch($event, $eventName); + } + } +} diff --git a/app/Job/ProjectMetricJob.php b/app/Job/ProjectMetricJob.php new file mode 100644 index 0000000..6330bd4 --- /dev/null +++ b/app/Job/ProjectMetricJob.php @@ -0,0 +1,40 @@ +<?php + +namespace Kanboard\Job; + +/** + * Class ProjectMetricJob + * + * @package Kanboard\Job + * @author Frederic Guillot + */ +class ProjectMetricJob extends BaseJob +{ + /** + * Set job parameters + * + * @access public + * @param integer $projectId + * @return $this + */ + public function withParams($projectId) + { + $this->jobParams = array($projectId); + return $this; + } + + /** + * Execute job + * + * @access public + * @param integer $projectId + */ + public function execute($projectId) + { + $this->logger->debug(__METHOD__.' Run project metrics calculation'); + $now = date('Y-m-d'); + + $this->projectDailyColumnStatsModel->updateTotals($projectId, $now); + $this->projectDailyStatsModel->updateTotals($projectId, $now); + } +} diff --git a/app/Job/SubtaskEventJob.php b/app/Job/SubtaskEventJob.php new file mode 100644 index 0000000..340ffcd --- /dev/null +++ b/app/Job/SubtaskEventJob.php @@ -0,0 +1,49 @@ +<?php + +namespace Kanboard\Job; + +use Kanboard\EventBuilder\SubtaskEventBuilder; + +/** + * Class SubtaskEventJob + * + * @package Kanboard\Job + * @author Frederic Guillot + */ +class SubtaskEventJob extends BaseJob +{ + /** + * Set job params + * + * @param int $subtaskId + * @param array $eventNames + * @param array $values + * @return $this + */ + public function withParams($subtaskId, array $eventNames, array $values = array()) + { + $this->jobParams = array($subtaskId, $eventNames, $values); + return $this; + } + + /** + * Execute job + * + * @param int $subtaskId + * @param array $eventNames + * @param array $values + */ + public function execute($subtaskId, array $eventNames, array $values = array()) + { + $event = SubtaskEventBuilder::getInstance($this->container) + ->withSubtaskId($subtaskId) + ->withValues($values) + ->buildEvent(); + + if ($event !== null) { + foreach ($eventNames as $eventName) { + $this->dispatcher->dispatch($event, $eventName); + } + } + } +} diff --git a/app/Job/TaskEventJob.php b/app/Job/TaskEventJob.php new file mode 100644 index 0000000..fd15847 --- /dev/null +++ b/app/Job/TaskEventJob.php @@ -0,0 +1,75 @@ +<?php + +namespace Kanboard\Job; + +use Kanboard\Event\TaskEvent; +use Kanboard\EventBuilder\TaskEventBuilder; +use Kanboard\Model\TaskModel; + +/** + * Class TaskEventJob + * + * @package Kanboard\Job + * @author Frederic Guillot + */ +class TaskEventJob extends BaseJob +{ + /** + * Set job params + * + * @param int $taskId + * @param array $eventNames + * @param array $changes + * @param array $values + * @param array $task + * @return $this + */ + public function withParams($taskId, array $eventNames, array $changes = array(), array $values = array(), array $task = array()) + { + $this->jobParams = array($taskId, $eventNames, $changes, $values, $task); + return $this; + } + + /** + * Execute job + * + * @param int $taskId + * @param array $eventNames + * @param array $changes + * @param array $values + * @param array $task + */ + public function execute($taskId, array $eventNames, array $changes = array(), array $values = array(), array $task = array()) + { + $event = TaskEventBuilder::getInstance($this->container) + ->withTaskId($taskId) + ->withChanges($changes) + ->withValues($values) + ->withTask($task) + ->buildEvent(); + + if ($event !== null) { + foreach ($eventNames as $eventName) { + $this->fireEvent($eventName, $event); + } + } + } + + /** + * Trigger event + * + * @access protected + * @param string $eventName + * @param TaskEvent $event + */ + protected function fireEvent($eventName, TaskEvent $event) + { + $this->logger->debug(__METHOD__.' Event fired: '.$eventName); + $this->dispatcher->dispatch($event, $eventName); + + if ($eventName === TaskModel::EVENT_CREATE) { + $userMentionJob = $this->userMentionJob->withParams($event['task']['description'], TaskModel::EVENT_USER_MENTION, $event); + $this->queueManager->push($userMentionJob); + } + } +} diff --git a/app/Job/TaskFileEventJob.php b/app/Job/TaskFileEventJob.php new file mode 100644 index 0000000..9b3cf82 --- /dev/null +++ b/app/Job/TaskFileEventJob.php @@ -0,0 +1,44 @@ +<?php + +namespace Kanboard\Job; + +use Kanboard\EventBuilder\TaskFileEventBuilder; + +/** + * Class TaskFileEventJob + * + * @package Kanboard\Job + * @author Frederic Guillot + */ +class TaskFileEventJob extends BaseJob +{ + /** + * Set job params + * + * @param int $fileId + * @param string $eventName + * @return $this + */ + public function withParams($fileId, $eventName) + { + $this->jobParams = array($fileId, $eventName); + return $this; + } + + /** + * Execute job + * + * @param int $fileId + * @param string $eventName + */ + public function execute($fileId, $eventName) + { + $event = TaskFileEventBuilder::getInstance($this->container) + ->withFileId($fileId) + ->buildEvent(); + + if ($event !== null) { + $this->dispatcher->dispatch($event, $eventName); + } + } +} diff --git a/app/Job/TaskLinkEventJob.php b/app/Job/TaskLinkEventJob.php new file mode 100644 index 0000000..55be819 --- /dev/null +++ b/app/Job/TaskLinkEventJob.php @@ -0,0 +1,44 @@ +<?php + +namespace Kanboard\Job; + +use Kanboard\EventBuilder\TaskLinkEventBuilder; + +/** + * Class TaskLinkEventJob + * + * @package Kanboard\Job + * @author Frederic Guillot + */ +class TaskLinkEventJob extends BaseJob +{ + /** + * Set job params + * + * @param int $taskLinkId + * @param string $eventName + * @return $this + */ + public function withParams($taskLinkId, $eventName) + { + $this->jobParams = array($taskLinkId, $eventName); + return $this; + } + + /** + * Execute job + * + * @param int $taskLinkId + * @param string $eventName + */ + public function execute($taskLinkId, $eventName) + { + $event = TaskLinkEventBuilder::getInstance($this->container) + ->withTaskLinkId($taskLinkId) + ->buildEvent(); + + if ($event !== null) { + $this->dispatcher->dispatch($event, $eventName); + } + } +} diff --git a/app/Job/UserMentionJob.php b/app/Job/UserMentionJob.php new file mode 100644 index 0000000..a60ee24 --- /dev/null +++ b/app/Job/UserMentionJob.php @@ -0,0 +1,75 @@ +<?php + +namespace Kanboard\Job; + +use Kanboard\Event\GenericEvent; +use Kanboard\Model\UserModel; + +/** + * Class UserMentionJob + * + * @package Kanboard\Job + * @author Frederic Guillot + */ +class UserMentionJob extends BaseJob +{ + /** + * Set job parameters + * + * @param string $text + * @param string $eventName + * @param GenericEvent $event + * @return $this + */ + public function withParams($text, $eventName, GenericEvent $event) + { + $this->jobParams = array($text, $eventName, $event->getAll()); + return $this; + } + + /** + * Execute job + * + * @param string $text + * @param string $eventName + * @param array $eventData + */ + public function execute($text, $eventName, array $eventData) + { + $event = new GenericEvent($eventData); + $users = $this->getMentionedUsers($text); + + foreach ($users as $user) { + if ($this->projectPermissionModel->isMember($event->getProjectId(), $user['id'])) { + $event['mention'] = $user; + $this->dispatcher->dispatch($event, $eventName); + } + } + } + + /** + * Get list of mentioned users + * + * @access public + * @param string $text + * @return array + */ + public function getMentionedUsers($text) + { + $users = array(); + + if ($text !== null && preg_match_all('/@([^\s,!:?]+)/', $text, $matches)) { + array_walk($matches[1], function (&$username) { + $username = rtrim($username, '.'); + }); + $users = $this->db->table(UserModel::TABLE) + ->columns('id', 'username', 'name', 'email', 'language') + ->eq('notifications_enabled', 1) + ->neq('id', $this->userSession->getId()) + ->in('username', array_unique($matches[1])) + ->findAll(); + } + + return $users; + } +} diff --git a/app/Locale/ar_SY/translations.php b/app/Locale/ar_SY/translations.php new file mode 100644 index 0000000..8b018ad --- /dev/null +++ b/app/Locale/ar_SY/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => '٫', + 'number.thousands_separator' => '٬', + 'None' => 'لا شيء', + 'Edit' => 'تعديل', + 'Remove' => 'إزالة', + 'Yes' => 'نعم', + 'No' => 'لا', + 'cancel' => 'إلغاء', + 'or' => 'أو', + 'Yellow' => 'أصفر', + 'Blue' => 'أزرق', + 'Green' => 'أخضر', + 'Purple' => 'أرجواني', + 'Red' => 'أحمر', + 'Orange' => 'برتقالي', + 'Grey' => 'رمادي', + 'Brown' => 'بني', + 'Deep Orange' => 'برتقالي داكن', + 'Dark Grey' => 'رمادي داكن', + 'Pink' => 'وردي', + 'Teal' => 'تركوازي', + 'Cyan' => 'سماوي', + 'Lime' => 'أخضر ليموني', + 'Light Green' => 'أخضر فاتح', + 'Amber' => 'كهرماني', + 'Save' => 'حفظ', + 'Login' => 'تسجيل الدخول', + 'Official website:' => 'الموقع الرسمي:', + 'Unassigned' => 'غير معيّن', + 'View this task' => 'عرض هذه المهمة', + 'Remove user' => 'إزالة المستخدم', + 'Do you really want to remove this user: "%s"?' => 'هل تريد فعلاً إزالة هذا المستخدم: "%s"؟', + 'All users' => 'جميع المستخدمين', + 'Username' => 'اسم المستخدم', + 'Password' => 'كلمة المرور', + 'Administrator' => 'مدير النظام', + 'Sign in' => 'تسجيل الدخول', + 'Users' => 'المستخدمون', + 'Forbidden' => 'ممنوع', + 'Access Forbidden' => 'الوصول ممنوع', + 'Edit user' => 'تعديل المستخدم', + 'Logout' => 'تسجيل الخروج', + 'Bad username or password' => 'اسم المستخدم أو كلمة المرور غير صحيحة', + 'Edit project' => 'تعديل المشروع', + 'Name' => 'الاسم', + 'Projects' => 'المشاريع', + 'No project' => 'لا يوجد مشروع', + 'Project' => 'مشروع', + 'Status' => 'الحالة', + 'Tasks' => 'المهام', + 'Board' => 'اللوحة', + 'Actions' => 'الإجراءات', + 'Inactive' => 'غير نشط', + 'Active' => 'نشط', + 'Unable to update this board.' => 'تعذّر تحديث هذه اللوحة.', + 'Disable' => 'تعطيل', + 'Enable' => 'تمكين', + 'New project' => 'مشروع جديد', + 'Do you really want to remove this project: "%s"?' => 'هل تريد فعلاً إزالة هذا المشروع: "%s"؟', + 'Remove project' => 'إزالة المشروع', + 'Edit the board for "%s"' => 'تعديل اللوحة لـ "%s"', + 'Add a new column' => 'إضافة عمود جديد', + 'Title' => 'العنوان', + 'Assigned to %s' => 'مُسند إلى %s', + 'Remove a column' => 'إزالة عمود', + 'Unable to remove this column.' => 'تعذّر إزالة هذا العمود.', + 'Do you really want to remove this column: "%s"?' => 'هل تريد فعلاً إزالة هذا العمود: "%s"؟', + 'Settings' => 'الإعدادات', + 'Application settings' => 'إعدادات التطبيق', + 'Language' => 'اللغة', + 'Webhook token:' => 'رمز Webhook:', + 'API token:' => 'رمز الواجهة البرمجية API:', + 'Database size:' => 'حجم قاعدة البيانات:', + 'Download the database' => 'تنزيل قاعدة البيانات', + 'Optimize the database' => 'تحسين قاعدة البيانات', + '(VACUUM command)' => '(أمر VACUUM)', + '(Gzip compressed Sqlite file)' => '(ملف Sqlite مضغوط بـ Gzip)', + 'Close a task' => 'إغلاق مهمة', + 'Column' => 'العمود', + 'Color' => 'اللون', + 'Assignee' => 'المكلّف', + 'Create another task' => 'إنشاء مهمة أخرى', + 'New task' => 'مهمة جديدة', + 'Open a task' => 'فتح مهمة', + 'Do you really want to open this task: "%s"?' => 'هل تريد فعلاً فتح هذه المهمة: "%s"؟', + 'Back to the board' => 'العودة إلى اللوحة', + 'There is nobody assigned' => 'لا يوجد أي شخص معيّن', + 'Column on the board:' => 'العمود على اللوحة:', + 'Close this task' => 'إغلاق هذه المهمة', + 'Open this task' => 'فتح هذه المهمة', + 'There is no description.' => 'لا يوجد وصف.', + 'Add a new task' => 'إضافة مهمة جديدة', + 'The username is required' => 'اسم المستخدم مطلوب', + 'The maximum length is %d characters' => 'الحد الأقصى للطول %d حرفًا', + 'The minimum length is %d characters' => 'الحد الأدنى للطول %d أحرف', + 'The password is required' => 'كلمة المرور مطلوبة', + 'This value must be an integer' => 'يجب أن تكون هذه القيمة عددًا صحيحًا', + 'The username must be unique' => 'يجب أن يكون اسم المستخدم فريدًا', + 'The user id is required' => 'معرّف المستخدم مطلوب', + 'Passwords don\'t match' => 'كلمتا المرور غير متطابقتين', + 'The confirmation is required' => 'التأكيد مطلوب', + 'The project is required' => 'المشروع مطلوب', + 'The id is required' => 'المعرّف مطلوب', + 'The project id is required' => 'معرّف المشروع مطلوب', + 'The project name is required' => 'اسم المشروع مطلوب', + 'The title is required' => 'العنوان مطلوب', + 'Settings saved successfully.' => 'تم حفظ الإعدادات بنجاح.', + 'Unable to save your settings.' => 'تعذّر حفظ إعداداتك.', + 'Database optimization done.' => 'تم تحسين قاعدة البيانات.', + 'Your project has been created successfully.' => 'تم إنشاء مشروعك بنجاح.', + 'Unable to create your project.' => 'تعذّر إنشاء مشروعك.', + 'Project updated successfully.' => 'تم تحديث المشروع بنجاح.', + 'Unable to update this project.' => 'تعذّر تحديث هذا المشروع.', + 'Unable to remove this project.' => 'تعذّر إزالة هذا المشروع.', + 'Project removed successfully.' => 'تمت إزالة المشروع بنجاح.', + 'Project activated successfully.' => 'تم تفعيل المشروع بنجاح.', + 'Unable to activate 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.' => 'تعذّر تحديث مهمتك.', + 'Task updated successfully.' => 'تم تحديث المهمة بنجاح.', + 'Unable to create your task.' => 'تعذّر إنشاء مهمتك.', + 'Task created successfully.' => 'تم إنشاء المهمة بنجاح.', + 'User created successfully.' => 'تم إنشاء المستخدم بنجاح.', + 'Unable to create your user.' => 'تعذّر إنشاء المستخدم.', + 'User updated successfully.' => 'تم تحديث المستخدم بنجاح.', + 'User removed successfully.' => 'تمت إزالة المستخدم بنجاح.', + 'Unable to remove this user.' => 'تعذّر إزالة هذا المستخدم.', + 'Board updated successfully.' => 'تم تحديث اللوحة بنجاح.', + 'Ready' => 'جاهزة', + 'Backlog' => 'المتراكم', + 'Work in progress' => 'العمل جارٍ', + 'Done' => 'منجز', + 'Application version:' => 'إصدار التطبيق:', + 'Id' => 'المعرّف', + 'Public link' => 'رابط عام', + 'Timezone' => 'المنطقة الزمنية', + 'Sorry, I didn\'t find this information in my database!' => 'عذرًا، لم أجد هذه المعلومات في قاعدة البيانات!', + 'Page not found' => 'الصفحة غير موجودة', + 'Complexity' => 'التعقيد', + 'Task limit' => 'حد المهام', + 'Task count' => 'عدد المهام', + 'User' => 'المستخدم', + 'Comments' => 'التعليقات', + 'Comment is required' => 'التعليق مطلوب', + 'Comment added successfully.' => 'تمت إضافة التعليق بنجاح.', + 'Unable to create your comment.' => 'تعذّر إنشاء تعليقك.', + 'Due Date' => 'تاريخ الاستحقاق', + 'Invalid date' => 'تاريخ غير صالح', + 'Automatic actions' => 'إجراءات تلقائية', + 'Your automatic action has been created successfully.' => 'تم إنشاء الإجراء التلقائي بنجاح.', + 'Unable to create your automatic action.' => 'تعذّر إنشاء الإجراء التلقائي.', + 'Remove an action' => 'إزالة إجراء', + 'Unable to remove this action.' => 'تعذّر إزالة هذا الإجراء.', + 'Action removed successfully.' => 'تمت إزالة الإجراء بنجاح.', + 'Automatic actions for the project "%s"' => 'الإجراءات التلقائية للمشروع "%s"', + 'Add an action' => 'إضافة إجراء', + 'Event name' => 'اسم الحدث', + 'Action' => 'الإجراء', + 'Event' => 'الحدث', + 'When the selected event occurs execute the corresponding action.' => 'عند حدوث الحدث المحدّد تُنفَّذ الإجراء الموافق.', + 'Next step' => 'الخطوة التالية', + 'Define action parameters' => 'تحديد معاملات الإجراء', + 'Do you really want to remove this action: "%s"?' => 'هل تريد فعلاً إزالة هذا الإجراء: "%s"؟', + 'Remove an automatic action' => 'إزالة إجراء تلقائي', + 'Assign the task to a specific user' => 'إسناد المهمة إلى مستخدم محدد', + 'Assign the task to the person who does the action' => 'إسناد المهمة إلى الشخص الذي نفّذ الإجراء', + 'Duplicate the task to another project' => 'استنساخ المهمة إلى مشروع آخر', + 'Move a task to another column' => 'نقل مهمة إلى عمود آخر', + 'Task modification' => 'تعديل مهمة', + 'Task creation' => 'إنشاء مهمة', + 'Closing a task' => 'إغلاق مهمة', + 'Assign a color to a specific user' => 'تعيين لون لمستخدم محدد', + 'Position' => 'الموضع', + 'Duplicate to project' => 'استنساخ إلى مشروع', + 'Duplicate' => 'استنساخ', + 'Link' => 'رابط', + 'Comment updated successfully.' => 'تم تحديث التعليق بنجاح.', + 'Unable to update your comment.' => 'تعذّر تحديث تعليقك.', + 'Remove a comment' => 'إزالة تعليق', + 'Comment removed successfully.' => 'تمت إزالة التعليق بنجاح.', + 'Unable to remove this comment.' => 'تعذّر إزالة هذا التعليق.', + 'Do you really want to remove this comment?' => 'هل تريد فعلاً إزالة هذا التعليق؟', + 'Current password for the user "%s"' => 'كلمة المرور الحالية للمستخدم "%s"', + 'The current password is required' => 'كلمة المرور الحالية مطلوبة', + 'Wrong password' => 'كلمة مرور غير صحيحة', + 'Unknown' => 'غير معروف', + 'Last logins' => 'آخر عمليات تسجيل الدخول', + 'Login date' => 'تاريخ تسجيل الدخول', + 'Authentication method' => 'طريقة المصادقة', + 'IP address' => 'عنوان IP', + 'User agent' => 'وكيل المستخدم', + 'Persistent connections' => 'جلسات دائمة', + 'No session.' => 'لا توجد جلسة.', + 'Expiration date' => 'تاريخ الانتهاء', + 'Remember Me' => 'تذكرني', + 'Creation date' => 'تاريخ الإنشاء', + 'Everybody' => 'الجميع', + 'Open' => 'مفتوح', + 'Closed' => 'مغلق', + 'Search' => 'بحث', + 'Nothing found.' => 'لم يتم العثور على شيء.', + 'Due date' => 'تاريخ الاستحقاق', + 'Description' => 'الوصف', + '%d comments' => '%d تعليقات', + '%d comment' => '%d تعليق', + 'Email address invalid' => 'عنوان البريد الإلكتروني غير صالح', + 'Your external account is not linked anymore to your profile.' => 'لم يعد حسابك الخارجي مرتبطًا بملفك.', + 'Unable to unlink your external account.' => 'تعذّر إلغاء ربط حسابك الخارجي.', + 'External authentication failed' => 'فشلت المصادقة الخارجية', + 'Your external account is linked to your profile successfully.' => 'تم ربط حسابك الخارجي بملفك بنجاح.', + 'Email' => 'البريد الإلكتروني', + 'Task removed successfully.' => 'تمت إزالة المهمة بنجاح.', + 'Unable to remove this task.' => 'تعذّر إزالة هذه المهمة.', + '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' => 'تعيين فئة تلقائيًا بناءً على اللون', + 'Task creation or modification' => 'إنشاء أو تعديل مهمة', + 'Category' => 'الفئة', + 'Category:' => 'الفئة:', + 'Categories' => 'الفئات', + 'Your category has been created successfully.' => 'تم إنشاء الفئة بنجاح.', + 'This category has been updated successfully.' => 'تم تحديث هذه الفئة بنجاح.', + 'Unable to update this category.' => 'تعذّر تحديث هذه الفئة.', + 'Remove a category' => 'إزالة فئة', + 'Category removed successfully.' => 'تمت إزالة الفئة بنجاح.', + 'Unable to remove this category.' => 'تعذّر إزالة هذه الفئة.', + 'Category modification for the project "%s"' => 'تعديل الفئة للمشروع "%s"', + 'Category Name' => 'اسم الفئة', + 'Add a new category' => 'إضافة فئة جديدة', + 'Do you really want to remove this category: "%s"?' => 'هل تريد فعلاً إزالة هذه الفئة: "%s"؟', + 'All categories' => 'جميع الفئات', + 'No category' => 'بلا فئة', + 'The name is required' => 'الاسم مطلوب', + 'Remove a file' => 'إزالة ملف', + 'Unable to remove this file.' => 'تعذّر إزالة هذا الملف.', + 'File removed successfully.' => 'تمت إزالة الملف بنجاح.', + 'Attach a document' => 'إرفاق مستند', + 'Do you really want to remove this file: "%s"?' => 'هل تريد فعلاً إزالة هذا الملف: "%s"؟', + 'Attachments' => 'المرفقات', + 'Edit the task' => 'تعديل المهمة', + 'Add a comment' => 'إضافة تعليق', + 'Edit a comment' => 'تعديل تعليق', + 'Summary' => 'ملخص', + 'Time tracking' => 'تتبع الوقت', + 'Estimate:' => 'التقدير:', + 'Spent:' => 'المستغرق:', + 'Do you really want to remove this sub-task?' => 'هل تريد فعلاً إزالة هذه المهمة الفرعية؟', + 'Remaining:' => 'المتبقي:', + 'hours' => 'ساعات', + 'estimated' => 'مقدّر', + 'Sub-Tasks' => 'المهام الفرعية', + 'Add a sub-task' => 'إضافة مهمة فرعية', + 'Original estimate' => 'التقدير الأصلي', + 'Create another sub-task' => 'إنشاء مهمة فرعية أخرى', + 'Time spent' => 'الوقت المستغرق', + 'Edit a sub-task' => 'تعديل مهمة فرعية', + 'Remove a sub-task' => 'إزالة مهمة فرعية', + 'The time must be a numeric value' => 'يجب أن يكون الوقت قيمة رقمية', + 'Todo' => 'للإنجاز', + 'In progress' => 'قيد التنفيذ', + 'Sub-task removed successfully.' => 'تمت إزالة المهمة الفرعية بنجاح.', + 'Unable to remove this sub-task.' => 'تعذّر إزالة هذه المهمة الفرعية.', + 'Sub-task updated successfully.' => 'تم تحديث المهمة الفرعية بنجاح.', + 'Unable to update your sub-task.' => 'تعذّر تحديث مهمتك الفرعية.', + 'Unable to create your sub-task.' => 'تعذّر إنشاء مهمتك الفرعية.', + 'Maximum size: ' => 'الحجم الأقصى: ', + 'Display another project' => 'عرض مشروع آخر', + 'Created by %s' => 'أنشأه %s', + 'Tasks Export' => 'تصدير المهام', + 'Start Date' => 'تاريخ البدء', + 'Execute' => 'تنفيذ', + 'Task Id' => 'معرّف المهمة', + 'Creator' => 'المنشئ', + 'Modification date' => 'تاريخ التعديل', + 'Completion date' => 'تاريخ الإكمال', + 'Clone' => 'استنساخ', + 'Project cloned successfully.' => 'تم استنساخ المشروع بنجاح.', + 'Unable to clone this project.' => 'تعذّر استنساخ هذا المشروع.', + 'Enable email notifications' => 'تمكين إشعارات البريد الإلكتروني', + 'Task position:' => 'موضع المهمة:', + 'The task #%d has been opened.' => 'تم فتح المهمة #%d.', + 'The task #%d has been closed.' => 'تم إغلاق المهمة #%d.', + 'Sub-task updated' => 'تم تحديث المهمة الفرعية', + 'Title:' => 'العنوان:', + 'Status:' => 'الحالة:', + 'Assignee:' => 'المكلّف:', + 'Time tracking:' => 'تتبع الوقت:', + 'New sub-task' => 'مهمة فرعية جديدة', + 'New attachment added "%s"' => 'تمت إضافة مرفق جديد "%s"', + 'New comment posted by %s' => 'تم نشر تعليق جديد بواسطة %s', + 'New comment' => 'تعليق جديد', + 'Comment updated' => 'تم تحديث التعليق', + 'New subtask' => 'مهمة فرعية جديدة', + 'I only want to receive notifications for these projects:' => 'أرغب في تلقي الإشعارات لهذه المشاريع فقط:', + 'view the task on Kanboard' => 'عرض المهمة على Kanboard', + 'Public access' => 'وصول عام', + 'Disable public access' => 'تعطيل الوصول العام', + 'Enable public access' => 'تمكين الوصول العام', + 'Public access disabled' => 'تم تعطيل الوصول العام', + 'Move the task to another project' => 'نقل المهمة إلى مشروع آخر', + 'Move to project' => 'نقل إلى مشروع', + 'Do you really want to duplicate this task?' => 'هل تريد فعلاً استنساخ هذه المهمة؟', + 'Duplicate a task' => 'استنساخ مهمة', + 'External accounts' => 'حسابات خارجية', + 'Account type' => 'نوع الحساب', + 'Local' => 'محلي', + 'Remote' => 'بعيد', + 'Enabled' => 'مُمكّن', + 'Disabled' => 'معطّل', + 'Login:' => 'اسم المستخدم:', + 'Full Name:' => 'الاسم الكامل:', + 'Email:' => 'البريد الإلكتروني:', + 'Notifications:' => 'الإشعارات:', + 'Notifications' => 'الإشعارات', + 'Account type:' => 'نوع الحساب:', + 'Edit profile' => 'تعديل الملف الشخصي', + 'Change password' => 'تغيير كلمة المرور', + 'Password modification' => 'تعديل كلمة المرور', + 'External authentications' => 'مصادقات خارجية', + 'Never connected.' => 'لم يتصل مطلقًا.', + 'No external authentication enabled.' => 'لا توجد مصادقة خارجية مفعّلة.', + 'Password modified successfully.' => 'تم تعديل كلمة المرور بنجاح.', + 'Unable to change the password.' => 'تعذّر تغيير كلمة المرور.', + 'Change category' => 'تغيير الفئة', + '%s updated the task %s' => 'قام %s بتحديث المهمة %s', + '%s opened the task %s' => 'قام %s بفتح المهمة %s', + '%s moved the task %s to the position #%d in the column "%s"' => 'قام %s بنقل المهمة %s إلى الموضع #%d في العمود "%s"', + '%s moved the task %s to the column "%s"' => 'قام %s بنقل المهمة %s إلى العمود "%s"', + '%s created the task %s' => 'قام %s بإنشاء المهمة %s', + '%s closed the task %s' => 'قام %s بإغلاق المهمة %s', + '%s created a subtask for the task %s' => 'قام %s بإنشاء مهمة فرعية للمهمة %s', + '%s updated a subtask for the task %s' => 'قام %s بتحديث مهمة فرعية للمهمة %s', + 'Assigned to %s with an estimate of %s/%sh' => 'أُسنِدت إلى %s بتقدير %s/%sس', + 'Not assigned, estimate of %sh' => 'غير مُسندة، تقدير %sس', + '%s updated a comment on the task %s' => 'قام %s بتحديث تعليق على المهمة %s', + '%s commented the task %s' => 'قام %s بالتعليق على المهمة %s', + '%s\'s activity' => 'نشاط %s', + 'RSS feed' => 'تلقيم RSS', + '%s updated a comment 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', + '%s created the task #%d' => 'قام %s بإنشاء المهمة #%d', + '%s closed the task #%d' => 'قام %s بإغلاق المهمة #%d', + '%s opened the task #%d' => 'قام %s بفتح المهمة #%d', + 'Activity' => 'النشاط', + 'Default values are "%s"' => 'القيم الافتراضية هي "%s"', + 'Default columns for new projects (Comma-separated)' => 'الأعمدة الافتراضية للمشاريع الجديدة (مفصولة بفواصل)', + 'Task assignee change' => 'تغيير المكلّف بالمهمة', + '%s changed the assignee of the task #%d to %s' => 'قام %s بتغيير المكلّف بالمهمة #%d إلى %s', + '%s changed the assignee of the task %s to %s' => 'قام %s بتغيير المكلّف بالمهمة %s إلى %s', + 'New password for the user "%s"' => 'كلمة مرور جديدة للمستخدم "%s"', + 'Choose an event' => 'اختر حدثًا', + 'Create a task from an external provider' => 'إنشاء مهمة من مزوّد خارجي', + 'Change the assignee based on an external username' => 'تغيير المكلّف بناءً على اسم مستخدم خارجي', + 'Change the category based on an external label' => 'تغيير الفئة بناءً على وسم خارجي', + 'Reference' => 'مرجع', + 'Label' => 'وسم', + 'Database' => 'قاعدة البيانات', + 'About' => 'حول', + 'Database driver:' => 'مشغّل قاعدة البيانات:', + 'Board settings' => 'إعدادات اللوحة', + 'Webhook settings' => 'إعدادات Webhook', + 'Reset token' => 'إعادة ضبط الرمز', + 'API endpoint:' => 'عنوان واجهة API:', + 'Refresh interval for personal board' => 'فترة التحديث للوحة الشخصية', + 'Refresh interval for public board' => 'فترة التحديث للوحة العامة', + 'Task highlight period' => 'مدة تمييز المهمة', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'المدة (بالثواني) لاعتبار أن المهمة عُدِّلت مؤخرًا (0 للتعطيل، افتراضيًا يومان)', + 'Frequency in second (60 seconds by default)' => 'التردد بالثواني (افتراضيًا 60 ثانية)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'التردد بالثواني (0 لتعطيل هذه الميزة، افتراضيًا 10 ثوانٍ)', + 'Application URL' => 'رابط التطبيق', + 'Token regenerated.' => 'تمت إعادة توليد الرمز.', + 'Date format' => 'تنسيق التاريخ', + 'ISO format is always accepted, example: "%s" and "%s"' => 'تنسيق ISO مقبول دائمًا، مثال: "%s" و "%s"', + 'New personal project' => 'مشروع شخصي جديد', + 'This project is personal' => 'هذا المشروع شخصي', + 'Add' => 'إضافة', + 'Start date' => 'تاريخ البدء', + 'Time estimated' => 'الوقت المقدّر', + 'There is nothing assigned to you.' => 'لا شيء مُسند إليك.', + 'My tasks' => 'مهامي', + 'Activity stream' => 'سجل النشاط', + 'Dashboard' => 'لوحة المعلومات', + 'Confirmation' => 'تأكيد', + 'Webhooks' => 'خطافات الويب', + 'API' => 'واجهة برمجية (API)', + 'Create a comment from an external provider' => 'إنشاء تعليق من مزوّد خارجي', + 'Project management' => 'إدارة المشروع', + 'Columns' => 'الأعمدة', + 'Task' => 'مهمة', + 'Percentage' => 'النسبة المئوية', + 'Number of tasks' => 'عدد المهام', + 'Task distribution' => 'توزيع المهام', + 'Analytics' => 'التحليلات', + 'Subtask' => 'مهمة فرعية', + 'User repartition' => 'توزيع المستخدمين', + 'Clone this project' => 'استنساخ هذا المشروع', + 'Column removed successfully.' => 'تم إزالة العمود بنجاح.', + 'Not enough data to show the graph.' => 'لا توجد بيانات كافية لعرض الرسم.', + 'Previous' => 'السابق', + 'The id must be an integer' => 'يجب أن يكون المعرّف عددًا صحيحًا', + 'The project id must be an integer' => 'يجب أن يكون معرّف المشروع عددًا صحيحًا', + 'The status must be an integer' => 'يجب أن تكون الحالة عددًا صحيحًا', + 'The subtask id is required' => 'معرّف المهمة الفرعية مطلوب', + 'The subtask id must be an integer' => 'يجب أن يكون معرّف المهمة الفرعية عددًا صحيحًا', + 'The task id is required' => 'معرّف المهمة مطلوب', + 'The task id must be an integer' => 'يجب أن يكون معرّف المهمة عددًا صحيحًا', + 'The user id must be an integer' => 'يجب أن يكون معرّف المستخدم عددًا صحيحًا', + 'This value is required' => 'هذه القيمة مطلوبة', + 'This value must be numeric' => 'يجب أن تكون هذه القيمة رقمية', + 'Unable to create this task.' => 'تعذّر إنشاء هذه المهمة.', + 'Cumulative flow diagram' => 'مخطط التدفق التراكمي', + 'Daily project summary' => 'ملخص المشروع اليومي', + 'Daily project summary export' => 'تصدير الملخص اليومي للمشروع', + 'Exports' => 'عمليات التصدير', + 'This export contains the number of tasks per column grouped per day.' => 'يحتوي هذا التصدير على عدد المهام لكل عمود، مجمعة حسب اليوم.', + 'Active swimlanes' => 'مسارات السباحة النشطة', + 'Add a new swimlane' => 'إضافة مسار سباحة جديد', + 'Default swimlane' => 'مسار السباحة الافتراضي', + 'Do you really want to remove this swimlane: "%s"?' => 'هل تريد فعلاً إزالة مسار السباحة هذا: "%s"؟', + 'Inactive swimlanes' => 'مسارات السباحة غير النشطة', + 'Remove a swimlane' => 'إزالة مسار سباحة', + 'Swimlane modification for the project "%s"' => 'تعديل مسار السباحة للمشروع "%s"', + 'Swimlane removed successfully.' => 'تم إزالة مسار السباحة بنجاح.', + 'Swimlanes' => 'مسارات السباحة', + 'Swimlane updated successfully.' => 'تم تحديث مسار السباحة بنجاح.', + 'Unable to remove this swimlane.' => 'تعذّر إزالة مسار السباحة هذا.', + 'Unable to update this swimlane.' => 'تعذّر تحديث مسار السباحة هذا.', + 'Your swimlane has been created successfully.' => 'تم إنشاء مسار السباحة بنجاح.', + 'Example: "Bug, Feature Request, Improvement"' => 'مثال: "عطل، طلب ميزة، تحسين"', + 'Default categories for new projects (Comma-separated)' => 'الفئات الافتراضية للمشاريع الجديدة (مفصولة بفواصل)', + 'Integrations' => 'تكاملات', + 'Integration with third-party services' => 'التكامل مع خدمات طرف ثالث', + 'Subtask Id' => 'معرّف المهمة الفرعية', + 'Subtasks' => 'المهام الفرعية', + 'Subtasks Export' => 'تصدير المهام الفرعية', + 'Task Title' => 'عنوان المهمة', + 'Untitled' => 'بدون عنوان', + 'Application default' => 'افتراضي التطبيق', + 'Language:' => 'اللغة:', + 'Timezone:' => 'المنطقة الزمنية:', + 'All columns' => 'كل الأعمدة', + 'Next' => 'التالي', + '#%d' => '#%d', + 'All swimlanes' => 'كل مسارات السباحة', + 'All colors' => 'جميع الألوان', + 'Moved to column %s' => 'نُقلت إلى العمود %s', + 'User dashboard' => 'لوحة المستخدم', + 'Allow only one subtask in progress at the same time for a user' => 'السماح بمهمة فرعية واحدة قيد التنفيذ في نفس الوقت لكل مستخدم', + 'Edit column "%s"' => 'تعديل العمود "%s"', + '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?' => 'أي أجزاء من المشروع تريد استنساخها؟', + 'Disallow login form' => 'منع نموذج تسجيل الدخول', + 'Start' => 'البدء', + 'End' => 'الانتهاء', + 'Task age in days' => 'عمر المهمة بالأيام', + 'Days in this column' => 'الأيام في هذا العمود', + '%dd' => '%dd', + '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' => 'تعديل الرابط', + 'Opposite label' => 'التسمية المقابلة', + 'Remove a link' => 'إزالة رابط', + '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' => '<1س', + '%dh' => '%dس', + 'Expand tasks' => 'توسيع المهام', + 'Collapse tasks' => 'طيّ المهام', + 'Expand/collapse tasks' => 'توسيع/طيّ المهام', + 'Close dialog box' => 'إغلاق نافذة الحوار', + 'Submit a form' => 'إرسال نموذج', + 'Board view' => 'عرض اللوحة', + 'Keyboard shortcuts' => 'اختصارات لوحة المفاتيح', + 'Open board switcher' => 'فتح مبدّل اللوحات', + 'Application' => 'التطبيق', + 'Compact view' => 'عرض مضغوط', + 'Horizontal scrolling' => 'تمرير أفقي', + 'Compact/wide view' => 'عرض مضغوط/واسع', + 'Currency' => 'العملة', + 'Personal project' => 'مشروع شخصي', + 'AUD - Australian Dollar' => 'AUD - دولار أسترالي', + 'CAD - Canadian Dollar' => 'CAD - دولار كندي', + 'CHF - Swiss Francs' => 'CHF - فرنك سويسري', + 'Custom Stylesheet' => 'ورقة أنماط مخصصة', + 'EUR - Euro' => 'EUR - يورو', + 'GBP - British Pound' => 'GBP - جنيه إسترليني', + 'INR - Indian Rupee' => 'INR - روبية هندية', + 'JPY - Japanese Yen' => 'JPY - ين ياباني', + 'NZD - New Zealand Dollar' => 'NZD - دولار نيوزيلندي', + 'PEN - Peruvian Sol' => 'PEN - سول بيروفي', + 'RSD - Serbian dinar' => 'RSD - دينار صربي', + 'CNY - Chinese Yuan' => 'CNY - يوان صيني', + 'USD - US Dollar' => 'USD - دولار أمريكي', + 'VES - Venezuelan Bolívar' => 'VES - بوليفار فنزويلي', + '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' => 'عمود المصدر', + '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' => 'تغيير عملة المرجع', + 'Reference currency' => 'عملة المرجع', + 'The currency rate has been added successfully.' => 'تمت إضافة سعر الصرف بنجاح.', + 'Unable to add this currency rate.' => 'تعذّر إضافة سعر الصرف هذا.', + 'Webhook URL' => 'عنوان URL للـ Webhook', + '%s removed the assignee of the task %s' => 'قام %s بإزالة المكلّف بالمهمة %s', + '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' => 'المصادقة الثنائية', + 'This QR code contains the key URI: ' => 'يحتوي رمز QR هذا على مفتاح URI: ', + 'Check my code' => 'تحقق من الرمز', + 'Secret key: ' => 'المفتاح السري: ', + 'Test your device' => 'اختبر جهازك', + 'Assign a color when the task is moved to a specific column' => 'تعيين لون عند نقل المهمة إلى عمود محدد', + '%s via Kanboard' => '%s عبر Kanboard', + 'Burndown chart' => 'مخطط الاحتراق', + 'This chart show the task complexity over the time (Work Remaining).' => 'يعرض هذا المخطط تعقيد المهمة بمرور الوقت (العمل المتبقي).', + 'Screenshot taken %s' => 'تم التقاط لقطة شاشة %s', + 'Add a screenshot' => 'إضافة لقطة شاشة', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'التقط لقطة شاشة ثم اضغط CTRL+V أو ⌘+V للصقها هنا.', + 'Screenshot uploaded successfully.' => 'تم رفع لقطة الشاشة بنجاح.', + 'SEK - Swedish Krona' => 'SEK - كرونا سويدية', + 'Identifier' => 'المعرّف', + 'Disable two factor authentication' => 'تعطيل المصادقة الثنائية', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'هل تريد فعلاً تعطيل المصادقة الثنائية لهذا المستخدم: "%s"؟', + 'Edit link' => 'تعديل الرابط', + 'Start to type task title...' => 'ابدأ بكتابة عنوان المهمة...', + 'A task cannot be linked to itself' => 'لا يمكن ربط المهمة بنفسها', + 'The exact same link already exists' => 'يوجد نفس الرابط تمامًا مسبقًا', + 'Recurrent task is scheduled to be generated' => 'تمت جدولة إنشاء مهمة متكررة', + 'Score' => 'الدرجة', + 'The identifier must be unique' => 'يجب أن يكون المعرّف فريدًا', + 'This linked task id doesn\'t exists' => 'معرّف المهمة المرتبطة غير موجود', + 'This value must be alphanumeric' => 'يجب أن تكون هذه القيمة أبجدية رقمية', + 'Edit recurrence' => 'تعديل التكرار', + 'Generate recurrent task' => 'إنشاء مهمة متكررة', + 'Trigger to generate recurrent task' => 'مشغّل إنشاء مهمة متكررة', + 'Factor to calculate new due date' => 'عامل احتساب تاريخ الاستحقاق الجديد', + 'Timeframe to calculate new due date' => 'الإطار الزمني لاحتساب تاريخ الاستحقاق الجديد', + 'Base date to calculate new due date' => 'التاريخ الأساس لاحتساب تاريخ الاستحقاق الجديد', + 'Action date' => 'تاريخ الإجراء', + 'Base date to calculate new due date: ' => 'التاريخ الأساس لاحتساب تاريخ الاستحقاق الجديد: ', + 'This task has created this child task: ' => 'أنشأت هذه المهمة المهمة التابعة: ', + 'Day(s)' => 'يوم/أيام', + 'Existing due date' => 'تاريخ الاستحقاق الحالي', + 'Factor to calculate new due date: ' => 'عامل احتساب تاريخ الاستحقاق الجديد: ', + 'Month(s)' => 'شهر/أشهر', + 'This task has been created by: ' => 'تم إنشاء هذه المهمة بواسطة: ', + 'Recurrent task has been generated:' => 'تم إنشاء مهمة متكررة:', + 'Timeframe to calculate new due date: ' => 'الإطار الزمني لاحتساب تاريخ الاستحقاق الجديد: ', + 'Trigger to generate recurrent task: ' => 'مشغّل إنشاء مهمة متكررة: ', + 'When task is closed' => 'عند إغلاق المهمة', + 'When task is moved from first column' => 'عند نقل المهمة من العمود الأول', + 'When task is moved to last column' => 'عند نقل المهمة إلى العمود الأخير', + 'Year(s)' => 'سنة/سنوات', + 'Project settings' => 'إعدادات المشروع', + 'Automatically update the start date' => 'تحديث تاريخ البدء تلقائيًا', + 'iCal feed' => 'خلاصة iCal', + 'Preferences' => 'تفضيلات', + 'Security' => 'أمان', + 'Two factor authentication disabled' => 'تم تعطيل المصادقة الثنائية', + 'Two factor authentication enabled' => 'تم تمكين المصادقة الثنائية', + 'Unable to update this user.' => 'تعذّر تحديث هذا المستخدم.', + 'There is no user management for personal projects.' => 'لا توجد إدارة مستخدمين للمشاريع الشخصية.', + 'User that will receive the email' => 'المستخدم الذي سيستلم البريد الإلكتروني', + 'Email subject' => 'موضوع البريد الإلكتروني', + 'Date' => 'التاريخ', + 'Add a comment log when moving the task between columns' => 'إضافة سجل تعليق عند نقل المهمة بين الأعمدة', + 'Move the task to another column when the category is changed' => 'نقل المهمة إلى عمود آخر عند تغيير الفئة', + 'Send a task by email to someone' => 'إرسال مهمة عبر البريد الإلكتروني لشخص ما', + 'Reopen a task' => 'إعادة فتح مهمة', + 'Notification' => 'إشعار', + '%s moved the task #%d to the first swimlane' => 'قام %s بنقل المهمة #%d إلى مسار السباحة الأول', + 'Swimlane' => 'مسار السباحة', + '%s moved the task %s to the first swimlane' => 'قام %s بنقل المهمة %s إلى مسار السباحة الأول', + '%s moved the task %s to the swimlane "%s"' => 'قام %s بنقل المهمة %s إلى مسار السباحة "%s"', + 'This report contains all subtasks information for the given date range.' => 'يحتوي هذا التقرير على معلومات جميع المهام الفرعية للنطاق الزمني المحدد.', + 'This report contains all tasks information for the given date range.' => 'يحتوي هذا التقرير على معلومات جميع المهام للنطاق الزمني المحدد.', + 'Project activities for %s' => 'أنشطة المشروع لـ %s', + 'view the board on Kanboard' => 'عرض اللوحة على Kanboard', + 'The task has been moved to the first swimlane' => 'تم نقل المهمة إلى مسار السباحة الأول', + 'The task has been moved to another swimlane:' => 'تم نقل المهمة إلى مسار سباحة آخر:', + 'New title: %s' => 'عنوان جديد: %s', + 'The task is not assigned anymore' => 'لم تعد المهمة مُسندة', + 'New assignee: %s' => 'المكلّف الجديد: %s', + 'There is no category now' => 'لا توجد فئة الآن', + 'New category: %s' => 'الفئة الجديدة: %s', + 'New color: %s' => 'اللون الجديد: %s', + 'New complexity: %d' => 'التعقيد الجديد: %d', + 'The due date has been removed' => 'تمت إزالة تاريخ الاستحقاق', + 'There is no description anymore' => 'لم يعد هناك وصف', + 'Recurrence settings has been modified' => 'تم تعديل إعدادات التكرار', + 'Time spent changed: %sh' => 'تم تغيير الوقت المستغرق: %sس', + 'Time estimated changed: %sh' => 'تم تغيير الوقت المقدّر: %sس', + 'The field "%s" has been updated' => 'تم تحديث الحقل "%s"', + 'The description has been modified:' => 'تم تعديل الوصف:', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'هل تريد فعلاً إغلاق المهمة "%s" وجميع مهامها الفرعية؟', + 'I want to receive notifications for:' => 'أريد تلقي إشعارات بشأن:', + 'All tasks' => 'كل المهام', + 'Only for tasks assigned to me' => 'فقط للمهام المسندة إليّ', + 'Only for tasks created by me' => 'فقط للمهام التي أنشأتها', + 'Only for tasks created by me and tasks assigned to me' => 'فقط للمهام التي أنشأتها والمهام المسندة إليّ', + '%%Y-%%m-%%d' => '%%Y-%%m-%%d', + 'Total for all columns' => 'الإجمالي لجميع الأعمدة', + 'You need at least 2 days of data to show the chart.' => 'تحتاج إلى ما لا يقل عن يومين من البيانات لعرض المخطط.', + '<15m' => '<15د', + '<30m' => '<30د', + 'Stop timer' => 'إيقاف المؤقت', + 'Start timer' => 'بدء المؤقت', + 'My activity stream' => 'سجل نشاطي', + 'Search tasks' => 'البحث عن مهام', + 'Reset filters' => 'إعادة تعيين المرشّحات', + 'My tasks due tomorrow' => 'مهامي المستحقة غدًا', + 'Tasks due today' => 'المهام المستحقة اليوم', + 'Tasks due tomorrow' => 'المهام المستحقة غدًا', + 'Tasks due yesterday' => 'المهام المستحقة أمس', + 'Closed tasks' => 'المهام المغلقة', + 'Open tasks' => 'المهام المفتوحة', + 'Not assigned' => 'غير مُسندة', + 'View advanced search syntax' => 'عرض صيغة البحث المتقدم', + 'Overview' => 'نظرة عامة', + 'Board/Calendar/List view' => 'عرض لوحة/تقويم/قائمة', + 'Switch to the board view' => 'التبديل إلى عرض اللوحة', + 'Switch to the list view' => 'التبديل إلى عرض القائمة', + 'Go to the search/filter box' => 'الانتقال إلى مربع البحث/الترشيح', + 'There is no activity yet.' => 'لا يوجد نشاط حتى الآن.', + 'No tasks found.' => 'لم يتم العثور على مهام.', + 'Keyboard shortcut: "%s"' => 'اختصار لوحة المفاتيح: "%s"', + 'List' => 'قائمة', + 'Filter' => 'مرشّح', + 'Advanced search' => 'بحث متقدم', + 'Example of query: ' => 'مثال على الاستعلام: ', + 'Search by project: ' => 'بحث حسب المشروع: ', + 'Search by column: ' => 'بحث حسب العمود: ', + 'Search by assignee: ' => 'بحث حسب المكلّف: ', + 'Search by color: ' => 'بحث حسب اللون: ', + 'Search by category: ' => 'بحث حسب الفئة: ', + 'Search by description: ' => 'بحث حسب الوصف: ', + 'Search by due date: ' => 'بحث حسب تاريخ الاستحقاق: ', + 'Average time spent in each column' => 'متوسط الوقت المستغرق في كل عمود', + 'Average time spent' => 'متوسط الوقت المستغرق', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'يعرض هذا المخطط متوسط الوقت المستغرق في كل عمود لآخر %d مهمة.', + 'Average Lead and Cycle time' => 'متوسط زمن القيادة والدورة', + 'Average lead time: ' => 'متوسط زمن القيادة: ', + 'Average cycle time: ' => 'متوسط زمن الدورة: ', + 'Cycle Time' => 'زمن الدورة', + 'Lead Time' => 'زمن القيادة', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'يعرض هذا المخطط متوسط زمن القيادة والدورة لآخر %d مهمة عبر الزمن.', + 'Average time into each column' => 'متوسط الوقت في كل عمود', + 'Lead and cycle time' => 'زمن القيادة والدورة', + 'Lead time: ' => 'زمن القيادة: ', + 'Cycle time: ' => 'زمن الدورة: ', + 'Time spent in each column' => 'الوقت المستغرق في كل عمود', + 'The lead time is the duration between the task creation and the completion.' => 'زمن القيادة هو المدة بين إنشاء المهمة وإكمالها.', + 'The cycle time is the duration between the start date and the completion.' => 'زمن الدورة هو المدة بين تاريخ البدء والإكمال.', + 'If the task is not closed the current time is used instead of the completion date.' => 'إذا لم تُغلق المهمة يُستخدم الوقت الحالي بدلًا من تاريخ الإكمال.', + 'Set the start date automatically' => 'تعيين تاريخ البدء تلقائيًا', + 'Edit Authentication' => 'تحرير المصادقة', + 'Remote user' => 'مستخدم بعيد', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'لا يخزن المستخدمون البعيدون كلمات المرور في قاعدة بيانات Kanboard، أمثلة: حسابات LDAP وGoogle وGithub.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'إذا حدّدت خيار "منع نموذج تسجيل الدخول"، سيتم تجاهل بيانات الاعتماد المُدخلة في نموذج تسجيل الدخول.', + 'Default task color' => 'لون المهمة الافتراضي', + 'This feature does not work with all browsers.' => 'هذه الميزة لا تعمل مع جميع المتصفحات.', + 'There is no destination project available.' => 'لا يوجد مشروع وجهة متاح.', + 'Trigger automatically subtask time tracking' => 'تشغيل تتبّع وقت المهمة الفرعية تلقائيًا', + 'Include closed tasks in the cumulative flow diagram' => 'تضمين المهام المغلقة في مخطط التدفق التراكمي', + 'Current swimlane: %s' => 'مسار السباحة الحالي: %s', + 'Current column: %s' => 'العمود الحالي: %s', + 'Current category: %s' => 'الفئة الحالية: %s', + 'no category' => 'بدون فئة', + 'Current assignee: %s' => 'المكلّف الحالي: %s', + 'not assigned' => 'غير مُسند', + 'Author:' => 'المؤلف:', + 'contributors' => 'المساهمون', + 'License:' => 'الترخيص:', + 'License' => 'الترخيص', + 'Enter the text below' => 'أدخل النص أدناه', + 'Start date:' => 'تاريخ البدء:', + 'Due date:' => 'تاريخ الاستحقاق:', + 'People who are project managers' => 'الأشخاص الذين هم مدراء المشروع', + 'People who are project members' => 'الأشخاص الذين هم أعضاء المشروع', + 'NOK - Norwegian Krone' => 'NOK - كرونة نرويجية', + 'Show this column' => 'إظهار هذا العمود', + 'Hide this column' => 'إخفاء هذا العمود', + 'End date' => 'تاريخ الانتهاء', + 'Users overview' => 'نظرة عامة على المستخدمين', + 'Members' => 'الأعضاء', + 'Shared project' => 'مشروع مشترك', + 'Project managers' => 'مدراء المشروع', + 'Projects list' => 'قائمة المشاريع', + 'End date:' => 'تاريخ الانتهاء:', + 'Change task color when using a specific task link' => 'تغيير لون المهمة عند استخدام رابط مهمة محدد', + 'Task link creation or modification' => 'إنشاء أو تعديل رابط مهمة', + 'Milestone' => 'معلَم', + 'Reset the search/filter box' => 'إعادة تعيين مربع البحث/الترشيح', + 'Documentation' => 'التوثيق', + 'Author' => 'المؤلف', + 'Version' => 'الإصدار', + 'Plugins' => 'الإضافات', + 'There is no plugin loaded.' => 'لا توجد أي إضافة محمّلة.', + 'My notifications' => 'إشعاراتي', + 'Custom filters' => 'مرشّحات مخصصة', + 'Your custom filter has been created successfully.' => 'تم إنشاء المرشّح المخصص بنجاح.', + 'Unable to create your custom filter.' => 'تعذّر إنشاء المرشّح المخصص.', + 'Custom filter removed successfully.' => 'تمت إزالة المرشّح المخصص بنجاح.', + 'Unable to remove this custom filter.' => 'تعذّر إزالة هذا المرشّح المخصص.', + 'Edit custom filter' => 'تعديل مرشّح مخصص', + 'Your custom filter has been updated successfully.' => 'تم تحديث المرشّح المخصص بنجاح.', + 'Unable to update custom filter.' => 'تعذّر تحديث المرشّح المخصص.', + 'Web' => 'الويب', + 'New attachment on task #%d: %s' => 'مرفق جديد على المهمة #%d: %s', + 'New comment on task #%d' => 'تعليق جديد على المهمة #%d', + 'Comment updated on task #%d' => 'تم تحديث التعليق على المهمة #%d', + 'New subtask on task #%d' => 'مهمة فرعية جديدة على المهمة #%d', + 'Subtask updated on task #%d' => 'تم تحديث المهمة الفرعية على المهمة #%d', + 'New task #%d: %s' => 'مهمة جديدة #%d: %s', + 'Task updated #%d' => 'تم تحديث المهمة #%d', + 'Task #%d closed' => 'أُغلقت المهمة #%d', + 'Task #%d opened' => 'فُتحت المهمة #%d', + 'Column changed for task #%d' => 'تم تغيير العمود للمهمة #%d', + 'New position for task #%d' => 'موضع جديد للمهمة #%d', + 'Swimlane changed for task #%d' => 'تم تغيير مسار السباحة للمهمة #%d', + 'Assignee changed on task #%d' => 'تم تغيير المكلّف بالمهمة #%d', + '%d overdue tasks' => '%d مهام متأخرة', + 'No notification.' => 'لا توجد إشعارات.', + 'Mark all as read' => 'وضع علامة مقروء على الكل', + 'Mark as read' => 'وضع علامة مقروء', + 'Total number of tasks in this column across all swimlanes' => 'إجمالي عدد المهام في هذا العمود عبر جميع مسارات السباحة', + 'Collapse swimlane' => 'طيّ مسار السباحة', + 'Expand swimlane' => 'توسيع مسار السباحة', + 'Add a new filter' => 'إضافة مرشّح جديد', + 'Share with all project members' => 'مشاركة مع جميع أعضاء المشروع', + 'Shared' => 'مشترك', + 'Owner' => 'المالك', + 'Unread notifications' => 'إشعارات غير مقروءة', + 'Notification methods:' => 'طرق الإشعارات:', + 'Unable to read your file' => 'تعذّر قراءة ملفك', + '%d task(s) have been imported successfully.' => 'تم استيراد %d مهمة بنجاح.', + 'Nothing has been imported!' => 'لم يتم استيراد أي شيء!', + 'Import users from CSV file' => 'استيراد مستخدمين من ملف CSV', + '%d user(s) have been imported successfully.' => 'تم استيراد %d مستخدمًا بنجاح.', + 'Comma' => 'فاصلة', + 'Semi-colon' => 'فاصلة منقوطة', + 'Tab' => 'علامة تبويب', + 'Vertical bar' => 'شريط عمودي', + 'Double Quote' => 'علامة اقتباس مزدوجة', + 'Single Quote' => 'علامة اقتباس مفردة', + '%s attached a file to the task #%d' => 'أرفق %s ملفًا بالمهمة #%d', + 'There is no column or swimlane activated in your project!' => 'لا يوجد عمود أو مسار سباحة مفعّل في مشروعك!', + 'Append filter (instead of replacement)' => 'إلحاق المرشّح (بدلاً من الاستبدال)', + 'Append/Replace' => 'إلحاق/استبدال', + 'Append' => 'إلحاق', + 'Replace' => 'استبدال', + 'Import' => 'استيراد', + 'Change sorting' => 'تغيير الفرز', + 'Tasks Importation' => 'استيراد المهام', + 'Delimiter' => 'الفاصل', + 'Enclosure' => 'المغلِّف', + 'CSV File' => 'ملف CSV', + 'Instructions' => 'تعليمات', + 'Your file must use the predefined CSV format' => 'يجب أن يستخدم ملفك تنسيق CSV المحدّد مسبقًا', + 'Your file must be encoded in UTF-8' => 'يجب أن يكون ملفك مُرمَّزًا بترميز UTF-8', + 'The first row must be the header' => 'يجب أن تكون الصف الأول هو الترويسة', + 'Duplicates are not verified for you' => 'لن يتم التحقق من العناصر المكررة لك', + 'The due date must use the ISO format: YYYY-MM-DD' => 'يجب أن يكون تاريخ الاستحقاق بتنسيق ISO: YYYY-MM-DD', + 'Download CSV template' => 'تنزيل قالب CSV', + 'No external integration registered.' => 'لا توجد تكاملات خارجية مسجلة.', + 'Duplicates are not imported' => 'لا يتم استيراد العناصر المكررة', + 'Usernames must be lowercase and unique' => 'يجب أن تكون أسماء المستخدمين بحروف صغيرة وفريدة', + 'Passwords will be encrypted if present' => 'سيتم تشفير كلمات المرور إن وجدت', + '%s attached a new file to the task %s' => 'أرفق %s ملفًا جديدًا بالمهمة %s', + 'Link type' => 'نوع الرابط', + 'Assign automatically a category based on a link' => 'تعيين فئة تلقائيًا استنادًا إلى رابط', + 'BAM - Konvertible Mark' => 'BAM - مارك قابل للتحويل', + 'Assignee Username' => 'اسم مستخدم المكلّف', + 'Assignee Name' => 'اسم المكلّف', + 'Groups' => 'المجموعات', + 'Members of %s' => 'أعضاء %s', + 'New group' => 'مجموعة جديدة', + 'Group created successfully.' => 'تم إنشاء المجموعة بنجاح.', + 'Unable to create your group.' => 'تعذّر إنشاء مجموعتك.', + 'Edit group' => 'تعديل المجموعة', + 'Group updated successfully.' => 'تم تحديث المجموعة بنجاح.', + 'Unable to update your group.' => 'تعذّر تحديث مجموعتك.', + 'Add group member to "%s"' => 'إضافة عضو مجموعة إلى "%s"', + 'Group member added successfully.' => 'تمت إضافة عضو المجموعة بنجاح.', + 'Unable to add group member.' => 'تعذّر إضافة عضو المجموعة.', + 'Remove user from group "%s"' => 'إزالة مستخدم من المجموعة "%s"', + 'User removed successfully from this group.' => 'تمت إزالة المستخدم من هذه المجموعة بنجاح.', + 'Unable to remove this user from the group.' => 'تعذّر إزالة هذا المستخدم من المجموعة.', + 'Remove group' => 'إزالة مجموعة', + 'Group removed successfully.' => 'تمت إزالة المجموعة بنجاح.', + 'Unable to remove this group.' => 'تعذّر إزالة هذه المجموعة.', + 'Project Permissions' => 'أذونات المشروع', + 'Manager' => 'مدير', + 'Project Manager' => 'مدير مشروع', + 'Project Member' => 'عضو مشروع', + 'Project Viewer' => 'عارض مشروع', + 'Your account is locked for %d minutes' => 'تم قفل حسابك لمدة %d دقيقة', + 'Invalid captcha' => 'رمز التحقق غير صالح', + 'The name must be unique' => 'يجب أن يكون الاسم فريدًا', + 'View all groups' => 'عرض جميع المجموعات', + 'There is no user available.' => 'لا يوجد مستخدم متاح.', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'هل تريد فعلاً إزالة المستخدم "%s" من المجموعة "%s"؟', + 'There is no group.' => 'لا توجد مجموعة.', + 'Add group member' => 'إضافة عضو مجموعة', + 'Do you really want to remove this group: "%s"?' => 'هل تريد فعلاً إزالة هذه المجموعة: "%s"؟', + 'There is no user in this group.' => 'لا يوجد مستخدم في هذه المجموعة.', + 'Permissions' => 'الأذونات', + 'Allowed Users' => 'المستخدمون المسموح لهم', + 'No specific user has been allowed.' => 'لم يُسمح بأي مستخدم محدد.', + 'Role' => 'الدور', + 'Enter user name...' => 'أدخل اسم المستخدم...', + 'Allowed Groups' => 'المجموعات المسموح لها', + 'No group has been allowed.' => 'لم يُسمح بأي مجموعة.', + 'Group' => 'المجموعة', + 'Group Name' => 'اسم المجموعة', + 'Enter group name...' => 'أدخل اسم المجموعة...', + 'Role:' => 'الدور:', + 'Project members' => 'أعضاء المشروع', + '%s mentioned you in the task #%d' => 'ذكرَك %s في المهمة #%d', + '%s mentioned you in a comment on the task #%d' => 'ذكرَك %s في تعليق على المهمة #%d', + 'You were mentioned in the task #%d' => 'تمت الإشارة إليك في المهمة #%d', + 'You were mentioned in a comment on the task #%d' => 'تمت الإشارة إليك في تعليق على المهمة #%d', + 'Estimated hours: ' => 'ساعات مقدّرة: ', + 'Actual hours: ' => 'ساعات فعلية: ', + 'Hours Spent' => 'الساعات المستغرقة', + 'Hours Estimated' => 'الساعات المقدّرة', + 'Estimated Time' => 'الوقت المقدّر', + 'Actual Time' => 'الوقت الفعلي', + 'Estimated vs actual time' => 'المقدّر مقابل الفعلي', + 'RUB - Russian Ruble' => 'RUB - روبل روسي', + 'Assign the task to the person who does the action when the column is changed' => 'إسناد المهمة إلى منفّذ الإجراء عند تغيير العمود', + 'Close a task in a specific column' => 'إغلاق مهمة في عمود محدد', + 'Time-based One-time Password Algorithm' => 'خوارزمية كلمة المرور ذات الاستخدام الواحد المبنية على الوقت', + 'Two-Factor Provider: ' => 'موفّر المصادقة الثنائية: ', + 'Disable two-factor authentication' => 'تعطيل المصادقة الثنائية', + 'Enable two-factor authentication' => 'تمكين المصادقة الثنائية', + 'There is no integration registered at the moment.' => 'لا توجد تكاملات مسجلة في الوقت الحالي.', + 'Password Reset for Kanboard' => 'إعادة تعيين كلمة المرور لـ Kanboard', + 'Forgot password?' => 'هل نسيت كلمة المرور؟', + 'Enable "Forget Password"' => 'تمكين "نسيت كلمة المرور"', + 'Password Reset' => 'إعادة تعيين كلمة المرور', + 'New password' => 'كلمة مرور جديدة', + 'Change Password' => 'تغيير كلمة المرور', + 'To reset your password click on this link:' => 'لإعادة تعيين كلمة المرور انقر على هذا الرابط:', + 'Last Password Reset' => 'آخر إعادة تعيين لكلمة المرور', + 'The password has never been reinitialized.' => 'لم تتم إعادة تعيين كلمة المرور من قبل.', + 'Creation' => 'الإنشاء', + 'Expiration' => 'الانتهاء', + 'Password reset history' => 'سجل إعادة تعيين كلمة المرور', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'تم إغلاق جميع مهام العمود "%s" ومسار السباحة "%s" بنجاح.', + 'Do you really want to close all tasks of this column?' => 'هل تريد فعلاً إغلاق جميع المهام في هذا العمود؟', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => 'سيتم إغلاق %d مهمة في العمود "%s" ومسار السباحة "%s".', + 'Close all tasks in this column and this swimlane' => 'إغلاق جميع المهام في هذا العمود ومسار السباحة هذا', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'لم تسجّل أي إضافة طريقة إشعارات للمشروع. ما زال بإمكانك ضبط الإشعارات الفردية في ملفك الشخصي.', + 'My dashboard' => 'لوحتي', + 'My profile' => 'ملفي الشخصي', + 'Project owner: ' => 'مالك المشروع: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'معرّف المشروع اختياري ويجب أن يكون أبجديًا رقميًا، مثال: MYPROJECT.', + 'Project owner' => 'مالك المشروع', + 'Personal projects do not have users and groups management.' => 'المشاريع الشخصية لا تحتوي على إدارة مستخدمين ومجموعات.', + 'There is no project member.' => 'لا يوجد عضو في المشروع.', + 'Priority' => 'الأولوية', + 'Task priority' => 'أولوية المهمة', + 'General' => 'عام', + 'Dates' => 'التواريخ', + 'Default priority' => 'الأولوية الافتراضية', + 'Lowest priority' => 'أدنى أولوية', + 'Highest priority' => 'أعلى أولوية', + 'Close a task when there is no activity' => 'إغلاق مهمة عند عدم وجود نشاط', + 'Duration in days' => 'المدة بالأيام', + 'Send email when there is no activity on a task' => 'إرسال بريد عند عدم وجود نشاط على مهمة', + 'Unable to fetch link information.' => 'تعذّر جلب معلومات الرابط.', + 'Daily background job for tasks' => 'مهمة خلفية يومية للمهام', + 'Auto' => 'تلقائي', + 'Related' => 'مرتبط', + 'Attachment' => 'مرفق', + 'Web Link' => 'رابط ويب', + 'External links' => 'روابط خارجية', + 'Add external link' => 'إضافة رابط خارجي', + 'Type' => 'النوع', + 'Dependency' => 'اعتمادية', + 'Add internal link' => 'إضافة رابط داخلي', + 'Add a new external link' => 'إضافة رابط خارجي جديد', + 'Edit external link' => 'تعديل رابط خارجي', + 'External link' => 'رابط خارجي', + 'Copy and paste your link here...' => 'انسخ وألصق رابطك هنا...', + 'URL' => 'الرابط', + 'Internal links' => 'روابط داخلية', + 'Assign to me' => 'إسناد إليّ', + 'Me' => 'أنا', + 'Do not duplicate anything' => 'لا تستنسخ أي شيء', + 'Projects management' => 'إدارة المشاريع', + 'Users management' => 'إدارة المستخدمين', + 'Groups management' => 'إدارة المجموعات', + 'Create from another project' => 'إنشاء من مشروع آخر', + 'open' => 'مفتوح', + 'closed' => 'مغلق', + 'Priority:' => 'الأولوية:', + 'Reference:' => 'المرجع:', + 'Complexity:' => 'التعقيد:', + 'Swimlane:' => 'مسار السباحة:', + 'Column:' => 'العمود:', + 'Position:' => 'الموضع:', + 'Creator:' => 'المنشئ:', + 'Time estimated:' => 'الوقت المقدّر:', + '%s hours' => '%s ساعات', + 'Time spent:' => 'الوقت المستغرق:', + 'Created:' => 'أُنشئت:', + 'Modified:' => 'عُدّلت:', + 'Completed:' => 'أُكمِلت:', + 'Started:' => 'بدأت:', + 'Moved:' => 'نُقلت:', + 'Task #%d' => 'المهمة #%d', + 'Time format' => 'تنسيق الوقت', + 'Start date: ' => 'تاريخ البدء: ', + 'End date: ' => 'تاريخ الانتهاء: ', + 'New due date: ' => 'تاريخ استحقاق جديد: ', + 'Start date changed: ' => 'تم تغيير تاريخ البدء: ', + 'Disable personal projects' => 'تعطيل المشاريع الشخصية', + 'Do you really want to remove this custom filter: "%s"?' => 'هل تريد فعلاً إزالة هذا المرشّح المخصص: "%s"؟', + 'Remove a custom filter' => 'إزالة مرشّح مخصص', + 'User activated successfully.' => 'تم تفعيل المستخدم بنجاح.', + 'Unable to enable this user.' => 'تعذّر تمكين هذا المستخدم.', + 'User disabled successfully.' => 'تم تعطيل المستخدم بنجاح.', + 'Unable to disable this user.' => 'تعذّر تعطيل هذا المستخدم.', + 'All files have been uploaded successfully.' => 'تم رفع جميع الملفات بنجاح.', + 'The maximum allowed file size is %sB.' => 'أقصى حجم ملف مسموح به هو %sB.', + 'Drag and drop your files here' => 'اسحب وأفلِت ملفاتك هنا', + 'choose files' => 'اختر ملفات', + 'View profile' => 'عرض الملف الشخصي', + 'Two Factor' => 'عاملان', + 'Disable user' => 'تعطيل المستخدم', + 'Do you really want to disable this user: "%s"?' => 'هل تريد فعلاً تعطيل هذا المستخدم: "%s"؟', + 'Enable user' => 'تمكين المستخدم', + 'Do you really want to enable this user: "%s"?' => 'هل تريد فعلاً تمكين هذا المستخدم: "%s"؟', + 'Download' => 'تنزيل', + 'Uploaded: %s' => 'تم الرفع: %s', + 'Size: %s' => 'الحجم: %s', + 'Uploaded by %s' => 'رفعها %s', + 'Filename' => 'اسم الملف', + 'Size' => 'الحجم', + 'Column created successfully.' => 'تم إنشاء العمود بنجاح.', + 'Another column with the same name exists in the project' => 'يوجد عمود آخر بنفس الاسم في المشروع', + 'Default filters' => 'المرشّحات الافتراضية', + 'Your board doesn\'t have any columns!' => 'لوحتك لا تحتوي على أي أعمدة!', + 'Change column position' => 'تغيير موضع العمود', + 'Switch to the project overview' => 'التبديل إلى نظرة عامة على المشروع', + 'User filters' => 'مرشّحات المستخدم', + 'Category filters' => 'مرشّحات الفئات', + 'Upload a file' => 'رفع ملف', + 'View file' => 'عرض الملف', + 'Last activity' => 'آخر نشاط', + 'Change subtask position' => 'تغيير موضع المهمة الفرعية', + 'This value must be greater than %d' => 'يجب أن تكون هذه القيمة أكبر من %d', + 'Another swimlane with the same name exists in the project' => 'يوجد مسار سباحة آخر بنفس الاسم في المشروع', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'مثال: https://example.kanboard.org/ (يُستخدم لإنشاء روابط مطلقة)', + 'Actions duplicated successfully.' => 'تم استنساخ الإجراءات بنجاح.', + 'Unable to duplicate actions.' => 'تعذّر استنساخ الإجراءات.', + 'Add a new action' => 'إضافة إجراء جديد', + 'Import from another project' => 'استيراد من مشروع آخر', + 'There is no action at the moment.' => 'لا يوجد أي إجراء في الوقت الحالي.', + 'Import actions from another project' => 'استيراد إجراءات من مشروع آخر', + 'There is no available project.' => 'لا يوجد مشروع متاح.', + 'Local File' => 'ملف محلي', + 'Configuration' => 'الضبط', + 'PHP version:' => 'إصدار PHP:', + 'PHP SAPI:' => 'واجهة PHP SAPI:', + 'OS version:' => 'إصدار نظام التشغيل:', + 'Database version:' => 'إصدار قاعدة البيانات:', + 'Browser:' => 'المتصفح:', + 'Task view' => 'عرض المهمة', + 'Edit task' => 'تعديل المهمة', + 'Edit description' => 'تعديل الوصف', + 'New internal link' => 'رابط داخلي جديد', + 'Display list of keyboard shortcuts' => 'عرض قائمة اختصارات لوحة المفاتيح', + 'Avatar' => 'الصورة الرمزية', + 'Upload my avatar image' => 'رفع صورتي الرمزية', + 'Remove my image' => 'إزالة صورتي', + 'The OAuth2 state parameter is invalid' => 'معامل حالة OAuth2 غير صالح', + 'User not found.' => 'المستخدم غير موجود.', + 'Search in activity stream' => 'بحث في سجل النشاط', + 'My activities' => 'أنشطتي', + 'Activity until yesterday' => 'نشاط حتى البارحة', + 'Activity until today' => 'نشاط حتى اليوم', + 'Search by creator: ' => 'بحث حسب المنشئ: ', + 'Search by creation date: ' => 'بحث حسب تاريخ الإنشاء: ', + 'Search by task status: ' => 'بحث حسب حالة المهمة: ', + 'Search by task title: ' => 'بحث حسب عنوان المهمة: ', + 'Activity stream search' => 'بحث في سجل النشاط', + 'Projects where "%s" is manager' => 'المشاريع التي يكون "%s" مديرًا لها', + 'Projects where "%s" is member' => 'المشاريع التي يكون "%s" عضوًا فيها', + 'Open tasks assigned to "%s"' => 'المهام المفتوحة المسندة إلى "%s"', + 'Closed tasks assigned to "%s"' => 'المهام المغلقة المسندة إلى "%s"', + 'Assign automatically a color based on a priority' => 'تعيين لون تلقائيًا بناءً على الأولوية', + 'Overdue tasks for the project(s) "%s"' => 'المهام المتأخرة للمشروع(ات) "%s"', + 'Upload files' => 'رفع ملفات', + 'Installed Plugins' => 'الإضافات المثبتة', + 'Plugin Directory' => 'دليل الإضافات', + 'Plugin installed successfully.' => 'تم تثبيت الإضافة بنجاح.', + 'Plugin updated successfully.' => 'تم تحديث الإضافة بنجاح.', + 'Plugin removed successfully.' => 'تمت إزالة الإضافة بنجاح.', + 'Subtask converted to task successfully.' => 'تم تحويل المهمة الفرعية إلى مهمة بنجاح.', + 'Unable to convert the subtask.' => 'تعذّر تحويل المهمة الفرعية.', + 'Unable to extract plugin archive.' => 'تعذّر استخراج أرشيف الإضافة.', + 'Plugin not found.' => 'الإضافة غير موجودة.', + 'You don\'t have the permission to remove this plugin.' => 'ليست لديك صلاحية إزالة هذه الإضافة.', + 'Unable to download plugin archive.' => 'تعذّر تنزيل أرشيف الإضافة.', + 'Unable to write temporary file for plugin.' => 'تعذّر كتابة ملف مؤقت للإضافة.', + 'Unable to open plugin archive.' => 'تعذّر فتح أرشيف الإضافة.', + 'There is no file in the plugin archive.' => 'لا يوجد ملف في أرشيف الإضافة.', + 'Create tasks in bulk' => 'إنشاء مهام بالجملة', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'نسخة Kanboard لديك غير مهيأ لتثبيت الإضافات من واجهة المستخدم.', + 'There is no plugin available.' => 'لا توجد إضافة متاحة.', + 'Install' => 'تثبيت', + 'Update' => 'تحديث', + 'Up to date' => 'محدّث', + 'Not available' => 'غير متاح', + 'Remove plugin' => 'إزالة الإضافة', + 'Do you really want to remove this plugin: "%s"?' => 'هل تريد فعلاً إزالة هذه الإضافة: "%s"؟', + 'Uninstall' => 'إلغاء التثبيت', + 'Listing' => 'قائمة', + 'Metadata' => 'بيانات وصفية', + 'Manage projects' => 'إدارة المشاريع', + 'Convert to task' => 'تحويل إلى مهمة', + 'Convert sub-task to task' => 'تحويل مهمة فرعية إلى مهمة', + 'Do you really want to convert this sub-task to a task?' => 'هل تريد فعلاً تحويل هذه المهمة الفرعية إلى مهمة؟', + 'My task title' => 'عنوان مهمتي', + 'Enter one task by line.' => 'أدخل مهمة واحدة في كل سطر.', + 'Number of failed login:' => 'عدد محاولات الدخول الفاشلة:', + 'Account locked until:' => 'الحساب مقفل حتى:', + 'Email settings' => 'إعدادات البريد الإلكتروني', + 'Email sender address' => 'عنوان مرسل البريد الإلكتروني', + 'Email transport' => 'وسيلة نقل البريد الإلكتروني', + 'Webhook token' => 'رمز Webhook', + 'Project tags management' => 'إدارة وسوم المشروع', + 'Tag created successfully.' => 'تم إنشاء الوسم بنجاح.', + 'Unable to create this tag.' => 'تعذّر إنشاء هذا الوسم.', + 'Tag updated successfully.' => 'تم تحديث الوسم بنجاح.', + 'Unable to update this tag.' => 'تعذّر تحديث هذا الوسم.', + 'Tag removed successfully.' => 'تمت إزالة الوسم بنجاح.', + 'Unable to remove this tag.' => 'تعذّر إزالة هذا الوسم.', + 'Global tags management' => 'إدارة الوسوم العامة', + 'Tags' => 'الوسوم', + 'Tags management' => 'إدارة الوسوم', + 'Add new tag' => 'إضافة وسم جديد', + 'Edit a tag' => 'تعديل وسم', + 'Project tags' => 'وسوم المشروع', + 'There is no specific tag for this project at the moment.' => 'لا يوجد وسم خاص بهذا المشروع حاليًا.', + 'Tag' => 'وسم', + 'Remove a tag' => 'إزالة وسم', + 'Do you really want to remove this tag: "%s"?' => 'هل تريد فعلاً إزالة هذا الوسم: "%s"؟', + 'Global tags' => 'وسوم عامة', + 'There is no global tag at the moment.' => 'لا يوجد وسم عام حاليًا.', + 'This field cannot be empty' => 'لا يمكن أن يكون هذا الحقل فارغًا', + 'Close a task when there is no activity in a specific column' => 'إغلاق مهمة في عمود محدد عند عدم وجود نشاط', + '%s removed a subtask for the task #%d' => 'قام %s بإزالة مهمة فرعية للمهمة #%d', + '%s removed a comment on the task #%d' => 'قام %s بإزالة تعليق على المهمة #%d', + 'Comment removed on task #%d' => 'تمت إزالة التعليق على المهمة #%d', + 'Subtask removed on task #%d' => 'تمت إزالة المهمة الفرعية على المهمة #%d', + 'Hide tasks in this column in the dashboard' => 'إخفاء المهام في هذا العمود في لوحة المعلومات', + '%s removed a comment on the task %s' => 'قام %s بإزالة تعليق على المهمة %s', + '%s removed a subtask for the task %s' => 'قام %s بإزالة مهمة فرعية للمهمة %s', + 'Comment removed' => 'تمت إزالة التعليق', + 'Subtask removed' => 'تمت إزالة المهمة الفرعية', + '%s set a new internal link for the task #%d' => 'قام %s بتعيين رابط داخلي جديد للمهمة #%d', + '%s removed an internal link for the task #%d' => 'قام %s بإزالة رابط داخلي للمهمة #%d', + 'A new internal link for the task #%d has been defined' => 'تم تعريف رابط داخلي جديد للمهمة #%d', + 'Internal link removed for the task #%d' => 'تمت إزالة الرابط الداخلي للمهمة #%d', + '%s set a new internal link for the task %s' => 'قام %s بتعيين رابط داخلي جديد للمهمة %s', + '%s removed an internal link for the task %s' => 'قام %s بإزالة رابط داخلي للمهمة %s', + 'Automatically set the due date on task creation' => 'تعيين تاريخ الاستحقاق تلقائيًا عند إنشاء المهمة', + 'Move the task to another column when closed' => 'نقل المهمة إلى عمود آخر عند الإغلاق', + 'Move the task to another column when not moved during a given period' => 'نقل المهمة إلى عمود آخر عند عدم نقلها خلال فترة معيّنة', + 'Dashboard for %s' => 'لوحة المعلومات لـ %s', + 'Tasks overview for %s' => 'نظرة عامة على المهام لـ %s', + 'Subtasks overview for %s' => 'نظرة عامة على المهام الفرعية لـ %s', + 'Projects overview for %s' => 'نظرة عامة على المشاريع لـ %s', + 'Activity stream for %s' => 'سجل النشاط لـ %s', + 'Assign a color when the task is moved to a specific swimlane' => 'تعيين لون عند نقل المهمة إلى مسار سباحة محدد', + 'Assign a priority when the task is moved to a specific swimlane' => 'تعيين أولوية عند نقل المهمة إلى مسار سباحة محدد', + 'User unlocked successfully.' => 'تم فتح قفل المستخدم بنجاح.', + 'Unable to unlock the user.' => 'تعذّر فتح قفل المستخدم.', + 'Move a task to another swimlane' => 'نقل مهمة إلى مسار سباحة آخر', + 'Creator Name' => 'اسم المنشئ', + 'Time spent and estimated' => 'الوقت المستغرق والمقدّر', + 'Move position' => 'نقل الموضع', + 'Move task to another position on the board' => 'نقل المهمة إلى موضع آخر على اللوحة', + 'Insert before this task' => 'إدراج قبل هذه المهمة', + 'Insert after this task' => 'إدراج بعد هذه المهمة', + 'Unlock this user' => 'فتح قفل هذا المستخدم', + 'Custom Project Roles' => 'أدوار مشروع مخصصة', + 'Add a new custom role' => 'إضافة دور مخصص جديد', + 'Restrictions for the role "%s"' => 'القيود للدور "%s"', + 'Add a new project restriction' => 'إضافة قيد مشروع جديد', + 'Add a new drag and drop restriction' => 'إضافة قيد سحب وإفلات جديد', + 'Add a new column restriction' => 'إضافة قيد عمود جديد', + 'Edit this role' => 'تعديل هذا الدور', + 'Remove this role' => 'إزالة هذا الدور', + 'There is no restriction for this role.' => 'لا توجد قيود لهذا الدور.', + 'Only moving task between those columns is permitted' => 'يُسمح فقط بنقل المهمة بين هذه الأعمدة', + 'Close a task in a specific column when not moved during a given period' => 'إغلاق مهمة في عمود محدد عند عدم نقلها خلال فترة معيّنة', + 'Edit columns' => 'تحرير الأعمدة', + 'The column restriction has been created successfully.' => 'تم إنشاء قيد العمود بنجاح.', + 'Unable to create this column restriction.' => 'تعذّر إنشاء قيد العمود هذا.', + 'Column restriction removed successfully.' => 'تمت إزالة قيد العمود بنجاح.', + 'Unable to remove this restriction.' => 'تعذّر إزالة هذا القيد.', + 'Your custom project role has been created successfully.' => 'تم إنشاء دور المشروع المخصص بنجاح.', + 'Unable to create custom project role.' => 'تعذّر إنشاء دور مشروع مخصص.', + 'Your custom project role has been updated successfully.' => 'تم تحديث دور المشروع المخصص بنجاح.', + 'Unable to update custom project role.' => 'تعذّر تحديث دور المشروع المخصص.', + 'Custom project role removed successfully.' => 'تمت إزالة دور المشروع المخصص بنجاح.', + 'Unable to remove this project role.' => 'تعذّر إزالة دور المشروع هذا.', + 'The project restriction has been created successfully.' => 'تم إنشاء قيد المشروع بنجاح.', + 'Unable to create this project restriction.' => 'تعذّر إنشاء قيد المشروع هذا.', + 'Project restriction removed successfully.' => 'تمت إزالة قيد المشروع بنجاح.', + 'You cannot create tasks in this column.' => 'لا يمكنك إنشاء مهام في هذا العمود.', + 'Task creation is permitted for this column' => 'إنشاء المهام مسموح لهذا العمود', + 'Closing or opening a task is permitted for this column' => 'مسموح إغلاق أو فتح مهمة لهذا العمود', + 'Task creation is blocked for this column' => 'إنشاء المهام محظور لهذا العمود', + 'Closing or opening a task is blocked for this column' => 'إغلاق أو فتح مهمة محظور لهذا العمود', + 'Task creation is not permitted' => 'إنشاء المهام غير مسموح', + 'Closing or opening a task is not permitted' => 'إغلاق أو فتح مهمة غير مسموح', + 'New drag and drop restriction for the role "%s"' => 'قيد سحب وإفلات جديد للدور "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'سيتمكن الأشخاص المنتمون إلى هذا الدور من نقل المهام فقط بين عمود المصدر والعمود الوجهة.', + 'Remove a column restriction' => 'إزالة قيد عمود', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'هل تريد فعلاً إزالة قيد العمود هذا: "%s" إلى "%s"؟', + 'New column restriction for the role "%s"' => 'قيد عمود جديد للدور "%s"', + 'Rule' => 'قاعدة', + 'Do you really want to remove this column restriction?' => 'هل تريد فعلاً إزالة قيد العمود هذا؟', + 'Custom roles' => 'أدوار مخصصة', + 'New custom project role' => 'دور مشروع مخصص جديد', + 'Edit custom project role' => 'تعديل دور مشروع مخصص', + 'Remove a custom role' => 'إزالة دور مخصص', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'هل تريد فعلاً إزالة هذا الدور المخصص: "%s"؟ سيصبح جميع المعيّنين لهذا الدور أعضاء في المشروع.', + 'There is no custom role for this project.' => 'لا يوجد دور مخصص لهذا المشروع.', + 'New project restriction for the role "%s"' => 'قيد مشروع جديد للدور "%s"', + 'Restriction' => 'قيد', + 'Remove a project restriction' => 'إزالة قيد مشروع', + 'Do you really want to remove this project restriction: "%s"?' => 'هل تريد فعلاً إزالة قيد المشروع هذا: "%s"؟', + 'Duplicate to multiple projects' => 'استنساخ إلى مشاريع متعددة', + 'This field is required' => 'هذا الحقل مطلوب', + 'Moving a task is not permitted' => 'غير مسموح نقل مهمة', + 'This value must be in the range %d to %d' => 'يجب أن تكون هذه القيمة ضمن النطاق %d إلى %d', + 'You are not allowed to move this task.' => 'غير مسموح لك نقل هذه المهمة.', + 'API User Access' => 'وصول مستخدم الواجهة البرمجية (API)', + 'Preview' => 'معاينة', + 'Write' => 'كتابة', + 'Write your text in Markdown' => 'اكتب نصك بصيغة Markdown', + 'No personal API access token registered.' => 'لا يوجد رمز وصول شخصي للـ API مُسجَّل.', + 'Your personal API access token is "%s"' => 'رمز الوصول الشخصي للـ API الخاص بك هو "%s"', + 'Remove your token' => 'إزالة رمزك', + 'Generate a new token' => 'توليد رمز جديد', + 'Showing %d-%d of %d' => 'عرض %d-%d من %d', + 'Outgoing Emails' => 'رسائل البريد الصادرة', + 'Add or change currency rate' => 'إضافة أو تغيير سعر الصرف', + 'Reference currency: %s' => 'عملة المرجع: %s', + 'Add custom filters' => 'إضافة مرشّحات مخصصة', + 'Export' => 'تصدير', + 'Add link label' => 'إضافة تسمية رابط', + 'Incompatible Plugins' => 'إضافات غير متوافقة', + 'Compatibility' => 'التوافق', + 'Permissions and ownership' => 'الأذونات والملكية', + 'Priorities' => 'الأولويات', + 'Close this window' => 'أغلق هذه النافذة', + 'Unable to upload this file.' => 'تعذّر رفع هذا الملف.', + 'Import tasks' => 'استيراد مهام', + 'Choose a project' => 'اختر مشروعًا', + 'Profile' => 'الملف الشخصي', + 'Application role' => 'دور التطبيق', + '%d invitations were sent.' => 'تم إرسال %d دعوة.', + '%d invitation was sent.' => 'تم إرسال %d دعوة.', + 'Unable to create this user.' => 'تعذّر إنشاء هذا المستخدم.', + 'Kanboard Invitation' => 'دعوة Kanboard', + 'Visible on dashboard' => 'ظاهر في لوحة المعلومات', + 'Created at:' => 'أُنشئ في:', + 'Updated at:' => 'عُدِّل في:', + 'There is no custom filter.' => 'لا يوجد مرشّح مخصص.', + 'New User' => 'مستخدم جديد', + 'Authentication' => 'المصادقة', + 'If checked, this user will use a third-party system for authentication.' => 'إذا تم تحديده، سيستخدم هذا المستخدم نظام طرف ثالث للمصادقة.', + 'The password is necessary only for local users.' => 'كلمة المرور مطلوبة فقط للمستخدمين المحليين.', + 'You have been invited to register on Kanboard.' => 'لقد تمت دعوتك للتسجيل في Kanboard.', + 'Click here to join your team' => 'انقر هنا للانضمام إلى فريقك', + 'Invite people' => 'دعوة أشخاص', + 'Emails' => 'رسائل بريد', + 'Enter one email address by line.' => 'أدخل عنوان بريد إلكتروني واحد في كل سطر.', + 'Add these people to this project' => 'إضافة هؤلاء الأشخاص إلى هذا المشروع', + 'Add this person to this project' => 'إضافة هذا الشخص إلى هذا المشروع', + 'Sign-up' => 'تسجيل', + 'Credentials' => 'بيانات الاعتماد', + 'New user' => 'مستخدم جديد', + 'This username is already taken' => 'اسم المستخدم هذا محجوز مسبقًا', + 'Your profile must have a valid email address.' => 'يجب أن يحتوي ملفك الشخصي على عنوان بريد إلكتروني صالح.', + 'TRL - Turkish Lira' => 'TRL - ليرة تركية', + 'The project email is optional and could be used by several plugins.' => 'بريد المشروع اختياري ويمكن أن تستخدمه عدة إضافات.', + 'The project email must be unique across all projects' => 'يجب أن يكون بريد المشروع فريدًا عبر جميع المشاريع', + 'The email configuration has been disabled by the administrator.' => 'تم تعطيل إعدادات البريد الإلكتروني بواسطة المدير.', + 'Close this project' => 'إغلاق هذا المشروع', + 'Open this project' => 'فتح هذا المشروع', + 'Close a project' => 'إغلاق مشروع', + 'Do you really want to close this project: "%s"?' => 'هل تريد فعلاً إغلاق هذا المشروع: "%s"؟', + 'Reopen a project' => 'إعادة فتح مشروع', + 'Do you really want to reopen this project: "%s"?' => 'هل تريد فعلاً إعادة فتح هذا المشروع: "%s"؟', + 'This project is open' => 'هذا المشروع مفتوح', + 'This project is closed' => 'هذا المشروع مغلق', + 'Unable to upload files, check the permissions of your data folder.' => 'تعذّر رفع الملفات، تحقّق من أذونات مجلد البيانات.', + 'Another category with the same name exists in this project' => 'توجد فئة أخرى بنفس الاسم في هذا المشروع', + 'Comment sent by email successfully.' => 'تم إرسال التعليق عبر البريد بنجاح.', + 'Sent by email to "%s" (%s)' => 'أُرسل عبر البريد إلى "%s" (%s)', + 'Unable to read uploaded file.' => 'تعذّر قراءة الملف المرفوع.', + 'Database uploaded successfully.' => 'تم رفع قاعدة البيانات بنجاح.', + 'Task sent by email successfully.' => 'تم إرسال المهمة عبر البريد بنجاح.', + 'There is no category in this project.' => 'لا توجد فئة في هذا المشروع.', + 'Send by email' => 'إرسال عبر البريد', + 'Create and send a comment by email' => 'إنشاء وإرسال تعليق عبر البريد', + 'Subject' => 'الموضوع', + 'Upload the database' => 'رفع قاعدة البيانات', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'يمكنك رفع قاعدة بيانات Sqlite التي سبق تنزيلها (تنسيق Gzip).', + 'Database file' => 'ملف قاعدة البيانات', + 'Upload' => 'رفع', + 'Your project must have at least one active swimlane.' => 'يجب أن يحتوي مشروعك على مسار سباحة واحد نشط على الأقل.', + 'Project: %s' => 'المشروع: %s', + 'Automatic action not found: "%s"' => 'لم يتم العثور على إجراء تلقائي: "%s"', + '%d projects' => '%d مشاريع', + '%d project' => '%d مشروع', + 'There is no project.' => 'لا يوجد مشروع.', + 'Sort' => 'فرز', + 'Project ID' => 'معرّف المشروع', + 'Project name' => 'اسم المشروع', + 'Public' => 'عام', + 'Personal' => 'شخصي', + '%d tasks' => '%d مهام', + '%d task' => '%d مهمة', + 'Task ID' => 'معرّف المهمة', + 'Assign automatically a color when due date is expired' => 'تعيين لون تلقائيًا عند انقضاء تاريخ الاستحقاق', + 'Total score in this column across all swimlanes' => 'إجمالي النقاط في هذا العمود عبر جميع مسارات السباحة', + 'HRK - Kuna' => 'HRK - كونا', + 'ARS - Argentine Peso' => 'ARS - بيزو أرجنتيني', + 'COP - Colombian Peso' => 'COP - بيزو كولومبي', + '%d groups' => '%d مجموعات', + '%d group' => '%d مجموعة', + 'Group ID' => 'معرّف المجموعة', + 'External ID' => 'معرّف خارجي', + '%d users' => '%d مستخدمين', + '%d user' => '%d مستخدم', + 'Hide subtasks' => 'إخفاء المهام الفرعية', + 'Show subtasks' => 'إظهار المهام الفرعية', + 'Authentication Parameters' => 'معاملات المصادقة', + 'API Access' => 'وصول API', + 'No users found.' => 'لم يتم العثور على مستخدمين.', + 'User ID' => 'معرّف المستخدم', + 'Notifications are activated' => 'الإشعارات مفعّلة', + 'Notifications are disabled' => 'الإشعارات معطّلة', + 'User disabled' => 'المستخدم معطّل', + '%d notifications' => '%d إشعارات', + '%d notification' => '%d إشعار', + 'There is no external integration installed.' => 'لا توجد أي تكاملات خارجية مثبتة.', + 'You are not allowed to update tasks assigned to someone else.' => 'غير مسموح لك بتحديث المهام المسندة إلى شخص آخر.', + 'You are not allowed to change the assignee.' => 'غير مسموح لك بتغيير المكلّف.', + 'Task suppression is not permitted' => 'حذف المهام غير مسموح', + 'Changing assignee is not permitted' => 'تغيير المكلّف غير مسموح', + 'Update only assigned tasks is permitted' => 'مسموح تحديث المهام المسندة فقط', + 'Only for tasks assigned to the current user' => 'فقط للمهام المسندة إلى المستخدم الحالي', + 'My projects' => 'مشاريعي', + 'You are not a member of any project.' => 'لست عضوًا في أي مشروع.', + 'My subtasks' => 'مهامي الفرعية', + '%d subtasks' => '%d مهام فرعية', + '%d subtask' => '%d مهمة فرعية', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'يُسمح بنقل المهمة بين هذه الأعمدة فقط للمهام المسندة إلى المستخدم الحالي', + '[DUPLICATE]' => '[مُكرّر]', + 'DKK - Danish Krona' => 'DKK - كرونة دنماركية', + 'Remove user from group' => 'إزالة مستخدم من المجموعة', + 'Assign the task to its creator' => 'إسناد المهمة إلى منشئها', + 'This task was sent by email to "%s" with subject "%s".' => 'أُرسلت هذه المهمة عبر البريد إلى "%s" بالموضوع "%s".', + 'Predefined Email Subjects' => 'عناوين بريد محددة مسبقًا', + 'Write one subject by line.' => 'اكتب موضوعًا واحدًا في كل سطر.', + 'Create another link' => 'إنشاء رابط آخر', + 'BRL - Brazilian Real' => 'BRL - ريال برازيلي', + 'Add a new Kanboard task' => 'إضافة مهمة Kanboard جديدة', + 'Subtask not started' => 'مهمة فرعية غير مُبدوءة', + 'Subtask currently in progress' => 'مهمة فرعية قيد التنفيذ', + 'Subtask completed' => 'مهمة فرعية مكتملة', + 'Subtask added successfully.' => 'تمت إضافة المهمة الفرعية بنجاح.', + '%d subtasks added successfully.' => 'تمت إضافة %d مهمة فرعية بنجاح.', + 'Enter one subtask by line.' => 'أدخل مهمة فرعية واحدة في كل سطر.', + 'Predefined Contents' => 'محتويات محددة مسبقًا', + 'Predefined contents' => 'محتويات محددة مسبقًا', + 'Predefined Task Description' => 'وصف مهمة محدد مسبقًا', + 'Do you really want to remove this template? "%s"' => 'هل تريد فعلاً إزالة هذا القالب؟ "%s"', + 'Add predefined task description' => 'إضافة وصف مهمة محدد مسبقًا', + 'Predefined Task Descriptions' => 'أوصاف مهام محددة مسبقًا', + 'Template created successfully.' => 'تم إنشاء القالب بنجاح.', + 'Unable to create this template.' => 'تعذّر إنشاء هذا القالب.', + 'Template updated successfully.' => 'تم تحديث القالب بنجاح.', + 'Unable to update this template.' => 'تعذّر تحديث هذا القالب.', + 'Template removed successfully.' => 'تمت إزالة القالب بنجاح.', + 'Unable to remove this template.' => 'تعذّر إزالة هذا القالب.', + 'Template for the task description' => 'قالب لوصف المهمة', + 'The start date is greater than the end date' => 'تاريخ البدء أكبر من تاريخ الانتهاء', + 'Tags must be separated by a comma' => 'يجب فصل الوسوم بفاصلة', + 'Only the task title is required' => 'مطلوب فقط عنوان المهمة', + 'Creator Username' => 'اسم مستخدم المنشئ', + 'Color Name' => 'اسم اللون', + 'Column Name' => 'اسم العمود', + 'Swimlane Name' => 'اسم مسار السباحة', + 'Time Estimated' => 'الوقت المقدّر', + 'Time Spent' => 'الوقت المستغرق', + 'External Link' => 'رابط خارجي', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'تتيح هذه الميزة خلاصة iCal وتلقيم RSS وعرض اللوحة العامة.', + 'Stop the timer of all subtasks when moving a task to another column' => 'إيقاف مؤقت جميع المهام الفرعية عند نقل مهمة إلى عمود آخر', + 'Subtask Title' => 'عنوان المهمة الفرعية', + 'Add a subtask and activate the timer when moving a task to another column' => 'إضافة مهمة فرعية وتفعيل المؤقت عند نقل مهمة إلى عمود آخر', + 'days' => 'أيام', + 'minutes' => 'دقائق', + 'seconds' => 'ثوانٍ', + 'Assign automatically a color when preset start date is reached' => 'تعيين لون تلقائيًا عند بلوغ تاريخ البدء المحدّد مسبقًا', + 'Move the task to another column once a predefined start date is reached' => 'نقل المهمة إلى عمود آخر عند بلوغ تاريخ بدء محدد مسبقًا', + 'This task is now linked to the task %s with the relation "%s"' => 'تم ربط هذه المهمة الآن بالمهمة %s بالعلاقة "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'تمت إزالة الرابط بالعلاقة "%s" إلى المهمة %s', + 'Custom Filter:' => 'مرشّح مخصص:', + 'Unable to find this group.' => 'تعذّر العثور على هذه المجموعة.', + '%s moved the task #%d to the column "%s"' => 'قام %s بنقل المهمة #%d إلى العمود "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => 'قام %s بنقل المهمة #%d إلى الموضع %d في العمود "%s"', + '%s moved the task #%d to the swimlane "%s"' => 'قام %s بنقل المهمة #%d إلى مسار السباحة "%s"', + '%sh spent' => '%sس مستغرق', + '%sh estimated' => '%sس مقدّر', + 'Select All' => 'تحديد الكل', + 'Unselect All' => 'إلغاء تحديد الكل', + 'Apply action' => 'تطبيق الإجراء', + 'Move selected tasks to another column or swimlane' => 'نقل المهام المحددة إلى عمود أو مسار سباحة آخر', + 'Edit tasks in bulk' => 'تحرير المهام بالجملة', + 'Choose the properties that you would like to change for the selected tasks.' => 'اختر الخصائص التي ترغب في تغييرها للمهام المحددة.', + 'Configure this project' => 'تهيئة هذا المشروع', + 'Start now' => 'ابدأ الآن', + '%s removed a file from the task #%d' => 'قام %s بإزالة ملف من المهمة #%d', + 'Attachment removed from task #%d: %s' => 'أُزيل المرفق من المهمة #%d: %s', + 'No color' => 'بدون لون', + 'Attachment removed "%s"' => 'أُزيل المرفق "%s"', + '%s removed a file from the task %s' => 'قام %s بإزالة ملف من المهمة %s', + 'Move the task to another swimlane when assigned to a user' => 'نقل المهمة إلى مسار سباحة آخر عند إسنادها لمستخدم', + 'Destination swimlane' => 'مسار السباحة الوجهة', + 'Assign a category when the task is moved to a specific swimlane' => 'تعيين فئة عند نقل المهمة إلى مسار سباحة محدد', + 'Move the task to another swimlane when the category is changed' => 'نقل المهمة إلى مسار سباحة آخر عند تغيير الفئة', + 'Reorder this column by priority (ASC)' => 'إعادة ترتيب هذا العمود حسب الأولوية (تصاعدي)', + 'Reorder this column by priority (DESC)' => 'إعادة ترتيب هذا العمود حسب الأولوية (تنازلي)', + 'Reorder this column by assignee and priority (ASC)' => 'إعادة ترتيب هذا العمود حسب المكلّف والأولوية (تصاعدي)', + 'Reorder this column by assignee and priority (DESC)' => 'إعادة ترتيب هذا العمود حسب المكلّف والأولوية (تنازلي)', + 'Reorder this column by assignee (A-Z)' => 'إعادة ترتيب هذا العمود حسب المكلّف (أ-ي)', + 'Reorder this column by assignee (Z-A)' => 'إعادة ترتيب هذا العمود حسب المكلّف (ي-أ)', + 'Reorder this column by due date (ASC)' => 'إعادة ترتيب هذا العمود حسب تاريخ الاستحقاق (تصاعدي)', + 'Reorder this column by due date (DESC)' => 'إعادة ترتيب هذا العمود حسب تاريخ الاستحقاق (تنازلي)', + 'Reorder this column by id (ASC)' => 'إعادة ترتيب هذا العمود حسب المعرّف (تصاعدي)', + 'Reorder this column by id (DESC)' => 'إعادة ترتيب هذا العمود حسب المعرّف (تنازلي)', + '%s moved the task #%d "%s" to the project "%s"' => 'قام %s بنقل المهمة #%d "%s" إلى المشروع "%s"', + 'Task #%d "%s" has been moved to the project "%s"' => 'تم نقل المهمة #%d "%s" إلى المشروع "%s"', + 'Move the task to another column when the due date is less than a certain number of days' => 'نقل المهمة إلى عمود آخر عندما يكون تاريخ الاستحقاق أقل من عدد معيّن من الأيام', + 'Automatically update the start date when the task is moved away from a specific column' => 'تحديث تاريخ البدء تلقائيًا عند نقل المهمة بعيدًا عن عمود محدد', + 'HTTP Client:' => 'عميل HTTP:', + 'Assigned' => 'مُسند', + 'Task limits apply to each swimlane individually' => 'تنطبق حدود المهام على كل مسار سباحة على حدة', + 'Column task limits apply to each swimlane individually' => 'تنطبق حدود مهام العمود على كل مسار سباحة على حدة', + 'Column task limits are applied to each swimlane individually' => 'تُطبّق حدود مهام العمود على كل مسار سباحة على حدة', + 'Column task limits are applied across swimlanes' => 'تُطبّق حدود مهام العمود عبر مسارات السباحة', + 'Task limit: ' => 'حد المهام: ', + 'Change to global tag' => 'تغيير إلى وسم عام', + 'Do you really want to make the tag "%s" global?' => 'هل تريد فعلاً جعل الوسم "%s" عامًا؟', + 'Enable global tags for this project' => 'تمكين الوسوم العامة لهذا المشروع', + 'Group membership(s):' => 'عضوية المجموعات:', + '%s is a member of the following group(s): %s' => '%s عضو في المجموعات التالية: %s', + '%d/%d group(s) shown' => 'عُرض %d/%d مجموعة', + 'Subtask creation or modification' => 'إنشاء أو تعديل مهمة فرعية', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'إسناد المهمة إلى مستخدم محدد عند نقلها إلى مسار سباحة معين', + 'Comment' => 'تعليق', + 'Collapse vertically' => 'طيّ رأسيًا', + 'Expand vertically' => 'توسيع رأسيًا', + 'MXN - Mexican Peso' => 'MXN - بيزو مكسيكي', + 'Estimated vs actual time per column' => 'الوقت المقدّر مقابل الفعلي لكل عمود', + 'HUF - Hungarian Forint' => 'HUF - فورنت مجري', + 'XBT - Bitcoin' => 'XBT - بيتكوين', + 'You must select a file to upload as your avatar!' => 'يجب تحديد ملف لرفعه كصورتك الرمزية!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'الملف الذي رفعته ليس صورة صالحة! (يسمح فقط بـ *.gif و *.jpg و *.jpeg و *.png)', + 'Automatically set the due date when the task is moved away from a specific column' => 'تعيين تاريخ الاستحقاق تلقائيًا عند نقل المهمة بعيدًا عن عمود محدد', + 'No other projects found.' => 'لم يتم العثور على مشاريع أخرى.', + 'Tasks copied successfully.' => 'تم نسخ المهام بنجاح.', + 'Unable to copy tasks.' => 'تعذّر نسخ المهام.', + 'Theme' => 'السِمة', + 'Theme:' => 'السِمة:', + 'Light theme' => 'سِمة فاتحة', + 'Dark theme' => 'سِمة داكنة', + 'Automatic theme - Sync with system' => 'سِمة تلقائية - مزامنة مع النظام', + 'Application managers or more' => 'مدراء التطبيق أو أعلى', + 'Administrators' => 'المديرون', + 'Visibility:' => 'الظهور:', + 'Standard users' => 'المستخدمون العاديون', + 'Visibility is required' => 'الظهور مطلوب', + 'The visibility should be an app role' => 'يجب أن تكون درجة الظهور دورًا في التطبيق', + 'Reply' => 'ردّ', + '%s wrote: ' => 'كتب %s: ', + 'Number of visible tasks in this column and swimlane' => 'عدد المهام المرئية في هذا العمود ومسار السباحة', + 'Number of tasks in this swimlane' => 'عدد المهام في هذا مسار السباحة', + 'Unable to find another subtask in progress, you can close this window.' => 'تعذّر العثور على مهمة فرعية أخرى قيد التنفيذ، يمكنك إغلاق هذه النافذة.', + 'This theme is invalid' => 'هذه السِمة غير صالحة', + 'This role is invalid' => 'هذا الدور غير صالح', + 'This timezone is invalid' => 'هذه المنطقة الزمنية غير صالحة', + 'This language is invalid' => 'هذه اللغة غير صالحة', + 'This URL is invalid' => 'هذا الرابط غير صالح', + 'Date format invalid' => 'تنسيق التاريخ غير صالح', + 'Time format invalid' => 'تنسيق الوقت غير صالح', + 'Invalid Mail transport' => 'وسيلة نقل بريد غير صالحة', + 'Color invalid' => 'لون غير صالح', + 'This value must be greater or equal to %d' => 'يجب أن تكون هذه القيمة أكبر من أو تساوي %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'أضف BOM في بداية الملف (مطلوب لبرنامج Microsoft Excel)', + 'Just add these tag(s)' => 'أضف هذه الوسوم فقط', + 'Remove internal link(s)' => 'إزالة روابط داخلية', + 'Import tasks from another project' => 'استيراد مهام من مشروع آخر', + 'Select the project to copy tasks from' => 'حدد المشروع لنسخ المهام منه', + 'The total maximum allowed attachments size is %sB.' => 'إجمالي الحد الأقصى المسموح به للمرفقات هو %sB.', + 'Add attachments' => 'إضافة مرفقات', + 'Task #%d "%s" is overdue' => 'المهمة #%d "%s" متأخرة', + 'Enable notifications by default for all new users' => 'تمكين الإشعارات افتراضيًا لجميع المستخدمين الجدد', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'إسناد المهمة إلى منشئها لأعمدة محددة إذا لم يتم تعيين مكلّف يدويًا', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'إسناد المهمة إلى المستخدم المسجّل عند تغيير العمود إلى العمود المحدد إذا لم يكن هناك مستخدم معيّن', +]; diff --git a/app/Locale/bg_BG/translations.php b/app/Locale/bg_BG/translations.php new file mode 100644 index 0000000..793325c --- /dev/null +++ b/app/Locale/bg_BG/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => ' ', + 'None' => 'Няма', + 'Edit' => 'Редактиране', + 'Remove' => 'Премахни', + 'Yes' => 'Да', + 'No' => 'Не', + 'cancel' => 'Отказ', + 'or' => 'или', + 'Yellow' => 'Жълто', + 'Blue' => 'Синьо', + 'Green' => 'Зелено', + 'Purple' => 'Лилаво', + 'Red' => 'Червено', + 'Orange' => 'Оранжево', + 'Grey' => 'Сиво', + 'Brown' => 'Кафяво', + 'Deep Orange' => 'Тъмнооранжево', + 'Dark Grey' => 'Тъмносиво', + 'Pink' => 'Розово', + 'Teal' => 'Тил', + 'Cyan' => 'Циан', + 'Lime' => 'Лайм', + 'Light Green' => 'Светлозелено', + 'Amber' => 'Кехлибаренo', + 'Save' => 'Запиши', + 'Login' => ' Вход', + 'Official website:' => 'Официален сайт:', + 'Unassigned' => 'Неприсвоен', + 'View this task' => 'Преглед на тази задача', + 'Remove user' => 'Премахване на потребител', + 'Do you really want to remove this user: "%s"?' => 'Наистина ли искате да премахнете този потребител: "%s"?', + 'All users' => 'Всички потребители', + 'Username' => 'Потребителско име', + 'Password' => 'Парола', + 'Administrator' => 'Администратор', + 'Sign in' => 'Вход', + 'Users' => 'Потребители', + 'Forbidden' => 'Забранен', + 'Access Forbidden' => 'Достъпът е забранен', + 'Edit user' => 'Редактиране на потребител', + 'Logout' => 'Изход', + 'Bad username or password' => 'Грешно потребителско име или парола', + 'Edit project' => 'Редактирай проект', + 'Name' => 'Име', + 'Projects' => 'Проекти', + 'No project' => 'Няма проект', + 'Project' => 'Проект', + 'Status' => 'Статус', + 'Tasks' => 'Задачи', + 'Board' => 'Дъска', + 'Actions' => 'Действия', + 'Inactive' => 'Неактивен', + 'Active' => 'Активен', + 'Unable to update this board.' => 'Не може да се актуализира тази дъска.', + 'Disable' => 'Изключи', + 'Enable' => 'Включи', + 'New project' => 'Нов проект', + 'Do you really want to remove this project: "%s"?' => 'Наистина ли искате да премахнете този проект: "%s"?', + 'Remove project' => 'Премахване на проект', + 'Edit the board for "%s"' => 'Редактиране на дъската за "%s"', + 'Add a new column' => 'Добавяне на нова колона', + 'Title' => 'Заглавие', + 'Assigned to %s' => 'Възложено на %s', + 'Remove a column' => 'Премахване на колона', + 'Unable to remove this column.' => 'Тази колона не може да бъде премахната.', + 'Do you really want to remove this column: "%s"?' => 'Наистина ли искате да премахнете тази колона: "%s"?', + 'Settings' => 'Настройки', + 'Application settings' => 'Настройки на приложението', + 'Language' => 'Език', + 'Webhook token:' => 'Webhook токен:', + 'API token:' => 'API токен:', + 'Database size:' => 'Размер на базата данни:', + 'Download the database' => 'Изтегляне на базата данни', + 'Optimize the database' => 'Оптимизиране на базата данни', + '(VACUUM command)' => '(команда за VACUUM)', + '(Gzip compressed Sqlite file)' => '(Gzip компресиран Sqlite файл)', + 'Close a task' => 'Затваряне на задача', + 'Column' => 'Колона', + 'Color' => 'Цвят', + 'Assignee' => 'Изпълнител', + 'Create another task' => 'Създаване на друга задача', + 'New task' => 'Нова задача', + 'Open a task' => 'Отваряне на задача', + 'Do you really want to open this task: "%s"?' => 'Наистина ли искате да отворите тази задача: "%s"?', + 'Back to the board' => 'Обратно към дъската', + 'There is nobody assigned' => 'Никой не е назначен', + 'Column on the board:' => 'Колона на дъската:', + 'Close this task' => 'Затваряне на тази задача', + 'Open this task' => 'Отваряне на тази задача', + 'There is no description.' => 'Няма описание.', + 'Add a new task' => 'Добавяне на нова задача', + 'The username is required' => 'Потребителското име е задължително.', + 'The maximum length is %d characters' => 'Максималната дължина е %d знака', + 'The minimum length is %d characters' => 'Минималната дължина е %d знака', + 'The password is required' => 'Изисква се парола', + 'This value must be an integer' => 'Тази стойност трябва да бъде цяло число', + 'The username must be unique' => 'Потребителското име трябва да е уникално', + 'The user id is required' => 'Потребителското ID е задължителен', + 'Passwords don\'t match' => 'Паролите не съвпадат', + 'The confirmation is required' => 'Потвърждението е задължително', + 'The project is required' => 'Проекта е задължителен', + 'The id is required' => 'ID е задължително', + 'The project id is required' => 'ID на проекта е задължителен', + 'The project name is required' => 'Името на проекта е задължително', + 'The title is required' => 'Изисква се заглавие', + 'Settings saved successfully.' => 'Настройките са записани успешно.', + 'Unable to save your settings.' => 'Неуспешно запазване на вашите настройки.', + 'Database optimization done.' => 'Оптимизирането на базата данни е направено.', + 'Your project has been created successfully.' => 'Вашия проект е създаден успешно.', + 'Unable to create your project.' => 'Неуспешно създаване на вашия проект.', + 'Project updated successfully.' => 'Проекта е актуализиран успешно.', + 'Unable to update this project.' => 'Неуспешно актуализиране на този проект.', + 'Unable to remove this project.' => 'Този проект не може да бъде премахнат.', + 'Project removed successfully.' => 'Проекта е премахнат успешно.', + 'Project activated successfully.' => 'Проекта е активиран успешно.', + 'Unable to activate 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.' => 'Вашата задача не може да бъде актуализирана.', + 'Task updated successfully.' => 'Задачата е актуализирана успешно.', + 'Unable to create your task.' => 'Вашата задача не може да бъде създадена.', + 'Task created successfully.' => 'Задачата е създадена успешно.', + 'User created successfully.' => 'Създаването на потребител е успешно', + 'Unable to create your user.' => 'Неуспешно създаване на вашия потребител.', + 'User updated successfully.' => 'Потребителя е актуализиран успешно', + 'User removed successfully.' => 'Потребителя е премахнат успешно.', + 'Unable to remove this user.' => 'Този потребител не може да бъде премахнат.', + 'Board updated successfully.' => 'Дъската е актуализиран успешно.', + 'Ready' => 'В готовност', + 'Backlog' => 'Изчакващи', + 'Work in progress' => 'Работа в прогрес', + 'Done' => 'Готови', + 'Application version:' => 'Версия на приложението:', + 'Id' => 'Id', + 'Public link' => 'Публична връзка', + 'Timezone' => 'Часова зона', + 'Sorry, I didn\'t find this information in my database!' => 'За съжаление, не намерих тази информация в базата данни!', + 'Page not found' => 'Страницата не е намерена', + 'Complexity' => 'Сложност', + 'Task limit' => 'Лимит на задачите', + 'Task count' => 'Брой задачи', + 'User' => 'Потребител', + 'Comments' => 'Забележки', + 'Comment is required' => 'Необходима е забележка', + 'Comment added successfully.' => 'Забележката е добавена успешно.', + 'Unable to create your comment.' => 'Вашия коментар не може да бъде създаден.', + 'Due Date' => 'Краен срок', + 'Invalid date' => 'Невалидна дата', + 'Automatic actions' => 'Автоматични действия', + 'Your automatic action has been created successfully.' => 'Вашето автоматичното действие е създадено успешно.', + 'Unable to create your automatic action.' => 'Неуспешно създаване на вашето автоматично действие.', + 'Remove an action' => 'Премахване на действие', + 'Unable to remove this action.' => 'Това действие не може да бъде премахнато.', + 'Action removed successfully.' => 'Действието е премахнато успешно.', + 'Automatic actions for the project "%s"' => 'Автоматични действия на проекта "%s"', + 'Add an action' => 'Добавяне на действие', + 'Event name' => 'Име на събитието', + 'Action' => 'Действие', + 'Event' => 'Събитие', + 'When the selected event occurs execute the corresponding action.' => 'Когато се случи избраното събитие, изпълнете съответното действие.', + 'Next step' => 'Следваща стъпка', + 'Define action parameters' => 'Определяне на параметрите за действие', + 'Do you really want to remove this action: "%s"?' => 'Наистина ли искате да премахнете това действие: "%s"?', + 'Remove an automatic action' => 'Премахване на автоматично действие', + 'Assign the task to a specific user' => 'Възлагане на задачата на конкретен потребител', + 'Assign the task to the person who does the action' => 'Възлагане на задачата на лицето, което извършва действието', + 'Duplicate the task to another project' => 'Дублиране на задачата към друг проект', + 'Move a task to another column' => 'Преместване на задача в друга колона', + 'Task modification' => 'Промяна на задача', + 'Task creation' => 'Създаване на задача', + 'Closing a task' => 'Приключване на задача', + 'Assign a color to a specific user' => 'Присвояване на цвят към конкретен потребител', + 'Position' => 'позиция', + 'Duplicate to project' => 'Дублиране на проект', + 'Duplicate' => 'Дублиране', + 'Link' => 'Линк', + 'Comment updated successfully.' => 'Забележката е актуализирана успешно.', + 'Unable to update your comment.' => 'Вашата забележка не може да бъде актуализирана.', + 'Remove a comment' => 'Премахване на забележка', + 'Comment removed successfully.' => 'Забележката е премахната успешно.', + 'Unable to remove this comment.' => 'Тази забележка не може да бъде премахната.', + 'Do you really want to remove this comment?' => 'Наистина ли искате да премахнете тази забележката?', + 'Current password for the user "%s"' => 'Текуща парола на потребителя "%s"', + 'The current password is required' => 'Изисква се текущата парола', + 'Wrong password' => 'Грешна парола', + 'Unknown' => 'Неизвестно', + 'Last logins' => 'Последни влизания', + 'Login date' => 'Дата на влизане', + 'Authentication method' => 'Удостоверяване', + 'IP address' => 'IP адреси', + 'User agent' => 'Потребителски агент', + 'Persistent connections' => 'Устойчиви връзки', + 'No session.' => 'Няма сесия', + 'Expiration date' => ' Изтича на', + 'Remember Me' => ' Запомни ме', + 'Creation date' => 'Дата на създаване', + 'Everybody' => 'Всеки', + 'Open' => 'Отворено', + 'Closed' => 'Затворено', + 'Search' => 'Търсене', + 'Nothing found.' => 'Няма намерени.', + 'Due date' => 'Краен срок', + 'Description' => 'Описание', + '%d comments' => '%d забележки', + '%d comment' => '%d забележка', + 'Email address invalid' => 'Имейл адреса е невалиден', + 'Your external account is not linked anymore to your profile.' => 'Вашия външен акаунт вече не е свързан с вашия профил.', + 'Unable to unlink your external account.' => 'Неуспешно прекратяване на връзката с вашия външния акаунт.', + 'External authentication failed' => 'Неуспешно външно удостоверяване', + 'Your external account is linked to your profile successfully.' => 'Вашия външен акаунт е свързан успешно с вашия профил.', + 'Email' => 'Имейл', + 'Task removed successfully.' => 'Задачата е премахната успешно.', + 'Unable to remove this task.' => 'Тази задача не може да бъде премахната.', + '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' => 'Автоматично задаване на категория въз основа на цвят', + 'Task creation or modification' => 'Създаване или промяна на задача', + 'Category' => 'Категория', + 'Category:' => 'Категория:', + 'Categories' => ' Категории', + 'Your category has been created successfully.' => 'Вашата категория е създадена успешно.', + 'This category has been updated successfully.' => 'Тази категория е актуализирана успешно.', + 'Unable to update this category.' => 'Тази категория не може да бъде актуализирана.', + 'Remove a category' => 'Премахване на категория', + 'Category removed successfully.' => 'Успешно премахната категория.', + 'Unable to remove this category.' => 'Тази категория не може да бъде премахната.', + 'Category modification for the project "%s"' => 'Промяна на категория за проект "%s"', + 'Category Name' => 'Име на категория', + 'Add a new category' => 'Добавяне на нова категория', + 'Do you really want to remove this category: "%s"?' => 'Наистина ли искате да премахнете тази категория: "%s"?', + 'All categories' => 'Всички категории', + 'No category' => 'Без категория', + 'The name is required' => 'Името е задължително', + 'Remove a file' => 'Премахване на файл', + 'Unable to remove this file.' => 'Този файл не може да бъде премахнат.', + 'File removed successfully.' => 'Файла е успешно премахнат', + 'Attach a document' => 'Прикачване на документ', + 'Do you really want to remove this file: "%s"?' => 'Наистина ли искате да премахнете този файл: "%s"?', + 'Attachments' => 'Прикачени файлове', + 'Edit the task' => 'Редактиране на задачата', + 'Add a comment' => 'Добавете забележка', + 'Edit a comment' => 'Редактиране на забележка', + 'Summary' => 'Обобщение', + 'Time tracking' => 'Проследяване на времето', + 'Estimate:' => 'Приблизително:', + 'Spent:' => 'Прекарано:', + 'Do you really want to remove this sub-task?' => 'Наистина ли искате да премахнете тази подзадача ?', + 'Remaining:' => 'Остават:', + 'hours' => 'часа', + 'estimated' => 'Очаквано', + 'Sub-Tasks' => 'Подзадачи', + 'Add a sub-task' => 'Добавяне на подзадача', + 'Original estimate' => 'Първоначална оценка', + 'Create another sub-task' => 'Създаване на друга подзадача', + 'Time spent' => 'Прекарано време', + 'Edit a sub-task' => 'Редактиране на подзадача', + 'Remove a sub-task' => 'Премахване на подзадача', + 'The time must be a numeric value' => 'Времето трябва да бъде цифрова стойност', + 'Todo' => 'Todo', + 'In progress' => 'В ход', + 'Sub-task removed successfully.' => 'Подзадачата е премахната успешно.', + 'Unable to remove this sub-task.' => 'Тази подзадача не може да бъде премахната.', + 'Sub-task updated successfully.' => 'Подзадачата е актуализирана успешно.', + 'Unable to update your sub-task.' => 'Неуспешно актуализиране на подзадачата.', + 'Unable to create your sub-task.' => 'Неуспешно създаване на подзадачата.', + 'Maximum size: ' => 'Максимален размер:', + 'Display another project' => 'Показване на друг проект', + 'Created by %s' => 'Създадена от %s', + 'Tasks Export' => 'Експортиране на задачи', + 'Start Date' => 'Начална дата', + 'Execute' => 'Изпълни', + 'Task Id' => 'ID на задачата', + 'Creator' => 'Създател', + 'Modification date' => 'Дата на промяна', + 'Completion date' => 'Крайна дата', + 'Clone' => 'Клониране', + 'Project cloned successfully.' => 'Проекта е клониран успешно.', + 'Unable to clone this project.' => 'Не може да се клонира този проект.', + 'Enable email notifications' => 'Активиране на известия по имейл', + 'Task position:' => 'Позиция на задачата:', + 'The task #%d has been opened.' => 'Задача #%d е отворена.', + 'The task #%d has been closed.' => 'Задача #%d е затворена.', + 'Sub-task updated' => 'Подзадачата е актуализирана', + 'Title:' => 'Заглавие:', + 'Status:' => 'Статус:', + 'Assignee:' => 'Изпълнител:', + 'Time tracking:' => 'Проследяване на времето:', + 'New sub-task' => 'Нова подзадача', + 'New attachment added "%s"' => 'Добавен е нов прикачен файл "%s"', + 'New comment posted by %s' => 'Нова забележка, публикуван от %s', + 'New comment' => 'Нова забележка', + 'Comment updated' => 'Коментара е актуализиран', + 'New subtask' => 'Нова подзадача', + 'I only want to receive notifications for these projects:' => 'Искам да получавам известия само за тези проекти:', + 'view the task on Kanboard' => 'преглед на задачата в Kanboard', + 'Public access' => 'Публичност', + 'Disable public access' => 'Деактивиране на публичния достъп', + 'Enable public access' => 'Активиране на публичен достъп', + 'Public access disabled' => 'Обществения достъп е деактивиран', + 'Move the task to another project' => 'Преместване на задачата в друг проект', + 'Move to project' => 'Преместване в проект', + 'Do you really want to duplicate this task?' => 'Наистина ли искате да дублирате тази задача ?', + 'Duplicate a task' => 'Дублиране на задача', + 'External accounts' => 'Външни акаунти', + 'Account type' => 'Вид акаунт', + 'Local' => 'Локален', + 'Remote' => 'Отдалечен', + 'Enabled' => 'Активирано', + 'Disabled' => 'Деактивирано', + 'Login:' => ' Вход:', + 'Full Name:' => 'Пълно име:', + 'Email:' => 'Имейл:', + 'Notifications:' => 'Уведомления:', + 'Notifications' => 'Уведомления', + 'Account type:' => 'Вид акаунт:', + 'Edit profile' => 'Промяна на профил', + 'Change password' => 'Смяна на паролата', + 'Password modification' => 'Промяна на паролата', + 'External authentications' => 'Външни удостоверявания', + 'Never connected.' => '- Никога не са се свързвали.', + 'No external authentication enabled.' => 'Няма активирано външно удостоверяване.', + 'Password modified successfully.' => 'Паролата е променена успешно.', + 'Unable to change the password.' => 'Паролата не може да бъде променена.', + 'Change category' => 'Промени категория', + '%s updated the task %s' => '%s актуализира задачата %s', + '%s opened the task %s' => '%s отвори задачата %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s премести задачата %s на позиция #%d в колоната "%s"', + '%s moved the task %s to the column "%s"' => '%s премести задачата %s в колоната "%s"', + '%s created the task %s' => '%s създаде задача %s', + '%s closed the task %s' => '%s затвори задача %s', + '%s created a subtask for the task %s' => '%s създаде подзадача за задача %s', + '%s updated a subtask for the task %s' => '%s актуализира подзадача за задача %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Зададено на %s с приблизителна оценка от %s/%sч', + 'Not assigned, estimate of %sh' => 'Не е зададено, оценка от %sч', + '%s updated a comment on the task %s' => '%s актуализира коментар за задачата %s', + '%s commented the task %s' => '%s коментира задачата %s', + '%s\'s activity' => 'Дейност на %s', + 'RSS feed' => 'RSS канал', + '%s updated a comment 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', + '%s created the task #%d' => '%s създаде задача #%d', + '%s closed the task #%d' => '%s затвори задача #%d', + '%s opened the task #%d' => '%s отвори задача #%d', + 'Activity' => 'Активност', + 'Default values are "%s"' => 'Стойностите по подразбиране са "%s"', + 'Default columns for new projects (Comma-separated)' => 'Колони по подразбиране за нови проекти (разделени със запетая)', + 'Task assignee change' => 'Промяна на изпълнителя на задачата', + '%s changed the assignee of the task #%d to %s' => '%s промени изпълнителя на задачата #%d на %s', + '%s changed the assignee of the task %s to %s' => '%s промени изпълнителя на задачата %s на %s', + 'New password for the user "%s"' => 'Нова парола за потребител "%s" ', + 'Choose an event' => 'Избор на събитие', + 'Create a task from an external provider' => 'Създаване на задача от външен доставчик', + 'Change the assignee based on an external username' => 'Промяна на изпълнителя въз основа на външно потребителско име', + 'Change the category based on an external label' => 'Промяна на категорията въз основа на външен етикет', + 'Reference' => 'Референция', + 'Label' => 'Етикет', + 'Database' => 'База данни', + 'About' => 'Относно', + 'Database driver:' => 'Драйвер на база данни:', + 'Board settings' => 'Настройки на дъската', + 'Webhook settings' => 'Настройка на Webhook', + 'Reset token' => 'Нулиране на токена', + 'API endpoint:' => 'Крайна точка на API:', + 'Refresh interval for personal board' => 'Интервал на опресняване за лична дъска', + 'Refresh interval for public board' => 'Интервал на опресняване за публична дъска', + 'Task highlight period' => 'Период на подчертаване на задачата', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Периодът (в секунди) за разглеждане на задача е променен наскоро (0 за деактивиране, 2 дни по подразбиране)', + 'Frequency in second (60 seconds by default)' => 'Честота в секунди (60 секунди по подразбиране)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Честота в секунди (0 за деактивиране на тази функция, 10 секунди по подразбиране)', + 'Application URL' => 'Url адрес на приложението', + 'Token regenerated.' => '- Токена е регенериран.', + 'Date format' => 'Формат на дата', + 'ISO format is always accepted, example: "%s" and "%s"' => 'Формат ISO винаги се приема, например: "%s" и "%s"', + 'New personal project' => 'Нов личен проект', + 'This project is personal' => 'Този проект е личен', + 'Add' => 'Добави', + 'Start date' => 'Начална дата', + 'Time estimated' => 'Очаквано време', + 'There is nothing assigned to you.' => 'Нищо не Ви е възложено.', + 'My tasks' => 'Моите задачи', + 'Activity stream' => 'Поток на активността', + 'Dashboard' => 'Табло', + 'Confirmation' => 'Потвърждение', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Create a comment from an external provider' => 'Създаване на забележка от външен доставчик', + 'Project management' => 'Управление на проекти', + 'Columns' => 'Колони ', + 'Task' => 'Задача', + 'Percentage' => 'Процент', + 'Number of tasks' => 'Брой задачи', + 'Task distribution' => 'Разпределение на задачите', + 'Analytics' => 'Анализ', + 'Subtask' => 'Подзадача', + 'User repartition' => 'Разпределение на потребителите', + 'Clone this project' => 'Клониране на този проект', + 'Column removed successfully.' => 'Колоната е премахната успешно.', + 'Not enough data to show the graph.' => 'Няма достатъчно данни, за да се покаже графиката.', + 'Previous' => 'Предишна', + 'The id must be an integer' => 'Идентификатора трябва да е цяло число', + 'The project id must be an integer' => 'Идентификатора на проекта трябва да бъде цяло число', + 'The status must be an integer' => 'Статуса трябва да бъде цяло число', + 'The subtask id is required' => 'Изисква се идентификатор на подзадачата', + 'The subtask id must be an integer' => 'Идентификатора на подзадачата трябва да бъде цяло число', + 'The task id is required' => 'Идентификатора на задачата е задължителен', + 'The task id must be an integer' => 'Идентификатора на задачата трябва да бъде цяло число', + 'The user id must be an integer' => 'Потребителския идентификатор трябва да бъде цяло число', + 'This value is required' => 'Тази стойност е задължителна', + 'This value must be numeric' => 'Тази стойност трябва да е цифрова', + 'Unable to create this task.' => 'Тази задача не може да бъде създадена.', + 'Cumulative flow diagram' => 'Схема на кумулативния поток', + 'Daily project summary' => 'Ежедневно резюме на проекта', + 'Daily project summary export' => 'Ежедневен износ на обобщена информация за проекта', + 'Exports' => 'Експорти', + 'This export contains the number of tasks per column grouped per day.' => 'Този експорт съдържа броя задачи на колона, групирани на ден.', + 'Active swimlanes' => 'Активни коридори', + 'Add a new swimlane' => 'Добавяне на нов коридор', + 'Default swimlane' => 'Коридор по подразбиране', + 'Do you really want to remove this swimlane: "%s"?' => 'Наистина ли искате да премахнете този коридор: "%s"?', + 'Inactive swimlanes' => 'Неактивни коридори', + 'Remove a swimlane' => 'Премахване на коридор', + 'Swimlane modification for the project "%s"' => 'Модификация на коридор за проекта "%s"', + 'Swimlane removed successfully.' => 'Коридора е премахнато успешно.', + 'Swimlanes' => 'Коридори', + 'Swimlane updated successfully.' => 'Коридора е актуализиран успешно.', + 'Unable to remove this swimlane.' => 'Невъзможно е да се премахне този коридор.', + 'Unable to update this swimlane.' => 'Неуспешно актуализиране на този коридор.', + 'Your swimlane has been created successfully.' => 'Вашия коридор е създадена успешно.', + 'Example: "Bug, Feature Request, Improvement"' => 'Пример: „Грешка, Заявка за функционалност, Подобрение“', + 'Default categories for new projects (Comma-separated)' => 'Категории по подразбиране за нови проекти (разделени със запетая)', + 'Integrations' => 'Интеграции', + 'Integration with third-party services' => 'Интеграция с услуги на трети страни', + 'Subtask Id' => 'ID на подзадачата', + 'Subtasks' => 'Подзадачи', + 'Subtasks Export' => 'Експортиране на подзадачи', + 'Task Title' => 'Заглавие на задача', + 'Untitled' => 'Неозаглавено', + 'Application default' => 'По подразбиране на приложението', + 'Language:' => 'Език:', + 'Timezone:' => 'Часова зона:', + 'All columns' => 'Всички колони', + 'Next' => 'Напред', + '#%d' => '#%d', + 'All swimlanes' => 'Всички коридори', + 'All colors' => 'Всички цветове', + 'Moved to column %s' => 'Преместено в колона %s', + 'User dashboard' => 'Потребителско табло за управление', + 'Allow only one subtask in progress at the same time for a user' => 'Разрешаване само на една подзадача в ход по едно и също време за даден потребител', + 'Edit column "%s"' => 'Редактиране на колона "%s" ', + '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?' => 'Коя част от проекта искате да дублирате?', + 'Disallow login form' => 'Забраняване на формуляра за вход', + 'Start' => 'Старт', + 'End' => 'Край', + 'Task age in days' => 'Възраст на задачата в дни', + 'Days in this column' => 'Дни в тази колона', + '%dd' => '%dd', + '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' => 'Промяна на връзката', + 'Opposite label' => 'Противоположен етикет', + 'Remove a link' => 'Изтриване на връзка.', + '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' => '<1ч', + '%dh' => '%dч', + 'Expand tasks' => 'Разгъване на задачите', + 'Collapse tasks' => 'Свиване на задачите', + 'Expand/collapse tasks' => 'Разгъване/свиване на задачи', + 'Close dialog box' => 'Затваряне на диалоговия прозорец', + 'Submit a form' => 'Подаване на формуляр', + 'Board view' => 'Изглед на дъската', + 'Keyboard shortcuts' => 'Преки пътища от клавиатурата', + 'Open board switcher' => 'Превключвател на дъски', + 'Application' => 'Приложение', + 'Compact view' => 'Компактен изглед', + 'Horizontal scrolling' => 'Хоризонтално превъртане', + 'Compact/wide view' => 'Компактен/широк изглед', + 'Currency' => 'Валута', + 'Personal project' => 'Персонален проект', + 'AUD - Australian Dollar' => 'AUD - австралийски долар', + 'CAD - Canadian Dollar' => 'CAD - канадски долар', + 'CHF - Swiss Francs' => 'CHF - швейцарски франкове', + 'Custom Stylesheet' => 'Персонализиран стилов лист', + 'EUR - Euro' => 'EUR - евро ', + 'GBP - British Pound' => 'GBP - британски паунд', + 'INR - Indian Rupee' => 'INR - индийска рупия', + 'JPY - Japanese Yen' => 'JPY - японска йена', + 'NZD - New Zealand Dollar' => 'NZD - Нова Зеландия долар', + 'PEN - Peruvian Sol' => 'PEN - Перуански сол', + 'RSD - Serbian dinar' => 'RSD - сръбски динар', + 'CNY - Chinese Yuan' => 'CNY - китайски юан', + 'USD - US Dollar' => 'USD - щатски долар', + 'VES - Venezuelan Bolívar' => 'VES - Венецуелски боливар', + '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' => 'Колона източник', + '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' => 'Промяна на референтната валута', + 'Reference currency' => 'Референтна валута', + 'The currency rate has been added successfully.' => 'Валутния курс е добавен успешно.', + 'Unable to add this currency rate.' => 'Не може да се добави този валутен курс.', + 'Webhook URL' => 'Webhook URL', + '%s removed the assignee of the task %s' => '%s премахна изпълнителя на задачата %s', + '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' => 'Двустепенно удостоверяване', + 'This QR code contains the key URI: ' => 'Този QR код съдържа ключовия URI адрес:', + 'Check my code' => 'Проверка на кода ми', + 'Secret key: ' => 'Таен ключ: ', + 'Test your device' => 'Тествайте устройството си', + 'Assign a color when the task is moved to a specific column' => 'Задаване на цвят при преместване на задачата в конкретна колона', + '%s via Kanboard' => '%s чрез Kanboard', + 'Burndown chart' => 'Burndown chart', + 'This chart show the task complexity over the time (Work Remaining).' => 'Тази диаграма показва сложността на задачата във времето (Оставаща работа).', + 'Screenshot taken %s' => 'Екранната снимка е направена %s', + 'Add a screenshot' => 'Добавяне на екранна снимка', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Направете екранна снимка и натиснете CTRL+V или ⌘+V, за да поставите тук.', + 'Screenshot uploaded successfully.' => 'Екранната снимка е качена успешно.', + 'SEK - Swedish Krona' => 'SEK - шведска крона', + 'Identifier' => 'Идентификатор', + 'Disable two factor authentication' => 'Деактивиране на двустепенно удостоверяване', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Наистина ли искате да деактивирате двуфакторното удостоверяване за този потребител: "%s"?', + 'Edit link' => 'Редактиране на връзка', + 'Start to type task title...' => 'Започнете да въвеждате заглавие на задача...', + 'A task cannot be linked to itself' => 'Задачата не може да бъде свързана сама със себе си', + 'The exact same link already exists' => 'Същата връзка вече съществува', + 'Recurrent task is scheduled to be generated' => 'Планирано е генериране на повтаряща се задача', + 'Score' => 'Резултат', + 'The identifier must be unique' => 'Идентификатора трябва да бъде уникален', + 'This linked task id doesn\'t exists' => 'Този свързан идентификатор на задача не съществува', + 'This value must be alphanumeric' => 'Тази стойност трябва да бъде буквено-цифрова', + 'Edit recurrence' => 'Редактиране на повторение', + 'Generate recurrent task' => 'Генериране на повтаряща се задача', + 'Trigger to generate recurrent task' => 'Действие за генериране на повтаряща се задача', + 'Factor to calculate new due date' => 'Фактор за изчисляване на нов краен срок', + 'Timeframe to calculate new due date' => 'Срок за изчисляване на нов краен срок', + 'Base date to calculate new due date' => 'Базова дата за изчисляване на нов краен срок', + 'Action date' => 'Дата на действие', + 'Base date to calculate new due date: ' => 'Базова дата за изчисляване на нов краен срок:', + 'This task has created this child task: ' => 'Тази задача създаде тази дъщерна задача:', + 'Day(s)' => 'ден(и)', + 'Existing due date' => 'Съществуващ краен срок', + 'Factor to calculate new due date: ' => 'Фактор за изчисляване на нов краен срок: ', + 'Month(s)' => 'Месец(и)', + 'This task has been created by: ' => 'Тази задача е създадена от: ', + 'Recurrent task has been generated:' => 'Генерирана е повтаряща се задача:', + 'Timeframe to calculate new due date: ' => 'Срок за изчисляване на нов краен срок: ', + 'Trigger to generate recurrent task: ' => 'Действие за генериране на повтаряща се задача:', + 'When task is closed' => 'Когато задачата е затворена', + 'When task is moved from first column' => 'Когато задачата е преместена от първата колона', + 'When task is moved to last column' => 'Когато задачата е преместена в последната колона', + 'Year(s)' => 'година(и)', + 'Project settings' => 'Настройки на проекта', + 'Automatically update the start date' => 'Автоматично актуализиране на началната дата', + 'iCal feed' => 'iCal Feed', + 'Preferences' => 'Предпочитания', + 'Security' => 'Защита', + 'Two factor authentication disabled' => 'Двуфакторното удостоверяване е деактивирано', + 'Two factor authentication enabled' => 'Активирано е двуфакторно удостоверяване', + 'Unable to update this user.' => 'Неуспешно актуализиране на този потребител.', + 'There is no user management for personal projects.' => 'Няма управление на потребителите за лични проекти.', + 'User that will receive the email' => 'Потребител, който ще получи имейла', + 'Email subject' => 'Тема на имейла', + 'Date' => 'Дата', + 'Add a comment log when moving the task between columns' => 'Добавяне на бележка в коментарите при преместване на задачата между колоните', + 'Move the task to another column when the category is changed' => 'Преместване на задачата в друга колона при промяна на категория', + 'Send a task by email to someone' => 'Изпращане на задача по имейл до някого', + 'Reopen a task' => 'Повторно отваряне на задача', + 'Notification' => 'Уведомяване', + '%s moved the task #%d to the first swimlane' => '%s премести задачата #%d на първи коридор', + 'Swimlane' => 'Коридор', + '%s moved the task %s to the first swimlane' => '%s премести задачата %s на първи коридор', + '%s moved the task %s to the swimlane "%s"' => '%s премести задачата %s в коридор "%s"', + 'This report contains all subtasks information for the given date range.' => 'Този отчет съдържа цялата информация за подзадачите за дадения период от време.', + 'This report contains all tasks information for the given date range.' => 'Този отчет съдържа цялата информация за задачите за дадения период от време.', + 'Project activities for %s' => 'Проектни дейности за %s', + 'view the board on Kanboard' => 'преглед на дъската на Kanboard', + 'The task has been moved to the first swimlane' => 'Задачата е преместена на първи коридор', + 'The task has been moved to another swimlane:' => '- Задачата е преместена в друг коридор:', + 'New title: %s' => 'Ново заглавие: %s', + 'The task is not assigned anymore' => 'Задачата вече не е възложена', + 'New assignee: %s' => 'Нов изпълнител: %s', + 'There is no category now' => 'Вече няма категория', + 'New category: %s' => 'Нова категория: %s', + 'New color: %s' => 'Нов цвят: %s', + 'New complexity: %d' => 'Нова сложност: %d', + 'The due date has been removed' => 'Крайния срок е премахнат', + 'There is no description anymore' => 'Вече няма описание', + 'Recurrence settings has been modified' => 'Настройките за повторение са променени', + 'Time spent changed: %sh' => 'Времето, прекарано в работа, се промени: %sч', + 'Time estimated changed: %sh' => 'Променено очаквано време: %sч', + 'The field "%s" has been updated' => 'Полето "%s" е актуализирано', + 'The description has been modified:' => 'Описанието е променено:', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Наистина ли искате да затворите задачата "%s", както и всички подзадачи ?', + 'I want to receive notifications for:' => 'Искам да получавам известия за:', + 'All tasks' => 'Всички задачи', + 'Only for tasks assigned to me' => 'Само за задачи, които са ми възложени', + 'Only for tasks created by me' => 'Само за задачи, създадени от мен', + 'Only for tasks created by me and tasks assigned to me' => 'Само за задачи, създадени от мен, и задачи, възложени на мен', + '%%Y-%%m-%%d' => '%%Y-%%m-%%d', + 'Total for all columns' => 'Общо за всички колони', + 'You need at least 2 days of data to show the chart.' => 'Необходими са данни за поне два дена, за да се покаже тази диаграмата.', + '<15m' => '<15м', + '<30m' => '30m', + 'Stop timer' => 'Таймер за спиране', + 'Start timer' => 'Старт таймер', + 'My activity stream' => 'Мой поток от дейности', + 'Search tasks' => 'Задачи за търсене', + 'Reset filters' => 'Нулиране на филтрите', + 'My tasks due tomorrow' => 'Моите задачи, които изтичат утре', + 'Tasks due today' => 'Задачи, изтичащи днес', + 'Tasks due tomorrow' => 'Задачи, изтичащи утре', + 'Tasks due yesterday' => 'Задачи, изтекли вчера', + 'Closed tasks' => 'Приключени задачи', + 'Open tasks' => 'Отворени задачи', + 'Not assigned' => 'Не е възложено на никого', + 'View advanced search syntax' => 'Преглед на синтаксиса за разширено търсене', + 'Overview' => 'Преглед', + 'Board/Calendar/List view' => 'Изглед на табло/календар/списък', + 'Switch to the board view' => 'Превключване към изгледа на дъската', + 'Switch to the list view' => 'Превключване към изглед на списък', + 'Go to the search/filter box' => 'Към полето за търсене/филтриране', + 'There is no activity yet.' => 'Все още няма активност.', + 'No tasks found.' => 'Няма намерени задачи', + 'Keyboard shortcut: "%s"' => 'Клавишна комбинация: "%s"', + 'List' => 'Списък', + 'Filter' => 'Филтър', + 'Advanced search' => 'Разширено търсене', + 'Example of query: ' => 'Пример за заявка:', + 'Search by project: ' => 'Търсене по проект:', + 'Search by column: ' => 'Търсене по колона:', + 'Search by assignee: ' => 'Търсене по изпълнител:', + 'Search by color: ' => 'Търсене по цвят:', + 'Search by category: ' => 'Търсене по категория', + 'Search by description: ' => 'Търсене по описание:', + 'Search by due date: ' => 'Търсене по краен срок:', + 'Average time spent in each column' => 'Средно време, прекарано във всяка колона', + 'Average time spent' => 'Средно прекарано време', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Тази диаграма показва средното време, прекарано във всяка колона за последните %d задачи.', + 'Average Lead and Cycle time' => 'Средно време за изпълнение и цикъл', + 'Average lead time: ' => 'Средно време за изпълнение:', + 'Average cycle time: ' => 'Средно време на цикъла:', + 'Cycle Time' => 'Време на цикъла', + 'Lead Time' => 'Време за изпълнение', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Тази диаграма показва средното време за изпълнение и цикъл за последните %d задачи във времето.', + 'Average time into each column' => 'Средно време във всяка колона', + 'Lead and cycle time' => 'Време за изпълнение и цикъл', + 'Lead time: ' => 'Време за изпълнение:', + 'Cycle time: ' => 'Времетраене на цикъла:', + 'Time spent in each column' => 'Време, прекарано във всяка колона', + 'The lead time is the duration between the task creation and the completion.' => 'Времето за изпълнение е продължителността между създаването на задачата и завършването ѝ.', + 'The cycle time is the duration between the start date and the completion.' => 'Времето на цикъла е продължителността между началната дата и завършването.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Ако задачата не е затворена, се използва текущото време вместо датата на завършване.', + 'Set the start date automatically' => 'Автоматично задаване на началната дата', + 'Edit Authentication' => 'Редактиране на удостоверяване', + 'Remote user' => 'Отдалечен потребител', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Отдалечените потребители не съхраняват паролата си в базата данни на Kanboard, примери: LDAP, Google и Github акаунти.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Ако поставите отметка в квадратчето„ Забрана на формуляра за вход “, въведените във формуляра за вход идентификационни данни ще бъдат игнорирани.', + 'Default task color' => 'Цвят на задачата по подразбиране', + 'This feature does not work with all browsers.' => 'Тази функция не работи с всички браузъри.', + 'There is no destination project available.' => 'Няма наличен проект за местоназначение.', + 'Trigger automatically subtask time tracking' => 'Задействане на автоматично проследяване на времето за подзадачи', + 'Include closed tasks in the cumulative flow diagram' => 'Включване на затворени задачи в диаграмата на кумулативния поток', + 'Current swimlane: %s' => 'Текущ коридор: %s', + 'Current column: %s' => 'Текуща колона: %s', + 'Current category: %s' => 'Текуща категория: %s', + 'no category' => 'без категория', + 'Current assignee: %s' => 'Текущ изпълнител: %s', + 'not assigned' => 'Не е зададено', + 'Author:' => 'Автор', + 'contributors' => 'Сътрудници', + 'License:' => 'Лиценз:', + 'License' => 'Лиценз', + 'Enter the text below' => 'Въведете текста по-долу', + 'Start date:' => 'Начална дата:', + 'Due date:' => 'Краен срок:', + 'People who are project managers' => 'Хора, които са ръководители на проекти', + 'People who are project members' => 'Хора, които са членове на проекта', + 'NOK - Norwegian Krone' => 'NOK - норвежка крона ', + 'Show this column' => 'Показване на тази колона', + 'Hide this column' => 'Скриване на тази колона', + 'End date' => 'Крайна дата', + 'Users overview' => 'Общ преглед на потребителите', + 'Members' => 'Членове', + 'Shared project' => 'Споделен проект', + 'Project managers' => 'Мениджъри на проекти', + 'Projects list' => 'Списък с проекти', + 'End date:' => 'Крайна дата:', + 'Change task color when using a specific task link' => 'Промяна на цвета на задачата при използване на конкретна връзка към задача', + 'Task link creation or modification' => 'Създаване или модифициране на връзка към задача', + 'Milestone' => 'Milestone', + 'Reset the search/filter box' => 'Нулиране на полето за търсене/филтриране', + 'Documentation' => 'Документация', + 'Author' => 'Автор', + 'Version' => 'Версия', + 'Plugins' => 'Плъгини', + 'There is no plugin loaded.' => 'Няма зареден плъгин.', + 'My notifications' => 'Моите известия', + 'Custom filters' => 'Потребителски филтри', + 'Your custom filter has been created successfully.' => 'Вашия персонализиран филтър е създаден успешно.', + 'Unable to create your custom filter.' => 'Неуспешно създаване на персонализиран филтър.', + 'Custom filter removed successfully.' => 'Персонализирания филтър е премахнат успешно.', + 'Unable to remove this custom filter.' => 'Не може да се премахне този персонализиран филтър.', + 'Edit custom filter' => 'Редактиране на персонализиран филтър', + 'Your custom filter has been updated successfully.' => 'Вашия персонализиран филтър е актуализиран успешно.', + 'Unable to update custom filter.' => 'Неуспешно актуализиране на персонализирания филтър.', + 'Web' => 'Интернет страница', + 'New attachment on task #%d: %s' => 'Нов прикачен файл за задача #%d: %s', + 'New comment on task #%d' => 'Нов коментар за задача #%d', + 'Comment updated on task #%d' => 'Забележката е актуализирана за задача #%d', + 'New subtask on task #%d' => 'Нова подзадача за задача #%d', + 'Subtask updated on task #%d' => 'Подзадачата е актуализирана при задача #%d', + 'New task #%d: %s' => 'Нова задача #%d: %s', + 'Task updated #%d' => 'Задача #%d е актуализирана', + 'Task #%d closed' => 'Задача #%d е затворена', + 'Task #%d opened' => 'Задача #%d е отворена', + 'Column changed for task #%d' => 'Променена колона на задача #%d', + 'New position for task #%d' => 'Нова позиция за задача #%d', + 'Swimlane changed for task #%d' => 'Променен коридор за задача #%d', + 'Assignee changed on task #%d' => 'Променен изпълнител при задача #%d', + '%d overdue tasks' => '%d просрочени задачи', + 'No notification.' => 'Няма известия.', + 'Mark all as read' => 'Отбележи всички като прочетени', + 'Mark as read' => 'Маркирай като прочетено', + 'Total number of tasks in this column across all swimlanes' => 'Общ брой задачи в тази колона във всички коридори', + 'Collapse swimlane' => 'Свиване на коридор', + 'Expand swimlane' => 'Разширяване на коридор', + 'Add a new filter' => 'Добавяне на нов филтър', + 'Share with all project members' => 'Споделяне с всички членове на проекта', + 'Shared' => 'Споделено', + 'Owner' => 'Собственик ', + 'Unread notifications' => 'Непрочетени известия', + 'Notification methods:' => 'Начини за уведомяване:', + 'Unable to read your file' => 'Файла не може да бъде прочетен.', + '%d task(s) have been imported successfully.' => '%d задача(и) са импортирани успешно.', + 'Nothing has been imported!' => 'Нищо не е импортирано!', + 'Import users from CSV file' => 'Импортиране на потребители от CSV', + '%d user(s) have been imported successfully.' => '%d потребител(и) са импортирани успешно.', + 'Comma' => 'Запетайка', + 'Semi-colon' => 'Точка и запетая', + 'Tab' => 'Tab', + 'Vertical bar' => 'Вертикална лента', + 'Double Quote' => 'Двойни кавички', + 'Single Quote' => 'Кавичка', + '%s attached a file to the task #%d' => '%s прикачи файл към задача #%d', + 'There is no column or swimlane activated in your project!' => 'Във вашия проект няма активирана колона или коридор!', + 'Append filter (instead of replacement)' => 'Добавяне на филтър (вместо замяна)', + 'Append/Replace' => 'Добавяне/замяна', + 'Append' => 'Добавяне', + 'Replace' => 'Замяна', + 'Import' => 'Импорт', + 'Change sorting' => 'Промяна на сортирането', + 'Tasks Importation' => 'Импортиране на задачи', + 'Delimiter' => 'Разделител', + 'Enclosure' => 'Затварящ символ', + 'CSV File' => 'CSV файл', + 'Instructions' => 'Инструкции', + 'Your file must use the predefined CSV format' => 'Вашия файл трябва да използва предварително дефиниран CSV формат', + 'Your file must be encoded in UTF-8' => 'Вашия файл трябва да бъде кодиран в UTF-8', + 'The first row must be the header' => 'Първия ред трябва да бъде заглавието', + 'Duplicates are not verified for you' => 'Дубликатите не са проверени за вас', + 'The due date must use the ISO format: YYYY-MM-DD' => 'Крайния срок трябва да използва ISO формат: ГГГГ-ММ-ДД', + 'Download CSV template' => 'Изтегляне на CSV шаблон', + 'No external integration registered.' => 'Няма регистрирана външна интеграция.', + 'Duplicates are not imported' => 'Дубликатите не се импортират', + 'Usernames must be lowercase and unique' => 'Потребителските имена трябва да са с малки букви и уникални', + 'Passwords will be encrypted if present' => 'Паролите ще бъдат шифровани, ако са налице', + '%s attached a new file to the task %s' => '%s прикачи нов файл към задачата %s', + 'Link type' => 'Вид на връзката', + 'Assign automatically a category based on a link' => 'Автоматично присвояване на категория въз основа на връзка', + 'BAM - Konvertible Mark' => 'BAM - Конвертируема марка', + 'Assignee Username' => 'Потребителско име на изпълнител', + 'Assignee Name' => 'Име на изпълнител', + 'Groups' => 'Групи', + 'Members of %s' => 'Членове на %s', + 'New group' => 'Нова група', + 'Group created successfully.' => 'Групата е създадена успешно', + 'Unable to create your group.' => 'Неуспешно създаване на вашата група.', + 'Edit group' => 'Редактиране на група', + 'Group updated successfully.' => 'Групата е актуализирана успешно', + 'Unable to update your group.' => 'Неуспешно актуализиране на вашата група.', + 'Add group member to "%s"' => 'Добавяне на групов член към "%s"', + 'Group member added successfully.' => 'Успешно добавен член на група.', + 'Unable to add group member.' => 'Неуспешно добавяне на групов член.', + 'Remove user from group "%s"' => 'Премахни потребителя от група "%s"', + 'User removed successfully from this group.' => 'Потребителя е премахнат успешно от тази група.', + 'Unable to remove this user from the group.' => 'Този потребител не може да бъде премахнат от групата.', + 'Remove group' => 'Премахване', + 'Group removed successfully.' => 'Групата е премахната успешно.', + 'Unable to remove this group.' => 'Тази група не може да бъде премахната.', + 'Project Permissions' => 'Права за проекти', + 'Manager' => 'Мениджър', + 'Project Manager' => 'Ръководител на проект', + 'Project Member' => 'Член на проекта', + 'Project Viewer' => 'Наблюдател на проекта', + 'Your account is locked for %d minutes' => 'Вашия акаунт е заключен за %d минути', + 'Invalid captcha' => 'Невалиден Captcha', + 'The name must be unique' => 'Името трябва да е уникално', + 'View all groups' => 'Преглед на всички групи', + 'There is no user available.' => 'Няма наличен потребител.', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Наистина ли искате да премахнете потребителя "%s" от групата "%s"?', + 'There is no group.' => 'Няма група.', + 'Add group member' => 'Добавяне на член на групата', + 'Do you really want to remove this group: "%s"?' => 'Наистина ли искате да премахнете тази група: "%s"?', + 'There is no user in this group.' => 'Няма потребител в тази група.', + 'Permissions' => 'Права', + 'Allowed Users' => 'Разрешени потребители', + 'No specific user has been allowed.' => 'Не е разрешен конкретен потребител.', + 'Role' => 'Роля', + 'Enter user name...' => 'Въведи потребителско име', + 'Allowed Groups' => 'Разрешени групи', + 'No group has been allowed.' => 'Никоя група не е разрешена.', + 'Group' => 'Група', + 'Group Name' => 'Име на група', + 'Enter group name...' => 'Въведете име на групата...', + 'Role:' => 'Роля:', + 'Project members' => 'Членове на проекта', + '%s mentioned you in the task #%d' => '%s ви спомена в задача #%d', + '%s mentioned you in a comment on the task #%d' => '%s ви спомена в забележката на задачата #%d', + 'You were mentioned in the task #%d' => 'Бяхте споменати в задачата #%d', + 'You were mentioned in a comment on the task #%d' => 'Споменати сте в забележка на задачата #%d', + 'Estimated hours: ' => 'Очаквани часове:', + 'Actual hours: ' => 'Действително работно време:', + 'Hours Spent' => 'Прекарани часове', + 'Hours Estimated' => 'Очаквани часове', + 'Estimated Time' => 'Прогнозен час', + 'Actual Time' => 'Действително време', + 'Estimated vs actual time' => 'Очаквано спрямо действително време', + 'RUB - Russian Ruble' => 'RUB - руска рубла', + 'Assign the task to the person who does the action when the column is changed' => 'Възлагане на задачата на лицето, което извършва действието, при промяна на колоната', + 'Close a task in a specific column' => 'Затваряне на задача в конкретна колона', + 'Time-based One-time Password Algorithm' => 'Алгоритъм за еднократна парола, базиран на време', + 'Two-Factor Provider: ' => 'Двуфакторен доставчик: ', + 'Disable two-factor authentication' => 'Деактивиране на двуфакторно удостоверяване', + 'Enable two-factor authentication' => 'Активиране на двуфакторно удостоверяване', + 'There is no integration registered at the moment.' => 'В момента няма регистрирана интеграция.', + 'Password Reset for Kanboard' => 'Нулиране на паролата за Kanboard', + 'Forgot password?' => 'Забравена парола', + 'Enable "Forget Password"' => 'Активиране на "Забравена парола"', + 'Password Reset' => ' Възстановяване на парола', + 'New password' => 'Нова парола', + 'Change Password' => 'Смяна на паролата', + 'To reset your password click on this link:' => 'За да нулирате паролата си, щракнете върху тази връзка:', + 'Last Password Reset' => 'Нулиране на последната парола', + 'The password has never been reinitialized.' => 'Паролата никога не е била инициализирана отново.', + 'Creation' => 'Създаване', + 'Expiration' => 'Изтича на', + 'Password reset history' => 'Хронология на нулирането на паролата', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Всички задачи на колоната "%s" и коридор "%s" са затворени успешно.', + 'Do you really want to close all tasks of this column?' => 'Наистина ли искате да затворите всички задачи в тази колона ?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d задача(и) в колоната "%s" и коридор "%s" ще бъдат затворени.', + 'Close all tasks in this column and this swimlane' => 'Затворете всички задачи в тази колона и този коридор', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Нито един плъгин не е регистрирал метод за уведомяване за проекта. Все още можете да конфигурирате отделни известия в потребителския си профил.', + 'My dashboard' => 'Моето табло', + 'My profile' => 'Моя профил', + 'Project owner: ' => 'Собственик на проекта: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'Идентификатора на проекта не е задължителен и трябва да бъде буквено-цифров, например: MYPROJECT.', + 'Project owner' => 'Собственик на проекта', + 'Personal projects do not have users and groups management.' => 'Личните проекти нямат управление на потребители и групи.', + 'There is no project member.' => 'Няма член на проекта.', + 'Priority' => 'Priority', + 'Task priority' => 'Приоритет на задачата', + 'General' => 'Общи', + 'Dates' => 'Дати', + 'Default priority' => 'Приоритет по подразбиране', + 'Lowest priority' => 'Най-ниския приоритет', + 'Highest priority' => 'Най-високия приоритет', + 'Close a task when there is no activity' => 'Затваряне на задача, когато няма активност', + 'Duration in days' => 'Продължителност в дни', + 'Send email when there is no activity on a task' => 'Изпращане на имейл, когато няма активност по дадена задача', + 'Unable to fetch link information.' => 'Не може да се извлече информация за връзката.', + 'Daily background job for tasks' => 'Ежедневно фоново задание за задачи', + 'Auto' => 'Авто', + 'Related' => 'Свързано', + 'Attachment' => 'Прикачен файл', + 'Web Link' => 'Препратка в Интернет', + 'External links' => 'Външна връзка', + 'Add external link' => 'Добавяне на външна връзка', + 'Type' => 'Тип', + 'Dependency' => 'Зависимост', + 'Add internal link' => 'Добавяне на вътрешна връзка', + 'Add a new external link' => 'Добавяне на нова външна връзка', + 'Edit external link' => 'Редактиране на външна връзка', + 'External link' => 'Външен линк', + 'Copy and paste your link here...' => 'Копирайте и поставете връзката си тук...', + 'URL' => 'URL', + 'Internal links' => 'Вътрешни връзки', + 'Assign to me' => 'Присвояване на мен', + 'Me' => 'Аз', + 'Do not duplicate anything' => 'Не дублирай нищо', + 'Projects management' => 'Управление на проекти', + 'Users management' => 'Управление на потребителите', + 'Groups management' => 'Управление на групи', + 'Create from another project' => 'Създаване от друг проект', + 'open' => 'отворено', + 'closed' => 'затворено', + 'Priority:' => 'Приоритет:', + 'Reference:' => 'Референция:', + 'Complexity:' => 'Сложност:', + 'Swimlane:' => 'Коридор:', + 'Column:' => 'Колона:', + 'Position:' => 'Позиция:', + 'Creator:' => 'Създател:', + 'Time estimated:' => 'Очаквано време:', + '%s hours' => '%s часове', + 'Time spent:' => 'Прекарано време:', + 'Created:' => 'Създадено:', + 'Modified:' => 'Модифицирано:', + 'Completed:' => 'Завършено:', + 'Started:' => 'Стартирано:', + 'Moved:' => 'Преместено:', + 'Task #%d' => 'Задача #%d', + 'Time format' => 'Времеви формат', + 'Start date: ' => 'Начална дата:', + 'End date: ' => 'Крайна дата:', + 'New due date: ' => 'Нов краен срок:', + 'Start date changed: ' => 'Началната дата е променена:', + 'Disable personal projects' => 'Деактивиране на лични проекти', + 'Do you really want to remove this custom filter: "%s"?' => 'Наистина ли искате да премахнете този персонализиран филтър: "%s"?', + 'Remove a custom filter' => 'Премахване на персонализиран филтър', + 'User activated successfully.' => 'Потребителя е активиран успешно!', + 'Unable to enable this user.' => 'Неуспешно активиране на този потребител.', + 'User disabled successfully.' => 'Потребителя е деактивиран успешно.', + 'Unable to disable this user.' => 'Този потребител не може да бъде деактивиран.', + 'All files have been uploaded successfully.' => 'Всички файлове са качени успешно.', + 'The maximum allowed file size is %sB.' => 'Максималния разрешен размер на файла е %sB.', + 'Drag and drop your files here' => 'Плъзнете и пуснете файловете си тук', + 'choose files' => 'Изберете файлове', + 'View profile' => 'Виж профил', + 'Two Factor' => 'Двуфакторен', + 'Disable user' => 'Деактивиране на потребител', + 'Do you really want to disable this user: "%s"?' => 'Наистина ли искате да деактивирате този потребител: "%s"?', + 'Enable user' => 'Активиране на потребител', + 'Do you really want to enable this user: "%s"?' => 'Наистина ли искате да активирате този потребител: "%s"?', + 'Download' => 'Изтегли', + 'Uploaded: %s' => 'Качено: %s', + 'Size: %s' => 'Размер: %s', + 'Uploaded by %s' => 'Качено от %s', + 'Filename' => 'Име на файл', + 'Size' => 'Размер', + 'Column created successfully.' => 'Колоната е създадена успешно.', + 'Another column with the same name exists in the project' => 'Друга колона със същото име съществува в проекта', + 'Default filters' => 'Филтри по подразбиране', + 'Your board doesn\'t have any columns!' => 'Вашето табло няма колони!', + 'Change column position' => 'Промяна на позиция в колоната', + 'Switch to the project overview' => 'Превключване към общ преглед на проекта', + 'User filters' => 'Потребителски филтри', + 'Category filters' => 'Филтри за категории', + 'Upload a file' => 'Качи файл', + 'View file' => 'Преглед', + 'Last activity' => 'Последна дейност', + 'Change subtask position' => 'Промяна позицията на подзадача', + 'This value must be greater than %d' => 'Тази стойност трябва да е по-голяма от %d', + 'Another swimlane with the same name exists in the project' => 'В проекта съществува друг коридор със същото име', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Пример: https://example.kanboard.org/ (използва се за генериране на абсолютни URL адреси)', + 'Actions duplicated successfully.' => 'Действията са дублирани успешно.', + 'Unable to duplicate actions.' => 'Не може да се дублират действия.', + 'Add a new action' => 'Добавяне на нова операция', + 'Import from another project' => 'Импортиране от друг проект', + 'There is no action at the moment.' => 'В момента няма действия.', + 'Import actions from another project' => 'Импортиране на действия от друг проект', + 'There is no available project.' => 'Няма наличен проект.', + 'Local File' => 'Локален файл', + 'Configuration' => 'Конфигурация', + 'PHP version:' => 'Версия на PHP:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'Версия на операционната система:', + 'Database version:' => 'Версия на базата данни:', + 'Browser:' => 'Браузър:', + 'Task view' => 'Изглед на задачите', + 'Edit task' => 'Редактиране на задача', + 'Edit description' => 'Редактиране на описанието', + 'New internal link' => 'Нова вътрешна връзка', + 'Display list of keyboard shortcuts' => 'Показване на списък с клавишни комбинации', + 'Avatar' => 'Профилна снимка', + 'Upload my avatar image' => 'Качване на профилна снимка', + 'Remove my image' => 'Премахване на изображението ми', + 'The OAuth2 state parameter is invalid' => 'Параметъра на състоянието OAuth2 е невалиден', + 'User not found.' => 'Потребителя не е намерен', + 'Search in activity stream' => 'Търсене в поток от дейности', + 'My activities' => 'Списък с мойта активност', + 'Activity until yesterday' => 'Активност до вчера', + 'Activity until today' => 'Дейност до днес', + 'Search by creator: ' => 'Търсене по създател:', + 'Search by creation date: ' => 'Търсене по дата на създаване:', + 'Search by task status: ' => 'Търсене по статус на задача:', + 'Search by task title: ' => 'Търсене по заглавие на задача:', + 'Activity stream search' => 'Търсене на поток от дейности', + 'Projects where "%s" is manager' => 'Проекти, в които "%s" е мениджър', + 'Projects where "%s" is member' => 'Проекти, в които "%s" е член', + 'Open tasks assigned to "%s"' => 'Отварени задачи, възложени на "%s"', + 'Closed tasks assigned to "%s"' => 'Затворени задачи, възложени на "%s"', + 'Assign automatically a color based on a priority' => 'Автоматично задаване на цвят въз основа на приоритет', + 'Overdue tasks for the project(s) "%s"' => 'Просрочени задачи за проекта/ите "%s"', + 'Upload files' => 'Качване на файлове', + 'Installed Plugins' => 'Инсталирани плъгини', + 'Plugin Directory' => 'Директория на плъгини', + 'Plugin installed successfully.' => 'Плъгина е инсталиран успешно.', + 'Plugin updated successfully.' => 'Плъгина е актуализиран успешно.', + 'Plugin removed successfully.' => 'Плъгина е премахнат успешно.', + 'Subtask converted to task successfully.' => 'Подзадачата е конвертирана успешно в задача.', + 'Unable to convert the subtask.' => 'Неуспешно конвертиране на подзадачата.', + 'Unable to extract plugin archive.' => 'Неуспешно извличане от архива на плъгина.', + 'Plugin not found.' => 'Плъгина не е намерен.', + 'You don\'t have the permission to remove this plugin.' => 'Нямате права да премахнете този плъгин.', + 'Unable to download plugin archive.' => 'Не може да се изтегли архив на плъгините.', + 'Unable to write temporary file for plugin.' => 'Не може да се запише временен файл за плъгин.', + 'Unable to open plugin archive.' => 'Архива на плъгините не може да се отвори.', + 'There is no file in the plugin archive.' => 'Няма файл в архива на плъгините.', + 'Create tasks in bulk' => 'Групово създаване на задачи', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Вашия екземпляр на Kanboard не е конфигуриран да инсталира приставки от потребителския интерфейс.', + 'There is no plugin available.' => 'Няма наличен плъгин.', + 'Install' => 'Инсталирай', + 'Update' => 'Актуализация', + 'Up to date' => 'Обновен', + 'Not available' => 'Не е на разположение', + 'Remove plugin' => 'Премахване на плъгин', + 'Do you really want to remove this plugin: "%s"?' => 'Наистина ли искате да премахнете този плъгин: "%s"?', + 'Uninstall' => 'Деинсталирай', + 'Listing' => 'Списък', + 'Metadata' => 'Метаданни', + 'Manage projects' => 'Управление на проекти', + 'Convert to task' => 'Превръщане в задача', + 'Convert sub-task to task' => 'Преобразуване на подзадача в задача', + 'Do you really want to convert this sub-task to a task?' => 'Наистина ли искате да преобразувате тази подзадача в задача?', + 'My task title' => 'Заглавие на моята задача', + 'Enter one task by line.' => 'Въведете една задача по ред.', + 'Number of failed login:' => 'Брой неуспешни влизания:', + 'Account locked until:' => 'Акаунта е заключен до:', + 'Email settings' => 'Имейл настройки', + 'Email sender address' => 'Имейл адрес на подателя', + 'Email transport' => 'Имейл транспорт', + 'Webhook token' => 'Маркер за уебкука', + 'Project tags management' => 'Управление на етикети на проекти', + 'Tag created successfully.' => 'Етикета е създаден успешно.', + 'Unable to create this tag.' => 'Този етикет не може да бъде създаден.', + 'Tag updated successfully.' => 'Етикета е актуализиран успешно.', + 'Unable to update this tag.' => 'Този етикет не може да бъде актуализиран.', + 'Tag removed successfully.' => 'Етикета е премахнат успешно.', + 'Unable to remove this tag.' => 'Този етикет не може да бъде премахнат.', + 'Global tags management' => 'Управление на глобални етикети', + 'Tags' => 'Етикети', + 'Tags management' => 'Управление на етикети', + 'Add new tag' => 'Добави нов етикет', + 'Edit a tag' => 'Редактиране на етикет', + 'Project tags' => 'Етикети на проекта', + 'There is no specific tag for this project at the moment.' => 'В момента няма конкретен етикет за този проект.', + 'Tag' => 'Етикет', + 'Remove a tag' => 'Премахване на етикет', + 'Do you really want to remove this tag: "%s"?' => 'Наистина ли искате да премахнете този етикет: "%s"?', + 'Global tags' => 'Глобални етикети', + 'There is no global tag at the moment.' => 'В момента няма глобален етикет.', + 'This field cannot be empty' => 'Това поле не може да бъде празно', + 'Close a task when there is no activity in a specific column' => 'Затваряне на задача, когато няма активност в конкретна колона', + '%s removed a subtask for the task #%d' => '%s премахна подзадача на задача #%d', + '%s removed a comment on the task #%d' => '%s премахна забележка на задачата #%d', + 'Comment removed on task #%d' => 'Забележката е премахната на задача #%d', + 'Subtask removed on task #%d' => 'Подзадачата е премахната на задача #%d', + 'Hide tasks in this column in the dashboard' => 'Скриване на задачите в тази колона в таблото', + '%s removed a comment on the task %s' => '%s премахна забележка за задачата %s', + '%s removed a subtask for the task %s' => '%s премахна подзадача за задачата %s', + 'Comment removed' => 'Забележката е премахната', + 'Subtask removed' => 'Подзадачата е премахната', + '%s set a new internal link for the task #%d' => '%s зададе нова вътрешна връзка за задачата #%d', + '%s removed an internal link for the task #%d' => '%s премахна вътрешна връзка за задачата #%d', + 'A new internal link for the task #%d has been defined' => 'Дефинирана е нова вътрешна връзка за задачата #%d', + 'Internal link removed for the task #%d' => 'Вътрешната връзка е премахната за задачата #%d', + '%s set a new internal link for the task %s' => '%s задаване на нова вътрешна връзка за задачата %s', + '%s removed an internal link for the task %s' => '%s премахна вътрешна връзка за задачата %s', + 'Automatically set the due date on task creation' => 'Автоматично задаване на краен срок при създаване на задача', + 'Move the task to another column when closed' => 'Преместване на задачата в друга колона, когато е затворена', + 'Move the task to another column when not moved during a given period' => 'Преместване на задачата в друга колона, когато не е преместена през даден период', + 'Dashboard for %s' => 'Табло за %s', + 'Tasks overview for %s' => 'Общ преглед на задачите за %s', + 'Subtasks overview for %s' => 'Общ преглед на подзадачите за %s', + 'Projects overview for %s' => 'Общ преглед на проектите за %s', + 'Activity stream for %s' => 'Поток на активност за %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Задаване на цвят, когато задачата се премества на конкретен коридор', + 'Assign a priority when the task is moved to a specific swimlane' => 'Задаване на приоритет, когато задачата се премества на конкретен коридор', + 'User unlocked successfully.' => 'Потребителя е отключен успешно.', + 'Unable to unlock the user.' => 'Потребителя не може да се отключи.', + 'Move a task to another swimlane' => 'Преместване на задача на друг коридор', + 'Creator Name' => 'Име на създател', + 'Time spent and estimated' => 'Прекарано и очаквано време', + 'Move position' => 'Преместване на позиция', + 'Move task to another position on the board' => 'Преместване на задача на друга позиция на дъската', + 'Insert before this task' => 'Вмъкване преди тази задача', + 'Insert after this task' => 'Вмъкване след тази задача', + 'Unlock this user' => 'Отключване на този потребител', + 'Custom Project Roles' => 'Персонализирани роли на проекта', + 'Add a new custom role' => 'Добавяне на нова персонализирана роля', + 'Restrictions for the role "%s"' => 'Ограничения за роля "%s"', + 'Add a new project restriction' => 'Добавяне на ново ограничение на проекта', + 'Add a new drag and drop restriction' => 'Добавяне на ново ограничение за "влачене и пускане"', + 'Add a new column restriction' => 'Добавяне на ново ограничение на колона', + 'Edit this role' => 'Редактиране на тази роля', + 'Remove this role' => 'Премахване на тази роля', + 'There is no restriction for this role.' => 'Няма ограничение за тази роля.', + 'Only moving task between those columns is permitted' => 'Разрешено е само преместване на задача между тези колони', + 'Close a task in a specific column when not moved during a given period' => 'Затваряне на задача в конкретна колона, когато не е преместена през даден период', + 'Edit columns' => 'Редактиране на колони', + 'The column restriction has been created successfully.' => 'Ограничението на колоната е създадено успешно.', + 'Unable to create this column restriction.' => 'Не може да се създаде това ограничение на колоната.', + 'Column restriction removed successfully.' => 'Ограничението за колони е премахнато успешно.', + 'Unable to remove this restriction.' => 'Това ограничение не може да бъде премахнато.', + 'Your custom project role has been created successfully.' => 'Вашата персонализирана роля в проекта е създадена успешно.', + 'Unable to create custom project role.' => 'Не може да се създаде персонализирана роля в проекта.', + 'Your custom project role has been updated successfully.' => 'Вашата персонализирана роля в проекта е актуализирана успешно.', + 'Unable to update custom project role.' => 'Не може да се актуализира персонализираната роля в проекта.', + 'Custom project role removed successfully.' => 'Персонализираната роля в проекта е премахната успешно.', + 'Unable to remove this project role.' => 'Не може да се премахне тази роля в проекта.', + 'The project restriction has been created successfully.' => 'Ограничението на проекта е създадено успешно.', + 'Unable to create this project restriction.' => 'Не може да се създаде това ограничение в проекта.', + 'Project restriction removed successfully.' => 'Ограничението на проекта е премахнато успешно.', + 'You cannot create tasks in this column.' => 'Не можете да създавате задачи в тази колона.', + 'Task creation is permitted for this column' => 'Създаването на задачи е разрешено за тази колона', + 'Closing or opening a task is permitted for this column' => 'Закриването или отварянето на задача е разрешено за тази колона', + 'Task creation is blocked for this column' => 'Създаването на задачи е блокирано за тази колона', + 'Closing or opening a task is blocked for this column' => 'Затварянето или отварянето на задача е блокирано за тази колона', + 'Task creation is not permitted' => 'Създаването на задачи не е разрешено', + 'Closing or opening a task is not permitted' => 'Не се разрешава затваряне или отваряне на задача', + 'New drag and drop restriction for the role "%s"' => 'Ново ограничение за плъзгане и пускане за ролята "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Хората, принадлежащи към тази роля, ще могат да преместват задачи само между източника и колоната местоназначение.', + 'Remove a column restriction' => 'Премахване на ограничение на колона', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Наистина ли искате да премахнете това ограничение на колоната: "%s" до "%s"?', + 'New column restriction for the role "%s"' => 'Ново ограничение на колоната за роля "%s"', + 'Rule' => 'Правило', + 'Do you really want to remove this column restriction?' => 'Наистина ли искате да премахнете това ограничение на колоната?', + 'Custom roles' => 'Персонализирани роли', + 'New custom project role' => 'Нова персонализирана роля на проект', + 'Edit custom project role' => 'Редактиране на персонализирана роля на проекта', + 'Remove a custom role' => 'Премахване на персонализирана роля', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Наистина ли искате да премахнете тази персонализирана роля: "%s"? Всички хора, назначени на тази роля, ще станат членове на проекта.', + 'There is no custom role for this project.' => 'Няма персонализирана роля за този проект.', + 'New project restriction for the role "%s"' => 'Ограничение за нов проект за роля "%s"', + 'Restriction' => 'Ограничение', + 'Remove a project restriction' => 'Премахване на ограничение на проект', + 'Do you really want to remove this project restriction: "%s"?' => 'Наистина ли искате да премахнете това ограничение на проекта: "%s"?', + 'Duplicate to multiple projects' => 'Дублиране на няколко проекта', + 'This field is required' => 'Това поле е задължително', + 'Moving a task is not permitted' => 'Преместването на задача не е разрешено', + 'This value must be in the range %d to %d' => 'Тази стойност трябва да бъде в диапазона от %d до %d', + 'You are not allowed to move this task.' => 'Нямате права да местите тази задача.', + 'API User Access' => 'Потребителски достъп до API', + 'Preview' => 'Визуализация', + 'Write' => 'Запис', + 'Write your text in Markdown' => 'Напишете текста си в Markdown', + 'No personal API access token registered.' => 'Няма регистриран персонален токен за достъп до API.', + 'Your personal API access token is "%s"' => 'Вашия личен API токен за достъп е "%s"', + 'Remove your token' => 'Премахване на вашия токен', + 'Generate a new token' => 'Генериране на нов токен', + 'Showing %d-%d of %d' => 'Показване на %d-%d от %d', + 'Outgoing Emails' => 'Изходящи имейли', + 'Add or change currency rate' => 'Добавяне или промяна на валутния курс', + 'Reference currency: %s' => 'Референтна валута: %s', + 'Add custom filters' => 'Добавяне на персонализирани филтри', + 'Export' => 'Експорт', + 'Add link label' => 'Добавяне на етикет на връзката', + 'Incompatible Plugins' => 'Несъвместими плъгини', + 'Compatibility' => 'Съвместимост', + 'Permissions and ownership' => 'Права и собственост', + 'Priorities' => 'Приоритети', + 'Close this window' => 'Затваряне на този прозорец', + 'Unable to upload this file.' => 'Този файл не може да бъде качен.', + 'Import tasks' => 'Импортиране на задачи', + 'Choose a project' => 'Изберете проект', + 'Profile' => 'Профил', + 'Application role' => 'Роля на приложението', + '%d invitations were sent.' => 'Изпратени са %d покани.', + '%d invitation was sent.' => 'Изпратена е %d покана.', + 'Unable to create this user.' => 'Този потребител не може да бъде създаден.', + 'Kanboard Invitation' => 'Покана от Канборд', + 'Visible on dashboard' => 'Видимо на таблото', + 'Created at:' => 'Създаден на:', + 'Updated at:' => 'Актуализирано:', + 'There is no custom filter.' => 'Няма персонализиран филтър.', + 'New User' => 'Нов потребител', + 'Authentication' => 'Удостоверяване', + 'If checked, this user will use a third-party system for authentication.' => 'Ако е поставена отметка, този потребител ще използва система на трета страна за удостоверяване.', + 'The password is necessary only for local users.' => 'Паролата е необходима само за локални потребители.', + 'You have been invited to register on Kanboard.' => 'Поканени сте да се регистрирате в Kanboard.', + 'Click here to join your team' => 'Щракнете тук, за да се присъедините към екипа си', + 'Invite people' => 'Поканете хора', + 'Emails' => 'Имейли', + 'Enter one email address by line.' => 'Въведете един имейл адрес по ред.', + 'Add these people to this project' => 'Добавете тези хора към този проект', + 'Add this person to this project' => 'Добавяне на този човек към този проект', + 'Sign-up' => 'Регистрация', + 'Credentials' => 'Акредитации', + 'New user' => 'Нов потребител', + 'This username is already taken' => 'Това потребителско име вече е заето', + 'Your profile must have a valid email address.' => 'Вашия профил трябва да има валиден имейл адрес.', + 'TRL - Turkish Lira' => 'TRL - Турска лира', + 'The project email is optional and could be used by several plugins.' => 'Имейла на проекта не е задължителен и може да се използва от няколко плъгина.', + 'The project email must be unique across all projects' => 'Имейла на проекта трябва да бъде уникален за всички проекти', + 'The email configuration has been disabled by the administrator.' => 'Конфигурацията на имейла е деактивирана от администратора.', + 'Close this project' => 'Затваряне на този проект', + 'Open this project' => 'Отваряне на този проект', + 'Close a project' => 'Затваряне на проект', + 'Do you really want to close this project: "%s"?' => 'Наистина ли искате да затворите този проект: "%s"?', + 'Reopen a project' => 'Повторно отваряне на проект', + 'Do you really want to reopen this project: "%s"?' => 'Наистина ли искате да отворите отново този проект: "%s"?', + 'This project is open' => 'Този проект е отворен', + 'This project is closed' => 'Този проект е затворен', + 'Unable to upload files, check the permissions of your data folder.' => 'Файловете не могат да бъдат качени, проверете правата за вашата папка с данни.', + 'Another category with the same name exists in this project' => 'Друга категория със същото име съществува в този проект', + 'Comment sent by email successfully.' => 'Коментара е изпратен успешно по имейл.', + 'Sent by email to "%s" (%s)' => 'Изпратено по имейл на "%s" (%s)', + 'Unable to read uploaded file.' => 'Качения файл не може да бъде прочетен.', + 'Database uploaded successfully.' => 'Базата данни е качена успешно.', + 'Task sent by email successfully.' => 'Задачата е изпратена успешно по имейл.', + 'There is no category in this project.' => 'В този проект няма категория.', + 'Send by email' => 'Изпращане по поща', + 'Create and send a comment by email' => 'Създаване и изпращане на коментар по имейл', + 'Subject' => 'Тема', + 'Upload the database' => 'Качване на базата данни', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Можете да качите изтеглената по-рано база данни на Sqlite (формат gzip).', + 'Database file' => 'Файл бази данни', + 'Upload' => 'Качване', + 'Your project must have at least one active swimlane.' => 'Вашия проект трябва да има поне един активен коридор.', + 'Project: %s' => 'Проект: %s', + 'Automatic action not found: "%s"' => 'Автоматичното действие не е намерено: "%s"', + '%d projects' => '%d проекта', + '%d project' => '%d проект', + 'There is no project.' => 'Няма проект.', + 'Sort' => 'Сортиране', + 'Project ID' => 'ID на проект', + 'Project name' => 'Име на проект', + 'Public' => 'Публичен', + 'Personal' => 'Личен', + '%d tasks' => '%d задачи', + '%d task' => '%d задача', + 'Task ID' => 'ID на задачата', + 'Assign automatically a color when due date is expired' => 'Автоматично задаване на цвят, когато крайния срок е изтекъл', + 'Total score in this column across all swimlanes' => 'Общ резултат в тази колона за всички коридори', + 'HRK - Kuna' => 'HRK - Куна', + 'ARS - Argentine Peso' => 'ARS - аржентинско песо', + 'COP - Colombian Peso' => 'COP - колумбийско песо', + '%d groups' => '%d групи', + '%d group' => '%d група', + 'Group ID' => 'Групово ID', + 'External ID' => 'Външно ID', + '%d users' => '%d потребители', + '%d user' => '%d потребител', + 'Hide subtasks' => 'Скриване на подзадачите', + 'Show subtasks' => 'Показване на подзадачи', + 'Authentication Parameters' => 'Параметри за удостоверяване', + 'API Access' => 'API достъп ', + 'No users found.' => 'Не са намерени потребители.', + 'User ID' => 'ID на потребителя', + 'Notifications are activated' => 'Известията са активирани', + 'Notifications are disabled' => 'Известията са деактивирани', + 'User disabled' => 'Потребителя е деактивиран', + '%d notifications' => '%d известия', + '%d notification' => '%d известие', + 'There is no external integration installed.' => 'Няма инсталирана външна интеграция.', + 'You are not allowed to update tasks assigned to someone else.' => 'Нямате право да актуализирате задачите, възложени на някой друг.', + 'You are not allowed to change the assignee.' => 'Нямате право да променяте изпълнителя.', + 'Task suppression is not permitted' => 'Потискането на задачи не е разрешено', + 'Changing assignee is not permitted' => 'Промяната на изпълнител не е разрешена', + 'Update only assigned tasks is permitted' => 'Актуализирането само на възложените задачи е разрешено', + 'Only for tasks assigned to the current user' => 'Само за задачи, възложени на текущия потребител', + 'My projects' => 'Моите проекти', + 'You are not a member of any project.' => 'Вие не сте член на нито един проект.', + 'My subtasks' => 'Моите подзадачи', + '%d subtasks' => '%d подзадачи', + '%d subtask' => '%d подзадача', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Разрешено е само преместване на задача между тези колони за задачи, възложени на текущия потребител', + '[DUPLICATE]' => '[ДУБЛИРАНО]', + 'DKK - Danish Krona' => 'DKK - датска крона', + 'Remove user from group' => 'Премахни потребителя от групата', + 'Assign the task to its creator' => 'Възлагане на задачата на нейния създател', + 'This task was sent by email to "%s" with subject "%s".' => 'Тази задача беше изпратена по имейл на "%s" с тема "%s".', + 'Predefined Email Subjects' => 'Предварително дефинирани теми на имейл', + 'Write one subject by line.' => 'Напишете по една тема на ред.', + 'Create another link' => 'Създаване на друга връзка', + 'BRL - Brazilian Real' => 'BRL - Бразилски Реал', + 'Add a new Kanboard task' => 'Добавяне на нова задача в Kanboard', + 'Subtask not started' => 'Подзадачата не е стартирана', + 'Subtask currently in progress' => 'Подзадачата се изпълнява в момента', + 'Subtask completed' => 'Подзадачата е изпълнена', + 'Subtask added successfully.' => 'Подзадачата е добавена успешно.', + '%d subtasks added successfully.' => '%d подзадачи са добавени успешно.', + 'Enter one subtask by line.' => 'Въведете една подзадача по ред.', + 'Predefined Contents' => 'Предварително определено съдържание', + 'Predefined contents' => 'Предварително определено съдържание', + 'Predefined Task Description' => 'Предварително дефинирано описание на задачата', + 'Do you really want to remove this template? "%s"' => 'Наистина ли искате да премахнете този шаблон? "%s"', + 'Add predefined task description' => 'Добавяне на предварително дефинирано описание на задачата', + 'Predefined Task Descriptions' => 'Предварително дефинирани описания на задачите', + 'Template created successfully.' => 'Шаблона е създаден успешно.', + 'Unable to create this template.' => 'Този шаблон не може да бъде създаден.', + 'Template updated successfully.' => 'Шаблона е актуализиран успешно.', + 'Unable to update this template.' => 'Този шаблон не може да бъде актуализиран.', + 'Template removed successfully.' => 'Шаблона е премахнат успешно.', + 'Unable to remove this template.' => 'Този шаблон не може да бъде премахнат.', + 'Template for the task description' => 'Образец за описание на задачата', + 'The start date is greater than the end date' => 'Началната дата е по-голяма от крайната дата', + 'Tags must be separated by a comma' => 'Етикетите трябва да бъдат разделени със запетая', + 'Only the task title is required' => 'Изисква се само заглавието на задачата', + 'Creator Username' => 'Потребителско име на създателя', + 'Color Name' => 'Име на цвета', + 'Column Name' => 'Име на колона', + 'Swimlane Name' => 'Име на коридор', + 'Time Estimated' => 'Очаквано време', + 'Time Spent' => 'Прекарано време', + 'External Link' => 'Външен линк', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Тази функция позволява емисия iCal, RSS емисия и публичния изглед на дъската.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Спиране на таймера на всички подзадачи, когато премествате задача в друга колона', + 'Subtask Title' => 'Заглавие на подзадача', + 'Add a subtask and activate the timer when moving a task to another column' => 'Добавяне на подзадача и активиране на таймера при преместване на задача в друга колона', + 'days' => 'дни', + 'minutes' => 'минути', + 'seconds' => 'секунди', + 'Assign automatically a color when preset start date is reached' => 'Автоматично задаване на цвят при достигане на предварително зададена начална дата', + 'Move the task to another column once a predefined start date is reached' => 'Преместване на задачата в друга колона след достигане на предварително определена начална дата', + 'This task is now linked to the task %s with the relation "%s"' => 'Тази задача сега е свързана със задачата %s с връзка "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'Връзката с релацията "%s" към задачата %s е премахната', + 'Custom Filter:' => 'Персонализиран филтър:', + 'Unable to find this group.' => 'Тази група не може да бъде намерена.', + '%s moved the task #%d to the column "%s"' => '%s премести задачата #%d в колоната "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => '%s премести задачата #%d на позиция %d в колоната "%s"', + '%s moved the task #%d to the swimlane "%s"' => '%s премести задачата #%d в коридор "%s"', + '%sh spent' => '%sч използвано', + '%sh estimated' => '%sч очаквано', + 'Select All' => 'Избери всички', + 'Unselect All' => 'Отмаркирай всички', + 'Apply action' => 'Приложи', + 'Move selected tasks to another column or swimlane' => 'Преместване на избраните задачи в друг коридор', + 'Edit tasks in bulk' => 'Групово редактиране на задачи', + 'Choose the properties that you would like to change for the selected tasks.' => 'Изберете свойствата, които искате да промените за избраните задачи.', + 'Configure this project' => 'Конфигуриране на този проект', + 'Start now' => 'Започнете сега', + '%s removed a file from the task #%d' => '%s премахна файл от задачата #%d', + 'Attachment removed from task #%d: %s' => 'Прикачения файл е премахнат от задача #%d: %s', + 'No color' => 'Без цвят', + 'Attachment removed "%s"' => 'Прикачения файл е премахнат "%s"', + '%s removed a file from the task %s' => '%s премахна файл от задачата %s', + 'Move the task to another swimlane when assigned to a user' => 'Преместване на задачата в друг коридор, когато е възложена на потребител', + 'Destination swimlane' => 'Дестинационен коридор', + 'Assign a category when the task is moved to a specific swimlane' => 'Задаване на категория, когато задачата бъде преместена в конкретен коридор', + 'Move the task to another swimlane when the category is changed' => 'Преместване на задачата в друг коридор при промяна на категорията', + 'Reorder this column by priority (ASC)' => 'Пренареждане на тази колона по приоритет (ВЪЗХОДЯЩ)', + 'Reorder this column by priority (DESC)' => 'Пренареждане на тази колона по приоритет (НИЗХОДЯЩ)', + 'Reorder this column by assignee and priority (ASC)' => 'Пренареждане на тази колона по изпълнител и приоритет (ВЪЗХОДЯЩ)', + 'Reorder this column by assignee and priority (DESC)' => 'Пренареждане на тази колона по изпълнител и приоритет (НИЗХОДЯЩ)', + 'Reorder this column by assignee (A-Z)' => 'Пренареждане на тази колона по изпълнител (А-Я)', + 'Reorder this column by assignee (Z-A)' => 'Пренареждане на тази колона по изпълнител (Я-А)', + 'Reorder this column by due date (ASC)' => 'Пренареждане на тази колона по краен срок (ВЪЗХОДЯЩ)', + 'Reorder this column by due date (DESC)' => 'Пренареждане на тази колона по краен срок (НИЗХОДЯЩ)', + 'Reorder this column by id (ASC)' => 'Пренареждане на тази колона по ID (ВЪЗХОДЯЩ)', + 'Reorder this column by id (DESC)' => 'Пренареждане на тази колона по ID (НИЗХОДЯЩ)', + '%s moved the task #%d "%s" to the project "%s"' => '%s премести задачата #%d "%s" в проекта "%s"', + 'Task #%d "%s" has been moved to the project "%s"' => 'Задача #%d "%s" е преместена в проекта "%s"', + 'Move the task to another column when the due date is less than a certain number of days' => 'Преместване на задачата в друга колона, когато крайния срок изтича след по-малко от определен брой дни', + 'Automatically update the start date when the task is moved away from a specific column' => 'Автоматично актуализиране на началната дата, когато задачата се премества от определена колона', + 'HTTP Client:' => 'HTTP клиент:', + 'Assigned' => 'Възложено', + 'Task limits apply to each swimlane individually' => 'Ограниченията на задачите се прилагат за всеки коридор поотделно', + 'Column task limits apply to each swimlane individually' => 'Ограниченията на задачите в колоните да се прилагат за всеки коридор поотделно', + 'Column task limits are applied to each swimlane individually' => 'Ограниченията на задачите в колоните се прилагат за всеки коридор поотделно', + 'Column task limits are applied across swimlanes' => 'Ограниченията на задачите в колони се прилагат върху всички коридори', + 'Task limit: ' => 'Лимит на задачите:', + 'Change to global tag' => 'Промяна на глобален етикет', + 'Do you really want to make the tag "%s" global?' => 'Наистина ли искате да направите етикета "%s" глобален?', + 'Enable global tags for this project' => 'Активиране на глобални етикети за този проект', + 'Group membership(s):' => 'Членство(а) в група(и):', + '%s is a member of the following group(s): %s' => '%s е член на следната(ите) група(и): %s', + '%d/%d group(s) shown' => 'Показани са %d/%d групи', + 'Subtask creation or modification' => 'Създаване или модифициране на подзадачи', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Задайте задачата на конкретен потребител, когато задачата се премести на конкретен коридор', + 'Comment' => 'Забележка', + 'Collapse vertically' => 'Свиване вертикално', + 'Expand vertically' => 'Разгъване вертикално', + 'MXN - Mexican Peso' => 'MXN - Мексиканско песо', + 'Estimated vs actual time per column' => 'Очаквано спрямо действително време по колона', + 'HUF - Hungarian Forint' => 'HUF - унгарски форинт', + 'XBT - Bitcoin' => 'XBT - Bitcoin', + 'You must select a file to upload as your avatar!' => 'Трябва да изберете файл за качване като ваша профилна снимка!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'Качения от Вас файл не е валидно изображение! (Разрешени са само *.gif, *.jpg, *.jpeg и *.png!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Автоматично задаване на краен срок, когато задачата се премества от конкретна колона', + 'No other projects found.' => 'Не са намерени други проекти.', + 'Tasks copied successfully.' => 'Задачите са копирани успешно.', + 'Unable to copy tasks.' => 'Задачите не могат да бъдат копирани.', + 'Theme' => 'Тема', + 'Theme:' => 'Тема:', + 'Light theme' => 'Светла тема', + 'Dark theme' => 'Тъмна тема', + 'Automatic theme - Sync with system' => 'Автоматична тема - Синхронизиране със системата', + 'Application managers or more' => 'Мениджъри на приложението или повече', + 'Administrators' => 'Администратори', + 'Visibility:' => 'Видимост:', + 'Standard users' => 'Стандартни потребители', + 'Visibility is required' => 'Видимостта е задължителна', + 'The visibility should be an app role' => 'Видимостта трябва да бъде роля на приложението', + 'Reply' => 'Отговор', + '%s wrote: ' => '%s написа: ', + 'Number of visible tasks in this column and swimlane' => 'Брой видими задачи в тази колона и лента', + 'Number of tasks in this swimlane' => 'Брой задачи в тази лента', + 'Unable to find another subtask in progress, you can close this window.' => 'Не може да бъде намерена друга подзадача в процес, можете да затворите този прозорец.', + 'This theme is invalid' => 'Тази тема е невалидна', + 'This role is invalid' => 'Тази роля е невалидна', + 'This timezone is invalid' => 'Тази часова зона е невалидна', + 'This language is invalid' => 'Този език е невалиден', + 'This URL is invalid' => 'Този URL е невалиден', + 'Date format invalid' => 'Невалиден формат на дата', + 'Time format invalid' => 'Невалиден формат на час', + 'Invalid Mail transport' => 'Невалиден пощенски транспорт', + 'Color invalid' => 'Невалиден цвят', + 'This value must be greater or equal to %d' => 'Тази стойност трябва да бъде по-голяма или равна на %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Добавете BOM в началото на файла (необходимо за Microsoft Excel)', + 'Just add these tag(s)' => 'Добавете само тези етикети', + 'Remove internal link(s)' => 'Премахнете вътрешните връзки', + 'Import tasks from another project' => 'Импортирайте задачи от друг проект', + 'Select the project to copy tasks from' => 'Изберете проект, от който да копирате задачи', + 'The total maximum allowed attachments size is %sB.' => 'Общият максимален разрешен размер на прикачените файлове е %sB.', + 'Add attachments' => 'Добавете прикачени файлове', + 'Task #%d "%s" is overdue' => 'Задача #%d "%s" е с изтекъл срок', + 'Enable notifications by default for all new users' => 'Активиране на известия по подразбиране за всички нови потребители', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Назначаване на задачата на нейния създател за определени колони, ако няма ръчно зададен изпълнител', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Назначаване на задачата на влезлия потребител при преместване в определена колона, ако няма назначен потребител', +]; diff --git a/app/Locale/bs_BA/translations.php b/app/Locale/bs_BA/translations.php new file mode 100644 index 0000000..7714ca7 --- /dev/null +++ b/app/Locale/bs_BA/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => '.', + 'None' => 'None', + 'Edit' => 'Uredi', + 'Remove' => 'Ukloni', + 'Yes' => 'Da', + 'No' => 'Ne', + 'cancel' => 'odustani', + 'or' => 'ili', + 'Yellow' => 'Žuta', + 'Blue' => 'Plava', + 'Green' => 'Zelena', + 'Purple' => 'Ljubičasta', + 'Red' => 'Crvena', + 'Orange' => 'Narandžasta', + 'Grey' => 'Siva', + 'Brown' => 'Smeđa', + 'Deep Orange' => 'Tamno narandžasta', + 'Dark Grey' => 'Tamno siva', + 'Pink' => 'Roze', + 'Teal' => 'Tirkizna', + 'Cyan' => 'Zelenkasto plava', + 'Lime' => 'Žućkasto zelena', + 'Light Green' => 'Svijetlo zelena', + 'Amber' => 'Ćilibarska', + 'Save' => 'Sačuvaj', + 'Login' => 'Prijava', + 'Official website:' => 'Zvanična stranica:', + 'Unassigned' => 'Nedodijeljen', + '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"?', + 'All users' => 'Svi korisnici', + 'Username' => 'Korisničko ime', + 'Password' => 'Šifra', + 'Administrator' => 'Administrator', + 'Sign in' => 'Prijava', + 'Users' => 'Korisnici', + 'Forbidden' => 'Zabranjeno', + 'Access Forbidden' => 'Pristup zabranjen', + 'Edit user' => 'Uredi korisnika', + 'Logout' => 'Odjava', + 'Bad username or password' => 'Pogrešno korisničko ime ili šifra', + 'Edit project' => 'Uredi projekat', + 'Name' => 'Ime', + 'Projects' => 'Projekti', + 'No project' => 'Bez projekta', + 'Project' => 'Projekat', + 'Status' => 'Status', + 'Tasks' => 'Zadaci', + 'Board' => 'Ploča', + 'Actions' => 'Akcije', + 'Inactive' => 'Neaktivan', + 'Active' => 'Aktivan', + 'Unable to update this board.' => 'Nemogu da ažuriram ovu ploču.', + 'Disable' => 'Onemogući', + 'Enable' => 'Omogući', + 'New project' => 'Novi projekat', + 'Do you really want to remove this project: "%s"?' => 'Da li zaista želiš ukloniti projekat: "%s"?', + 'Remove project' => 'Ukloni projekat', + 'Edit the board for "%s"' => 'Uredi ploču za "%s"', + 'Add a new column' => 'Dodaj novu kolonu', + 'Title' => 'Naslov', + 'Assigned to %s' => 'Dodijeljen korisniku %s', + 'Remove a column' => 'Ukloni kolonu', + '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"?', + 'Settings' => 'Podešavanja', + 'Application settings' => 'Podešavanja aplikacije', + 'Language' => 'Jezik', + 'Webhook token:' => 'Token:', + 'API token:' => 'Token za API', + 'Database size:' => 'Veličina baze:', + 'Download the database' => 'Preuzmi bazu', + 'Optimize the database' => 'Optimizuj bazu', + '(VACUUM command)' => '(Naredba VACUUM)', + '(Gzip compressed Sqlite file)' => '(Sqlite baza spakovana Gzip-om)', + 'Close a task' => 'Zatvori zadatak', + 'Column' => 'Kolona', + 'Color' => 'Boja', + 'Assignee' => 'Izvršilac', + 'Create another task' => 'Poslije ovoga autoamtski otvori formu za dodavanje novog zadatka', + '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 ploču', + 'There is nobody assigned' => 'Niko nije dodijeljen!', + 'Column on the board:' => 'Kolona na tabli:', + 'Close this task' => 'Zatvori ovaj zadatak', + 'Open this task' => 'Otvori ovaj zadatak', + 'There is no description.' => 'Bez opisa.', + 'Add a new task' => 'Dodaj novi 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' => 'Šifra je obavezna', + 'This value must be an integer' => 'Mora biti cio broj', + 'The username must be unique' => 'Korisničko ime mora biti jedinstveno', + 'The user id is required' => 'ID korisnika je obavezan', + 'Passwords don\'t match' => 'Šifre se ne podudaraju', + 'The confirmation is required' => 'Potvrda je obavezna', + 'The project is required' => 'Projekat je obavezan', + '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', + 'The title is required' => 'Naslov je obavezan', + 'Settings saved successfully.' => 'Podešavanja uspješno sačuvana.', + 'Unable to save your settings.' => 'Nemoguće sačuvati podešavanja.', + 'Database optimization done.' => 'Optimizacija baze je završena.', + 'Your project has been created successfully.' => 'Projekat je uspješno napravljen.', + 'Unable to create your project.' => 'Nemoguće kreiranje projekta.', + 'Project updated successfully.' => 'Projekat je uspješ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 uspješno uklonjen.', + 'Project activated successfully.' => 'Projekt uspješno aktiviran.', + 'Unable to activate this project.' => 'Nemoguće aktiviranje projekta.', + 'Project disabled successfully.' => 'Projekat uspješno deaktiviran.', + 'Unable to disable this project.' => 'nemoguće deaktiviranje projekta.', + 'Unable to open this task.' => 'Nemoguće otvaranje zadatka.', + 'Task opened successfully.' => 'Zadatak uspješno otvoren.', + 'Unable to close this task.' => 'Nije moguće zatvaranje ovog zadatka.', + 'Task closed successfully.' => 'Zadatak uspješno zatvoren.', + 'Unable to update your task.' => 'Nije moguće ažuriranje zadatka.', + 'Task updated successfully.' => 'Zadatak uspješno ažuriran.', + 'Unable to create your task.' => 'Nije moguće kreiranje zadatka.', + 'Task created successfully.' => 'Zadatak uspješno kreiran.', + 'User created successfully.' => 'Korisnik uspješno kreiran', + 'Unable to create your user.' => 'Nije uspjelo kreiranje korisnika.', + 'User updated successfully.' => 'Korisnik uspješno ažuriran.', + 'User removed successfully.' => 'Korisnik uspješno uklonjen.', + 'Unable to remove this user.' => 'Nije moguće uklanjanje korisnika.', + 'Board updated successfully.' => 'Ploča je uspješno ažurirana.', + 'Ready' => 'Spreman', + 'Backlog' => 'Zaliha', + 'Work in progress' => 'U izradi', + 'Done' => 'Gotovo', + 'Application version:' => 'Verzija aplikacije:', + 'Id' => 'Id', + 'Public link' => 'Javni link', + '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', + 'Task limit' => 'Najviše zadataka', + 'Task count' => 'Broj zadataka', + 'User' => 'Korisnik', + 'Comments' => 'Komentari', + 'Comment is required' => 'Komentar je obavezan', + 'Comment added successfully.' => 'Komentar uspješno dodan', + 'Unable to create your comment.' => 'Nemoguće kreiranje komentara', + 'Due Date' => 'Treba biti gotovo do dana', + 'Invalid date' => 'Pogrešan datum', + 'Automatic actions' => 'Automatske akcije', + 'Your automatic action has been created successfully.' => 'Uspješ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"' => 'Akcije za automatizaciju projekta "%s"', + 'Add an action' => 'dodaj akcju', + 'Event name' => 'Naziv događaja', + 'Action' => 'Akcija', + 'Event' => 'Događaj', + 'When the selected event occurs execute the corresponding action.' => 'Na izabrani događaj izvrši odgovarajuću akciju', + 'Next step' => 'Slijedeći korak', + 'Define action parameters' => 'Definiši parametre akcije', + 'Do you really want to remove this action: "%s"?' => 'Da li da obrišem akciju "%s"?', + 'Remove an automatic action' => 'Obriši automatsku akciju', + 'Assign the task to a specific user' => 'Dodijeli 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' => 'Premjesti zadatak u drugu kolonu', + 'Task modification' => 'Izmjene zadatka', + 'Task creation' => 'Kreiranje zadatka', + 'Closing a task' => 'Zatvaranja zadatka', + 'Assign a color to a specific user' => 'Dodeli boju korisniku', + 'Position' => 'Pozicija', + 'Duplicate to project' => 'Dupliciraj u drugi projekat', + 'Duplicate' => 'Dupliciraj', + 'Link' => 'Link', + 'Comment updated successfully.' => 'Komentar uspješno ažuriran.', + 'Unable to update your comment.' => 'Neuspješno ažuriranje komentara.', + 'Remove a comment' => 'Obriši komentar', + 'Comment removed successfully.' => 'Komentar je uspješno obrisan.', + 'Unable to remove this comment.' => 'Neuspješno brisanje komentara.', + 'Do you really want to remove this comment?' => 'Da li zaista želiš obrisati ovaj komentar?', + 'Current password for the user "%s"' => 'Trenutna šifra korisnika "%s"', + 'The current password is required' => 'Trenutna šifra je obavezna', + 'Wrong password' => 'Pogrešna šifra', + 'Unknown' => 'Nepoznat', + 'Last logins' => 'Posljednje prijave', + 'Login date' => 'Datum prijave', + 'Authentication method' => 'Metod autentikacije', + 'IP address' => 'IP adresa', + 'User agent' => 'Browser', + 'Persistent connections' => 'Stalna konekcija', + 'No session.' => 'Bez sesije', + 'Expiration date' => 'Ističe', + 'Remember Me' => 'Zapamti me', + 'Creation date' => 'Datum kreiranja', + 'Everybody' => 'Svi', + 'Open' => 'Otvoreni', + 'Closed' => 'Zatvoreni', + 'Search' => 'Pretraga', + 'Nothing found.' => 'Ništa nije pronađeno', + 'Due date' => 'Treba biti gotovo do dana', + 'Description' => 'Opis', + '%d comments' => '%d Komentara', + '%d comment' => '%d Komentar', + 'Email address invalid' => 'Pogrešan e-mail', + 'Your external account is not linked anymore to your profile.' => 'Tvoj vanjski korisnički profil nije više povezan.', + 'Unable to unlink your external account.' => 'Nemoguće ukloniti vezu s vanjskim korisničkim profilom', + 'External authentication failed' => 'Vanjska autentikacija nije uspostavljena', + 'Your external account is linked to your profile successfully.' => 'Uspješno uspostavljena vanjska autentikacija', + 'Email' => 'E-mail', + 'Task removed successfully.' => 'Zadatak uspješ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 zaista želiš ukloniti zadatak "%s"?', + 'Assign automatically a color based on a category' => 'Automatski dodijeli boju po kategoriji', + 'Assign automatically a category based on a color' => 'Automatski dodijeli kategoriju po boji', + 'Task creation or modification' => 'Kreiranje ili izmjena zadatka', + 'Category' => 'Kategorija', + 'Category:' => 'Kategorija:', + 'Categories' => 'Kategorije', + 'Your category has been created successfully.' => 'Uspješno kreirana kategorija.', + 'This category has been updated successfully.' => 'Kategorija je uspješno ažurirana', + 'Unable to update this category.' => 'Nemoguće izmijeniti kategoriju', + 'Remove a category' => 'Ukloni kategoriju', + 'Category removed successfully.' => 'Kategorija uspješno uklonjena.', + 'Unable to remove this category.' => 'Nije moguće ukloniti kategoriju.', + 'Category modification for the project "%s"' => 'Izmjena kategorije za projekat "%s"', + 'Category Name' => 'Naziv kategorije', + 'Add a new category' => 'Dodaj novu kategoriju', + 'Do you really want to remove this category: "%s"?' => 'Da li zaista želiš ukloniti kategoriju: "%s"?', + 'All categories' => 'Sve kategorije', + 'No category' => 'Bez kategorije', + 'The name is required' => 'Naziv je obavezan', + 'Remove a file' => 'Ukloni datoteku', + 'Unable to remove this file.' => 'Fajl nije moguće ukloniti.', + 'File removed successfully.' => 'Uspješno uklonjena datoteka.', + 'Attach a document' => 'Prikači dokument', + 'Do you really want to remove this file: "%s"?' => 'Da li zaista želiš ukloniti datoteku: "%s"?', + 'Attachments' => 'Prilozi', + 'Edit the task' => 'Uredi zadatak', + 'Add a comment' => 'Dodaj komentar', + 'Edit a comment' => 'Izmijeni komentar', + 'Summary' => 'Pregled', + 'Time tracking' => 'Praćenje vremena', + 'Estimate:' => 'Procjena:', + 'Spent:' => 'Potrošeno:', + 'Do you really want to remove this sub-task?' => 'Da li da zaista želiš ukloniti pod-zdadatak?', + 'Remaining:' => 'Preostalo:', + 'hours' => 'sati', + 'estimated' => 'procijenjeno', + 'Sub-Tasks' => 'Pod-zadaci', + 'Add a sub-task' => 'Dodaj pod-zadatak', + 'Original estimate' => 'Originalna procjena', + 'Create another sub-task' => 'Dodaj novi pod-zadatak', + 'Time spent' => 'Utrošeno vrijeme', + 'Edit a sub-task' => 'Izmijeni pod-zadatak', + 'Remove a sub-task' => 'Ukloni pod-zadatak', + 'The time must be a numeric value' => 'Vrijeme mora biti broj', + 'Todo' => 'Za uraditi', + 'In progress' => 'U radu', + 'Sub-task removed successfully.' => 'Pod-zadatak uspješno uklonjen.', + 'Unable to remove this sub-task.' => 'Nemoguće ukloniti pod-zadatak.', + 'Sub-task updated successfully.' => 'Pod-zadatak uspješno ažuriran.', + 'Unable to update your sub-task.' => 'Nemoguće ažurirati pod-zadatak.', + 'Unable to create your sub-task.' => 'Nemoguće dodati pod-zadatak.', + 'Maximum size: ' => 'Maksimalna veličina: ', + 'Display another project' => 'Prikaži drugi projekat', + 'Created by %s' => 'Kreirao %s', + 'Tasks Export' => 'Izvoz zadataka', + 'Start Date' => 'Početni datum', + 'Execute' => 'Izvrši', + 'Task Id' => 'Identifikator zadatka', + 'Creator' => 'Autor', + 'Modification date' => 'Datum izmjene', + 'Completion date' => 'Datum završetka', + 'Clone' => 'Kloniraj', + 'Project cloned successfully.' => 'Projekat uspješno kloniran.', + 'Unable to clone this project.' => 'Nije moguće klonirati projekat.', + 'Enable email notifications' => 'Omogući obavještenja e-mailom', + 'Task position:' => 'Pozicija zadatka:', + 'The task #%d has been opened.' => 'Zadatak #%d je otvoren.', + 'The task #%d has been closed.' => 'Zadatak #%d je zatvoren.', + 'Sub-task updated' => 'Pod-zadatak izmijenjen', + 'Title:' => 'Naslov:', + 'Status:' => 'Status:', + 'Assignee:' => 'Izvršilac:', + 'Time tracking:' => 'Praćenje vremena:', + 'New sub-task' => 'Novi pod-zadatak', + 'New attachment added "%s"' => 'Ubačen novi prilog "%s"', + 'New comment posted by %s' => '%s ostavio novi komentar', + 'New comment' => 'Novi komentar', + 'Comment updated' => 'Komentar ažuriran', + 'New subtask' => 'Novi pod-zadatak', + 'I only want to receive notifications for these projects:' => 'Želim obavještenja samo za ove projekte:', + 'view the task on Kanboard' => 'Pregledaj zadatke', + 'Public access' => 'Javni pristup', + 'Disable public access' => 'Zabrani javni pristup', + 'Enable public access' => 'Dozvoli javni pristup', + 'Public access disabled' => 'Javni pristup onemogućen!', + 'Move the task to another project' => 'Premjesti zadatak u drugi projekat', + 'Move to project' => 'Premjesti u drugi projekat', + 'Do you really want to duplicate this task?' => 'Da li zaista želiš duplicirati ovaj zadatak?', + 'Duplicate a task' => 'Dupliciraj zadatak', + 'External accounts' => 'Vanjski korisnički računi', + 'Account type' => 'Tip korisničkog računa', + 'Local' => 'Lokalno', + 'Remote' => 'Udaljeno', + 'Enabled' => 'Omogućeno', + 'Disabled' => 'Onemogućeno', + 'Login:' => 'Korisničko ime:', + 'Full Name:' => 'Ime i Prezime', + 'Email:' => 'Email: ', + 'Notifications:' => 'Obavještenja: ', + 'Notifications' => 'Obavještenja', + 'Account type:' => 'Vrsta korisničkog računa:', + 'Edit profile' => 'Uredi profil', + 'Change password' => 'Promijeni šifru', + 'Password modification' => 'Izmjena šifre', + 'External authentications' => 'Vanjske autentikacije', + 'Never connected.' => 'Bez konekcija.', + 'No external authentication enabled.' => 'Bez omogućenih vanjskih autentikacija.', + 'Password modified successfully.' => 'Uspješna izmjena šifre.', + 'Unable to change the password.' => 'Nije moguće izmijeniti šifru.', + 'Change category' => 'Izmijeni kategoriju', + '%s updated the task %s' => '%s izmijenio zadatak %s', + '%s opened the task %s' => '%s otvorio zadatak %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s premjestio zadatak %s na poziciju #%d u koloni "%s"', + '%s moved the task %s to the column "%s"' => '%s premjestio 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 kreirao pod-zadatak zadatka %s', + '%s updated a subtask for the task %s' => '%s izmijenio pod-zadatak zadatka %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Dodijeljen korisniku %s uz procjenu vremena %s/%sh', + 'Not assigned, estimate of %sh' => 'Ne dodijeljen, procijenjeno vrijeme %sh', + '%s updated a comment on the task %s' => '%s izmijenio komentar zadatka %s', + '%s commented the task %s' => '%s komentarisao zadatak %s', + '%s\'s activity' => 'Aktivnosti %s', + 'RSS feed' => 'RSS kanal', + '%s updated a comment on the task #%d' => '%s izmijenio komentar zadatka #%d', + '%s commented on the task #%d' => '%s komentarisao zadatak #%d', + '%s updated a subtask for the task #%d' => '%s izmijenio pod-zadatak zadatka #%d', + '%s created a subtask for the task #%d' => '%s kreirao pod-zadatak zadatka #%d', + '%s updated the task #%d' => '%s ažurirao zadatak #%d', + '%s created the task #%d' => '%s kreirao zadatak #%d', + '%s closed the task #%d' => '%s zatvorio zadatak #%d', + '%s opened the task #%d' => '%s otvorio zadatak #%d', + 'Activity' => 'Aktivnosti', + 'Default values are "%s"' => 'Podrazumijevane vrijednosti su: "%s"', + 'Default columns for new projects (Comma-separated)' => 'Podrazumijevane kolone za novi projekat (Odvojene zarezom)', + 'Task assignee change' => 'Promijena izvršioca zadatka', + '%s changed the assignee of the task #%d to %s' => '%s zamijeni izvršioca za zadatak #%d u %s', + '%s changed the assignee of the task %s to %s' => '%s promijenio izvršioca za zadatak %s u %s', + 'New password for the user "%s"' => 'Nova šifra korisnika "%s"', + 'Choose an event' => 'Izaberi događaj', + 'Create a task from an external provider' => 'Kreiraj zadatak preko posrednika', + 'Change the assignee based on an external username' => 'Izmijene izvršioca bazirano na vanjskom korisničkom imenu', + 'Change the category based on an external label' => 'Izmijene kategorije bazirano na vanjskoj etiketi', + 'Reference' => 'Referenca', + 'Label' => 'Etiketa', + 'Database' => 'Baza', + 'About' => 'O Kanboardu', + 'Database driver:' => 'Database driver:', + 'Board settings' => 'Postavke table', + 'Webhook settings' => 'Postavke za webhook', + 'Reset token' => 'Resetuj token', + 'API endpoint:' => 'API endpoint', + 'Refresh interval for personal board' => 'Interval osvježavanja privatnih ploča', + 'Refresh interval for public board' => 'Interval osvježavanja javnih ploča', + 'Task highlight period' => 'Period naznačavanja zadatka', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Period (u sekundama) u kom su se događale promjene na zadatku (0 je onemogućeno, 2 dana je uobičajeno)', + 'Frequency in second (60 seconds by default)' => 'Frekvencija u sekundama (60 sekundi je uobičajeno)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frekvencija u sekundama (0 je onemogućeno u budućnosti, 10 sekundi je uobičajeno)', + 'Application URL' => 'URL aplikacije', + 'Token regenerated.' => 'Token regenerisan.', + 'Date format' => 'Format datuma', + 'ISO format is always accepted, example: "%s" and "%s"' => 'Format ISO je uvijek prihvatljiv, primjer: "%s", "%s"', + 'New personal project' => 'Novi privatni projekat', + 'This project is personal' => 'Ovaj projekat je privatan', + 'Add' => 'Dodaj', + 'Start date' => 'Datum početka', + 'Time estimated' => 'Procijenjeno vrijeme', + 'There is nothing assigned to you.' => 'Ništa ti nije dodijeljeno', + 'My tasks' => 'Moji zadaci', + 'Activity stream' => 'Spisak aktivnosti', + 'Dashboard' => 'Panel', + 'Confirmation' => 'Potvrda', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Create a comment from an external provider' => 'Napravi komentar preko vanjskog posrednika', + 'Project management' => 'Upravljanje projektima', + 'Columns' => 'Kolone', + 'Task' => 'Zadatak', + 'Percentage' => 'Procenat', + 'Number of tasks' => 'Broj zadataka', + 'Task distribution' => 'Podjela zadataka', + 'Analytics' => 'Analiza', + 'Subtask' => 'Pod-zadatak', + 'User repartition' => 'Zaduženja korisnika', + 'Clone this project' => 'Kloniraj ovaj projekat', + 'Column removed successfully.' => 'Kolona uspješno uklonjena.', + 'Not enough data to show the graph.' => 'Nedovoljno podataka za prikaz na grafikonu.', + 'Previous' => 'Prethodni', + 'The id must be an integer' => 'ID mora biti cjeloviti broj', + 'The project id must be an integer' => 'ID projekta mora biti cjeloviti broj', + 'The status must be an integer' => 'Status mora biti cjeloviti broj', + 'The subtask id is required' => 'ID pod-zadataka je obavezan', + 'The subtask id must be an integer' => 'ID pod-zadatka mora biti cjeloviti broj', + 'The task id is required' => 'ID zadatka je obavezan', + 'The task id must be an integer' => 'ID zadatka mora biti cjeloviti broj', + 'The user id must be an integer' => 'ID korisnika mora biti cjeloviti broj', + 'This value is required' => 'Vrijednost je obavezna', + 'This value must be numeric' => 'Vrijednost mora biti broj', + 'Unable to create this task.' => 'Nije moguće kreirati zadatak.', + 'Cumulative flow diagram' => 'Zbirni dijagram toka', + 'Daily project summary' => 'Zbirni pregled po danima', + 'Daily project summary export' => 'Izvoz zbirnog pregleda po danima', + 'Exports' => 'Izvozi', + 'This export contains the number of tasks per column grouped per day.' => 'Ovaj izvoz sadržava broj zadataka po koloni grupisanih po danima.', + 'Active swimlanes' => 'Aktivne swimlane trake', + 'Add a new swimlane' => 'Dodaj novu swimlane traku', + 'Default swimlane' => 'Podrazumijevana swimlane traka', + 'Do you really want to remove this swimlane: "%s"?' => 'Da li zaista želiš ukloniti ovu swimlane traku: "%s"?', + 'Inactive swimlanes' => 'Neaktivne swimlane trake', + 'Remove a swimlane' => 'Ukloni swimlane traku', + 'Swimlane modification for the project "%s"' => 'Izmjene swimlane trake za projekat "%s"', + 'Swimlane removed successfully.' => 'Swimlane traka uspješno uklonjena.', + 'Swimlanes' => 'Swimlane trake', + 'Swimlane updated successfully.' => 'Swimlane traka uspjeno ažurirana.', + 'Unable to remove this swimlane.' => 'Nemoguće ukloniti swimlane traku.', + 'Unable to update this swimlane.' => 'Nemoguće ažurirati swimlane traku.', + 'Your swimlane has been created successfully.' => 'Swimlane traka je uspješno kreirana.', + 'Example: "Bug, Feature Request, Improvement"' => 'Npr: "Greška, Zahtjev za izmjenama, Poboljšanje"', + 'Default categories for new projects (Comma-separated)' => 'Podrazumijevane kategorije za novi projekat', + 'Integrations' => 'Integracije', + 'Integration with third-party services' => 'Integracija sa uslugama vanjskih servisa', + 'Subtask Id' => 'ID pod-zadatka', + 'Subtasks' => 'Pod-zadaci', + 'Subtasks Export' => 'Izvoz pod-zadataka', + 'Task Title' => 'Naslov zadatka', + 'Untitled' => 'Bez naslova', + 'Application default' => 'Podrazumijevano od aplikacije', + 'Language:' => 'Jezik:', + 'Timezone:' => 'Vremenska zona:', + 'All columns' => 'Sve kolone', + 'Next' => 'Slijedeći', + '#%d' => '#%d', + 'All swimlanes' => 'Sve swimlane trake', + 'All colors' => 'Sve boje', + 'Moved to column %s' => 'Premješten u kolonu %s', + 'User dashboard' => 'Korisnički panel', + 'Allow only one subtask in progress at the same time for a user' => 'Dozvoli samo jedan pod-zadatak "u radu" po korisniku', + 'Edit column "%s"' => 'Uredi kolonu "%s"', + 'Select the new status of the subtask: "%s"' => 'Izaberi novi status za pod-zadatak: "%s"', + 'Subtask timesheet' => 'Vremenska tabela za pod-zadatak', + 'There is nothing to show.' => 'Nema ništa za pokazati', + 'Time Tracking' => 'Praćenje vremena', + 'You already have one subtask in progress' => 'Već imaš jedan pod-zadatak "u radu"', + 'Which parts of the project do you want to duplicate?' => 'Koje delove projekta želiš duplicirati?', + 'Disallow login form' => 'Zabrani prijavnu formu', + 'Start' => 'Početak', + 'End' => 'Kraj', + 'Task age in days' => 'Trajanje zadatka u danima', + 'Days in this column' => 'Dani u ovoj koloni', + '%dd' => '%dd', + 'Add a new link' => 'Dodaj novu vezu', + 'Do you really want to remove this link: "%s"?' => 'Da li zaista želiš ukloniti ovu vezu: "%s"?', + 'Do you really want to remove this link with task #%d?' => 'Da li zaista želiš ukloniti ovu vezu sa zadatkom #%d?', + 'Field required' => 'Polje je obavezno', + 'Link added successfully.' => 'Veza je uspješno dodana.', + 'Link updated successfully.' => 'Veza je uspješno ažurirana.', + 'Link removed successfully.' => 'Veza je uspješno uklonjena.', + 'Link labels' => 'Veza s etiketama', + 'Link modification' => 'Veza modifikacija', + 'Opposite label' => 'Suprotna etiketa', + 'Remove a link' => 'Ukloni vezu', + 'The labels must be different' => 'Etikete moraju biti različite', + 'There is no link.' => 'Ovdje nema veza', + 'This label must be unique' => 'Ova etiketa mora biti jedinstvena', + 'Unable to create your link.' => 'Nemoguće napraviti vezu.', + 'Unable to update your link.' => 'Nemoguće ažurirati vezu.', + 'Unable to remove this link.' => 'Nemoguće ukloniti vezu.', + 'relates to' => 'relacija sa', + 'blocks' => 'blokira', + 'is blocked by' => 'je blokiran od', + 'duplicates' => 'duplicira', + 'is duplicated by' => 'je dupliciran od', + 'is a child of' => 'je dijete od', + 'is a parent of' => 'je roditelj od', + 'targets milestone' => 'cilj prekretnice', + 'is a milestone of' => 'je od prekretnice', + 'fixes' => 'popravlja', + 'is fixed by' => 'je popravljen od', + 'This task' => 'Ovaj zadatak', + '<1h' => '<1h', + '%dh' => '%dh', + 'Expand tasks' => 'Proširi zadatke', + 'Collapse tasks' => 'Skupi zadatke', + 'Expand/collapse tasks' => 'Proširi/skupi zadatke', + 'Close dialog box' => 'Skupi dialog', + 'Submit a form' => 'Pošalji obrazac', + 'Board view' => 'Pregled ploče', + 'Keyboard shortcuts' => 'Prečice tastature', + 'Open board switcher' => 'Otvori prekidače ploče', + 'Application' => 'Aplikacija', + 'Compact view' => 'Kompaktan pregled', + 'Horizontal scrolling' => 'Horizontalno listanje', + 'Compact/wide view' => 'Skupi/raširi pregled', + 'Currency' => 'Valuta', + 'Personal project' => 'Privatni projekat', + 'AUD - Australian Dollar' => 'AUD - Australijski dolar', + 'CAD - Canadian Dollar' => 'CAD - Kanadski dolar', + 'CHF - Swiss Francs' => 'CHF - Švicarski franak', + 'Custom Stylesheet' => 'Prilagođeni stil', + 'EUR - Euro' => 'EUR - Euro', + 'GBP - British Pound' => 'GBP - Britanska funta', + 'INR - Indian Rupee' => 'INR - Indijski rupi', + 'JPY - Japanese Yen' => 'JPY - Japanski jen', + 'NZD - New Zealand Dollar' => 'NZD - Novozelandski dolar', + 'PEN - Peruvian Sol' => 'PEN - Peruanski sol', + 'RSD - Serbian dinar' => 'RSD - Srpski dinar', + 'CNY - Chinese Yuan' => 'CNY - Kineski juan', + 'USD - US Dollar' => 'USD - Američki dolar', + 'VES - Venezuelan Bolívar' => 'VES - Venecuelanski bolivar', + 'Destination column' => 'Odredišna kolona', + 'Move the task to another column when assigned to a user' => 'Premjesti zadatak u neku drugu kolonu kada se dodijeli izvršiocu', + 'Move the task to another column when assignee is cleared' => 'Premjesti zadatak u neku drugu kolonu kada se ukloni izvršilac', + 'Source column' => 'Izvorna kolona', + 'Transitions' => 'Prelaz', + 'Executer' => 'Izvršilac', + 'Time spent in the column' => 'Vrijeme provedeno u koloni', + 'Task transitions' => 'Prelazi zadatka', + 'Task transitions export' => 'Izvezi prelaze zadatka', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Ovaj izvještaj sadržava sve kolone premještanja za svaki zadatak s datumom, te korisnikom i utrošenim vremenom za svaki premještaj.', + 'Currency rates' => 'Stopa valute', + 'Rate' => 'Stopa', + 'Change reference currency' => 'Promijeni referencu valute', + 'Reference currency' => 'Referentna valuta', + 'The currency rate has been added successfully.' => 'Stopa valute je uspješno dodana.', + 'Unable to add this currency rate.' => 'Nemoguće dodati stopu valute.', + 'Webhook URL' => 'Webhook URL', + '%s removed the assignee of the task %s' => '%s je uklonio izvršioca zadatka %s', + 'Information' => 'Informacije', + 'Check two factor authentication code' => 'Provjera "Dva faktora" autentifikacionog koda', + 'The two factor authentication code is not valid.' => '"Dva faktora" autentifikacionog koda nije validan.', + 'The two factor authentication code is valid.' => '"Dva faktora" autentifikacionog koda je validan.', + 'Code' => 'Kod', + 'Two factor authentication' => '"Dva faktora" autentifikacija', + 'This QR code contains the key URI: ' => 'Ovaj QR kod sadržava ključni URL: ', + 'Check my code' => 'Provjeri moj kod', + 'Secret key: ' => 'Tajni ključ: ', + 'Test your device' => 'Testiraj svoj uređaj', + 'Assign a color when the task is moved to a specific column' => 'Dodijeli boju kada je zadatak pomjeren u odabranu kolonu', + '%s via Kanboard' => '%s uz pomoć Kanboard-a', + 'Burndown chart' => 'Grafikon izgaranja', + 'This chart show the task complexity over the time (Work Remaining).' => 'Ovaj grafikon pokazuje kompleksnost zadatka u vremenu (Preostalo vremena)', + 'Screenshot taken %s' => 'Slika ekrana uzeta %s', + 'Add a screenshot' => 'Dodaj sliku ekrana', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Uzmi sliku ekrana i pritisni CTRL+V ili ⌘+V da zalijepiš ovdje.', + 'Screenshot uploaded successfully.' => 'Slika ekrana uspješno dodana.', + 'SEK - Swedish Krona' => 'SEK - Švedska kruna', + 'Identifier' => 'Identifikator', + 'Disable two factor authentication' => 'Onemogući "Dva faktora" autentifikaciju', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Da li zaista želiš onemogućiti "Dva faktora" autentifikaciju: "%s"?', + 'Edit link' => 'Uredi vezu', + 'Start to type task title...' => 'Počni pisati naslov zadatka...', + 'A task cannot be linked to itself' => 'Zadatak ne može biti povezan sa samim sobom', + 'The exact same link already exists' => 'Ista veza već postoji', + 'Recurrent task is scheduled to be generated' => 'Ponavljajući zadatak je pripremljen da bude kreiran', + 'Score' => 'Uspjeh', + 'The identifier must be unique' => 'Identifikator mora biti jedinstven', + 'This linked task id doesn\'t exists' => 'Povezani ID zadatka ne postoji', + 'This value must be alphanumeric' => 'Ova vrijednost mora biti alfanumerička', + 'Edit recurrence' => 'Uredi ponavljanje', + 'Generate recurrent task' => 'Napravi ponavljajući zadatak', + 'Trigger to generate recurrent task' => 'Okidač koji pravi ponavljajući zadatak', + 'Factor to calculate new due date' => 'Faktor za računanje novog datuma završetka', + 'Timeframe to calculate new due date' => 'Vremenski okvir za računanje novog datuma završetka', + 'Base date to calculate new due date' => 'Početni datum za računanje novog datuma završetka', + 'Action date' => 'Datum akcije', + 'Base date to calculate new due date: ' => 'Početni datum za računanje novog datuma završetka: ', + 'This task has created this child task: ' => 'Ovaj zadatak će napraviti zadatak-dijete: ', + 'Day(s)' => 'Dan(i)', + 'Existing due date' => 'Postojeći datum završetka', + 'Factor to calculate new due date: ' => 'Faktor za računanje novog datuma završetka: ', + 'Month(s)' => 'Mjesec(i)', + 'This task has been created by: ' => 'Ovaj zadatak je napravio: ', + 'Recurrent task has been generated:' => 'Ponavljajući zadatak je napravio:', + 'Timeframe to calculate new due date: ' => 'Vremenski okvir za računanje novog datuma završetka:', + 'Trigger to generate recurrent task: ' => 'Okidač za pravljenje ponavljajućeg zadatka', + 'When task is closed' => 'Kada je zadatak zatvoren', + 'When task is moved from first column' => 'Kada je zadatak premješten iz prve kolone', + 'When task is moved to last column' => 'Kada je zadatak premješten u posljednju kolonu', + 'Year(s)' => 'Godina/e', + 'Project settings' => 'Postavke projekta', + 'Automatically update the start date' => 'Automatski ažuriraj početni datum', + 'iCal feed' => 'iCal kanal', + 'Preferences' => 'Postavke', + 'Security' => 'Sigurnost', + 'Two factor authentication disabled' => '"Dva faktora" autentifikacija onemogućena', + 'Two factor authentication enabled' => '"Dva faktora" autentifikacija omogućena', + 'Unable to update this user.' => 'Nemoguće ažurirati ovog korisnika', + 'There is no user management for personal projects.' => 'Nema mehanizma za upravljanje korisnicima kod privatnih projekata.', + 'User that will receive the email' => 'Korisnik će dobiti email', + 'Email subject' => 'Predmet email-a', + 'Date' => 'Datum', + 'Add a comment log when moving the task between columns' => 'Dodaj komentar u dnevnik kada se pomjeri zadatak između kolona', + 'Move the task to another column when the category is changed' => 'Pomjeri zadatak u drugu kolonu kada je kategorija promijenjena', + 'Send a task by email to someone' => 'Pošalji zadatak nekome emailom', + 'Reopen a task' => 'Ponovo otvori zadatak', + 'Notification' => 'Obavještenja', + '%s moved the task #%d to the first swimlane' => '%s je premjestio zadatak #%d u prvu swimlane traku', + 'Swimlane' => 'Swimlane traka', + '%s moved the task %s to the first swimlane' => '%s je premjestio zadatak %s u prvi swimlane traku', + '%s moved the task %s to the swimlane "%s"' => '%s je premjestio zadatak %s u swimlane traku "%s"', + 'This report contains all subtasks information for the given date range.' => 'Ovaj izvještaj sadržava sve informacije o pod-zadacima za dati period', + 'This report contains all tasks information for the given date range.' => 'Ovaj izvještaj sadržava sve informacije o zadacima u datom periodu', + 'Project activities for %s' => 'Aktivnosti projekta za %s', + 'view the board on Kanboard' => 'pregled ploče na Kanboard-u', + 'The task has been moved to the first swimlane' => 'Zadatak je premješten u prvu swimlane traku', + 'The task has been moved to another swimlane:' => 'Zadatak je premješten u drugu swimlane traku', + 'New title: %s' => 'Novi naslov: %s', + 'The task is not assigned anymore' => 'Zadatak nema više izvršioca', + 'New assignee: %s' => 'Novi izvršilac: %s', + 'There is no category now' => 'Sada nema kategorije', + 'New category: %s' => 'Nova kategorija: %s', + 'New color: %s' => 'Nova boja: %s', + 'New complexity: %d' => 'Nova složenost: %d', + 'The due date has been removed' => 'Datum završetka je ukloljen', + 'There is no description anymore' => 'Nema više opisa', + 'Recurrence settings has been modified' => 'Promijenjene postavke za ponavljajuće zadatke', + 'Time spent changed: %sh' => 'Utrošeno vrijeme je promijenjeno: %sh', + 'Time estimated changed: %sh' => 'Očekivano vrijeme je promijenjeno: %sh', + 'The field "%s" has been updated' => 'Polje "%s" je ažurirano', + 'The description has been modified:' => 'Promijenjen opis:', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Da li zaista želiš zatvoriti zadatak "%s" kao i sve pod-zadatke?', + 'I want to receive notifications for:' => 'Želim dobijati obavještenja za:', + 'All tasks' => 'Sve zadatke', + 'Only for tasks assigned to me' => 'Samo za zadatke na kojima sam izvršilac', + 'Only for tasks created by me' => 'Samo za zadatke koje sam ja napravio', + 'Only for tasks created by me and tasks assigned to me' => 'Samo za zadatke koje sam ja napravio i na kojima sam izvršilac', + '%%Y-%%m-%%d' => '%%Y-%%m-%%d', + 'Total for all columns' => 'Ukupno za sve kolone', + 'You need at least 2 days of data to show the chart.' => 'Da bi se prikazao ovaj grafik potrebni su podaci iz najmanje posljednja dva dana.', + '<15m' => '<15m', + '<30m' => '<30m', + 'Stop timer' => 'Zaustavi tajmer', + 'Start timer' => 'Pokreni tajmer', + 'My activity stream' => 'Tok mojih aktivnosti', + 'Search tasks' => 'Pretraga zadataka', + 'Reset filters' => 'Vrati filtere na početno', + 'My tasks due tomorrow' => 'Moji zadaci koje treba završiti sutra', + 'Tasks due today' => 'Zadaci koje treba završiti danas', + 'Tasks due tomorrow' => 'Zadaci koje treba završiti sutra', + 'Tasks due yesterday' => 'Zadaci koje je trebalo završiti jučer', + 'Closed tasks' => 'Zatvoreni zadaci', + 'Open tasks' => 'Otvoreni zadaci', + 'Not assigned' => 'Bez izvršioca', + 'View advanced search syntax' => 'Vidi naprednu sintaksu pretrage', + 'Overview' => 'Opšti pregled', + 'Board/Calendar/List view' => 'Pregled Ploče/Kalendara/Liste', + 'Switch to the board view' => 'Promijeni da vidim ploču', + 'Switch to the list view' => 'Promijeni da vidim listu', + 'Go to the search/filter box' => 'Idi na kutiju s pretragom/filterima', + 'There is no activity yet.' => 'Još uvijek nema aktivnosti.', + 'No tasks found.' => 'Zadaci nisu pronađeni.', + 'Keyboard shortcut: "%s"' => 'Prečica tastature: "%s"', + 'List' => 'Lista', + 'Filter' => 'Filter', + 'Advanced search' => 'Napredna pretraga', + 'Example of query: ' => 'Primjer za upit: ', + 'Search by project: ' => 'Pretraga po projektu: ', + 'Search by column: ' => 'Pretraga po koloni: ', + 'Search by assignee: ' => 'Pretraga po izvršiocu: ', + 'Search by color: ' => 'Pretraga po boji: ', + 'Search by category: ' => 'Pretraga po kategoriji: ', + 'Search by description: ' => 'Pretraga po opisu: ', + 'Search by due date: ' => 'Pretraga po datumu završetka: ', + 'Average time spent in each column' => 'Prosjek utrošenog vrmena u svakoj koloni', + 'Average time spent' => 'Prosjek utrošenog vremena', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Ovaj grafik pokazuje prosjek utrošenog vremena u svakoj koloni za posljednjih %d zadataka.', + 'Average Lead and Cycle time' => 'Prosjek vremena upravljanja i vremenskog ciklusa', + 'Average lead time: ' => 'Prosjek vremena upravljanja', + 'Average cycle time: ' => 'Prosjek vremenskog ciklusa', + 'Cycle Time' => 'Vremenski ciklus', + 'Lead Time' => 'Vrijeme upravljanja', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Ovaj grafik pokazuje prosjek vremena vođenja i vremenskog ciklusa za posljednjih %d zadataka tokom vremena.', + 'Average time into each column' => 'Prosječno vrijeme u svakoj koloni', + 'Lead and cycle time' => 'Vrijeme vođenja i vremenski ciklus', + 'Lead time: ' => 'Vrijeme vođenja: ', + 'Cycle time: ' => 'Vremenski ciklus: ', + 'Time spent in each column' => 'Utrošeno vrijeme u svakoj koloni', + 'The lead time is the duration between the task creation and the completion.' => 'Vrijeme vođenja je vrijeme koje je proteklo između otvaranja i zatvaranja zadatka.', + 'The cycle time is the duration between the start date and the completion.' => 'Vremenski ciklus je vrijeme koje je proteklo između početka i završetka rada na zadatku.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Ako zadatak nije zatvoren trenutno vrijeme je iskorišteno umjesto datuma završetka.', + 'Set the start date automatically' => 'Automatski postavi početno vrijeme', + 'Edit Authentication' => 'Uredi autentifikaciju', + 'Remote user' => 'Vanjski korisnik', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Vanjski korisnik ne čuva šifru u Kanboard bazi, npr: LDAP, Google i Github korisnički računi.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Ako ste označili kvadratić "Zabrani prijavnu formu", unos pristupnih podataka u prijavnoj formi će biti ignorisan.', + 'Default task color' => 'Podrazumijevana boja zadatka', + 'This feature does not work with all browsers.' => 'Ovaj funkcionalnost ne radi na svim internet pretraživačima.', + 'There is no destination project available.' => 'Nema definisanog odredišta za projekat.', + 'Trigger automatically subtask time tracking' => 'Okidač za automatsko vremensko praćenje za pod-zadatke', + 'Include closed tasks in the cumulative flow diagram' => 'Obuhvati zatvorene zadatke u kumulativnom dijagramu toka', + 'Current swimlane: %s' => 'Trenutna swimlane traka: %s', + 'Current column: %s' => 'Trenutna kolona: %s', + 'Current category: %s' => 'Trenutna kategorija: %s', + 'no category' => 'bez kategorije', + 'Current assignee: %s' => 'Trenutni izvršilac: %s', + 'not assigned' => 'bez ivršioca', + 'Author:' => 'Autor:', + 'contributors' => 'saradnici', + 'License:' => 'Licenca:', + 'License' => 'Licenca', + 'Enter the text below' => 'Unesi tekst ispod', + 'Start date:' => 'Početno vrijeme:', + 'Due date:' => 'Vrijeme do kada treba završiti:', + 'People who are project managers' => 'Osobe koji su menadžeri projekta', + 'People who are project members' => 'Osobe koje su članovi projekta', + 'NOK - Norwegian Krone' => 'NOK - Norveška kruna', + 'Show this column' => 'Prikaži ovu kolonu', + 'Hide this column' => 'Sakrij ovu kolonu', + 'End date' => 'Datum završetka', + 'Users overview' => 'Opšti pregled korisnika', + 'Members' => 'Članovi', + 'Shared project' => 'Dijeljeni projekti', + 'Project managers' => 'Menadžeri projekta', + 'Projects list' => 'Lista projekata', + 'End date:' => 'Datum završetka:', + 'Change task color when using a specific task link' => 'Promijeni boju zadatka kada se koristi određena veza na zadatku', + 'Task link creation or modification' => 'Veza na zadatku je napravljena ili izmijenjena', + 'Milestone' => 'Prekretnica', + 'Reset the search/filter box' => 'Vrati na početno pretragu/filtere', + 'Documentation' => 'Dokumentacija', + 'Author' => 'Autor', + 'Version' => 'Verzija', + 'Plugins' => 'Dodaci', + 'There is no plugin loaded.' => 'Nema učitanih dodataka.', + 'My notifications' => 'Moja obavještenja', + 'Custom filters' => 'Prilagođeni filteri', + 'Your custom filter has been created successfully.' => 'Tvoj prilagođeni filter je uspješno napravljen.', + 'Unable to create your custom filter.' => 'Nemoguće napraviti prilagođeni filter.', + 'Custom filter removed successfully.' => 'Prilagođeni filter uspješno uklonjen.', + 'Unable to remove this custom filter.' => 'Nemoguće ukloniti prilagođeni filter.', + 'Edit custom filter' => 'Uredi prilagođeni filter', + 'Your custom filter has been updated successfully.' => 'Prilagođeni filter uspješno ažuriran.', + 'Unable to update custom filter.' => 'Nemoguće ažurirati prilagođeni filter', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Novi priložak na zadatku #%d: %s', + 'New comment on task #%d' => 'Novi komentar na zadatku #%d', + 'Comment updated on task #%d' => 'Ažuriran komentar na zadatku #%d', + 'New subtask on task #%d' => 'Novi pod-zadatak na zadatku #%d', + 'Subtask updated on task #%d' => 'Pod-zadatak ažuriran na zadatku #%d', + 'New task #%d: %s' => 'Novi zadatak #%d: %s', + 'Task updated #%d' => 'Zadatak ažuriran #%d', + 'Task #%d closed' => 'Zadatak #%d zatvoren', + 'Task #%d opened' => 'Zadatak #%d otvoren', + 'Column changed for task #%d' => 'Promijenjena kolona za zadatak #%d', + 'New position for task #%d' => 'Nova pozicija za zadatak #%d', + 'Swimlane changed for task #%d' => 'Swimlane traka promijenjena za zadatak #%d', + 'Assignee changed on task #%d' => 'Promijenjen izvršilac na zadatku #%d', + '%d overdue tasks' => '%d zadataka kasni', + 'No notification.' => 'Nema novih obavještenja.', + 'Mark all as read' => 'Označi sve kao pročitano', + 'Mark as read' => 'Označi kao pročitano', + 'Total number of tasks in this column across all swimlanes' => 'Ukupan broj zadataka u ovoj koloni u svim swimlane trakama', + 'Collapse swimlane' => 'Skupi swimlane trake', + 'Expand swimlane' => 'Proširi swimlane trake', + 'Add a new filter' => 'Dodaj novi filter', + 'Share with all project members' => 'Podijeli sa svim članovima projekta', + 'Shared' => 'Podijeljeno', + 'Owner' => 'Vlasnik', + 'Unread notifications' => 'Nepročitana obavještenja', + 'Notification methods:' => 'Metode obavještenja:', + 'Unable to read your file' => 'Nemoguće pročitati datoteku', + '%d task(s) have been imported successfully.' => '%d zadataka uspješno uvezeno.', + 'Nothing has been imported!' => 'Ništa nije uvezeno!', + 'Import users from CSV file' => 'Uvezi korisnike putem CSV datoteke', + '%d user(s) have been imported successfully.' => '%d korisnika uspješno uvezeno.', + 'Comma' => 'Zarez', + 'Semi-colon' => 'Tačka-zarez', + 'Tab' => 'Tab', + 'Vertical bar' => 'Vertikalna traka', + 'Double Quote' => 'Dvostruki navodnici', + 'Single Quote' => 'Jednostruki navodnici', + '%s attached a file to the task #%d' => '%s je dodao novu datoteku u zadatak %d', + 'There is no column or swimlane activated in your project!' => 'Nema kolone ili swimlane trake aktivirane za ovaj projekat!', + 'Append filter (instead of replacement)' => 'Dodaj filter (umjesto zamjene postojećeg)', + 'Append/Replace' => 'Dodaj/Zamijeni', + 'Append' => 'Dodaj', + 'Replace' => 'Zamijeni', + 'Import' => 'Uvoz', + 'Change sorting' => 'Promijeni sortiranje', + 'Tasks Importation' => 'Uvoz zadataka', + 'Delimiter' => 'Djelilac', + 'Enclosure' => 'Prilog', + 'CSV File' => 'CSV File', + 'Instructions' => 'Uputstva', + 'Your file must use the predefined CSV format' => 'File mora biti predefinisani CSV format', + 'Your file must be encoded in UTF-8' => 'File mora biti u UTF-8 kodu', + 'The first row must be the header' => 'Prvi red mora biti zaglavlje', + 'Duplicates are not verified for you' => 'Dupliciranja neće biti provjeravana (to ćeš morati uraditi ručno)', + 'The due date must use the ISO format: YYYY-MM-DD' => 'Datum do kog se treba izvršiti mora biti u ISO formatu: GGGG-MM-DD', + 'Download CSV template' => 'Preuzmi CSV šablon', + 'No external integration registered.' => 'Nema registrovanih vanjskih integracija.', + 'Duplicates are not imported' => 'Duplikati nisu uvezeni', + 'Usernames must be lowercase and unique' => 'Korisničko ime mora biti malim slovima i jedinstveno', + 'Passwords will be encrypted if present' => 'Šifra će biti kriptovana', + '%s attached a new file to the task %s' => '%s je dodao novu datoteku u zadatak %s', + 'Link type' => 'Tip veze', + 'Assign automatically a category based on a link' => 'Automatsko pridruživanje kategorije bazirano na vezi', + 'BAM - Konvertible Mark' => 'BAM - Konvertibilna marka', + 'Assignee Username' => 'Pridruži korisničko ime', + 'Assignee Name' => 'Pridruži ime', + 'Groups' => 'Grupe', + 'Members of %s' => 'Članovi %s', + 'New group' => 'Nova grupa', + 'Group created successfully.' => 'Grupa uspješno kreirana.', + 'Unable to create your group.' => 'Nemoguće kreirati grupu.', + 'Edit group' => 'Uredi grupu', + 'Group updated successfully.' => 'Grupa uspješno ažurirana.', + 'Unable to update your group.' => 'Nemoguće ažurirati grupu', + 'Add group member to "%s"' => 'Dodaj člana grupe u ""%s"', + 'Group member added successfully.' => 'Uspješno dodan član grupe.', + 'Unable to add group member.' => 'Nemoguće dodati člana grupe.', + 'Remove user from group "%s"' => 'Ukloni korisnika iz grupe "%s"', + 'User removed successfully from this group.' => 'Korisnik uspješno uklonjen iz grupe.', + 'Unable to remove this user from the group.' => 'Nemoguće ukloniti korisnika iz grupe.', + 'Remove group' => 'Ukloni grupu', + 'Group removed successfully.' => 'Grupa uspješno uklonjena.', + 'Unable to remove this group.' => 'Nemoguće ukloniti grupu.', + 'Project Permissions' => 'Prava na projektu', + 'Manager' => 'Menadžer', + 'Project Manager' => 'Menadžer projekta', + 'Project Member' => 'Član projekta', + 'Project Viewer' => 'Preglednik projekta', + 'Your account is locked for %d minutes' => 'Tvoj korisnički račun je zaključan za narednih %d minuta', + 'Invalid captcha' => 'Pogrešna captcha', + 'The name must be unique' => 'Ime mora biti jedinstveno', + 'View all groups' => 'Pregledaj sve grupe', + 'There is no user available.' => 'Trenutno nema dostupnih korisnika.', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Da li zaista želiš ukloniti korisnika "%s" iz grupe "%s"?', + 'There is no group.' => 'Trenutno nema grupa.', + 'Add group member' => 'Dodaj člana grupe', + 'Do you really want to remove this group: "%s"?' => 'Da li zaista želiš ukloniti ovu grupu: "%s"?', + 'There is no user in this group.' => 'Trenutno nema korisnika u grupi.', + 'Permissions' => 'Prava', + 'Allowed Users' => 'Dozvoljeni korisnici', + 'No specific user has been allowed.' => 'Nema korisnika sa specijalnim dozvolama.', + 'Role' => 'Uloge', + 'Enter user name...' => 'Unesi korisničko ime...', + 'Allowed Groups' => 'Dozvoljene grupe', + 'No group has been allowed.' => 'Nema grupa sa specijalnim dozvolama.', + 'Group' => 'Grupa', + 'Group Name' => 'Ime grupe', + 'Enter group name...' => 'Unesi ime grupe...', + 'Role:' => 'Uloga:', + 'Project members' => 'Članovi projekta', + '%s mentioned you in the task #%d' => '%s te spomenuo u zadatku #%d', + '%s mentioned you in a comment on the task #%d' => '%s te spomenuo u komentaru zadatka #%d', + 'You were mentioned in the task #%d' => 'Spomenut si u zadatku #%d', + 'You were mentioned in a comment on the task #%d' => 'Spomenut si u komentaru zadatka #%d', + 'Estimated hours: ' => 'Očekivani sati:', + 'Actual hours: ' => 'Aktuelni sati:', + 'Hours Spent' => 'Utrošeni sati:', + 'Hours Estimated' => 'Očekivani sati', + 'Estimated Time' => 'Očekivano vrijeme', + 'Actual Time' => 'Aktuelno vrijeme', + 'Estimated vs actual time' => 'Očekivano nasuprot aktuelnog vremena', + 'RUB - Russian Ruble' => 'RUB - Ruski rubij', + 'Assign the task to the person who does the action when the column is changed' => 'Dodijeli zadatak osobi koja izvrši akciju promjene kolone', + 'Close a task in a specific column' => 'Zatvori zadatak u određenoj koloni', + 'Time-based One-time Password Algorithm' => 'Vremenski bazirani One-time Algoritam šifri', + 'Two-Factor Provider: ' => '"Dva-faktora" provajder: ', + 'Disable two-factor authentication' => 'Onemogući "Dva-faktora" autentifikaciju', + 'Enable two-factor authentication' => 'Omogući "Dva-faktora" autentifikaciju', + 'There is no integration registered at the moment.' => 'Trenutno nema registrovanih integracija.', + 'Password Reset for Kanboard' => 'Promjena šifre za Kanboard', + 'Forgot password?' => 'Ne mogu da se sjetim šifre?', + 'Enable "Forget Password"' => 'Omogući "Ne mogu da se sjetim šifre"', + 'Password Reset' => 'Promijena šifre', + 'New password' => 'Nova šifra', + 'Change Password' => 'Promijeni šifru', + 'To reset your password click on this link:' => 'Da bi promijenio šifru klikni na ovaj link:', + 'Last Password Reset' => 'Posljednja promjena šifre', + 'The password has never been reinitialized.' => 'Šifra nije nikada mijenjena.', + 'Creation' => 'Napravljena', + 'Expiration' => 'Ističe', + 'Password reset history' => 'Hitorija promjena šifri', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Svi zadaci kolone "%s" i swimlane-a "%s" uspješno su zatvoreni.', + 'Do you really want to close all tasks of this column?' => 'Da li zaista želiš zatvoriti sve zadatke ove kolone?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d zadatak(ci) u koloni "%s" i swimlane-u "%s" će biti zatvoreni.', + 'Close all tasks in this column and this swimlane' => 'Zatvori sve zadatke ove kolone', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Nema posebnog Dodatka za obavještenja. Još uvijek možeš koristiti individualne postavke obavještenja na svom profilu.', + 'My dashboard' => 'Moj panel', + 'My profile' => 'Moj profil', + 'Project owner: ' => 'Vlasnik projekta: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'Identifikator projekta je opcionalan i mora biti alfanumerički, na primjer: MOJPROJEKAT.', + 'Project owner' => 'Vlasnik projekta', + 'Personal projects do not have users and groups management.' => 'Privatni projekti ne mogu imati korisnike ili grupe korisnika.', + 'There is no project member.' => 'Nema članova projekta.', + 'Priority' => 'Prioritet', + 'Task priority' => 'Prioritet zadatka', + 'General' => 'Opšte', + 'Dates' => 'Datumi', + 'Default priority' => 'Podrazumijevani prioritet', + 'Lowest priority' => 'Najmanji prioritet', + 'Highest priority' => 'Najveći prioritet', + 'Close a task when there is no activity' => 'Zatvori zadatak kada nema aktivnosti', + 'Duration in days' => 'Dužina trajanja u danima', + 'Send email when there is no activity on a task' => 'Pošalji email kada nema aktivnosti na zadatku', + 'Unable to fetch link information.' => 'Ne mogu da pribavim informacije o vezi.', + 'Daily background job for tasks' => 'Dnevni pozadinski poslovi na zadacima', + 'Auto' => 'Automatski', + 'Related' => 'Povezani', + 'Attachment' => 'Dodijeljeni', + 'Web Link' => 'Web veza', + 'External links' => 'Vanjska veza', + 'Add external link' => 'Dodaj vanjsku vezu', + 'Type' => 'Vrsta', + 'Dependency' => 'Zavisnost', + 'Add internal link' => 'Dodaj unutrašnju vezu', + 'Add a new external link' => 'Dodaj novu vanjsku vezu', + 'Edit external link' => 'Uredi vanjsku vezu', + 'External link' => 'Vanjska veza', + 'Copy and paste your link here...' => 'Kopiraj i zalijepi svoju vezu ovdje...', + 'URL' => 'URL', + 'Internal links' => 'Unutrašnje veze', + 'Assign to me' => 'Dodijeli meni', + 'Me' => 'Za mene', + 'Do not duplicate anything' => 'Ništa ne dupliciraj', + 'Projects management' => 'Menadžment projekata', + 'Users management' => 'Menadžment korisnika', + 'Groups management' => 'Menadžment grupa', + 'Create from another project' => 'Napravi iz drugog projekta', + 'open' => 'otvoreno', + 'closed' => 'zatvoreno', + 'Priority:' => 'Prioritet:', + 'Reference:' => 'Preporuka:', + 'Complexity:' => 'Složenost:', + 'Swimlane:' => 'Swimlane:', + 'Column:' => 'Kolona:', + 'Position:' => 'Pozicija:', + 'Creator:' => 'Kreator:', + 'Time estimated:' => 'Očekivano vrijeme:', + '%s hours' => '%s sati', + 'Time spent:' => 'Utrošeno vrijeme:', + 'Created:' => 'Kreirao:', + 'Modified:' => 'Uredio:', + 'Completed:' => 'Završio:', + 'Started:' => 'Početo:', + 'Moved:' => 'Pomjereno:', + 'Task #%d' => 'Zadatak #%d', + 'Time format' => 'Format za vrijeme', + 'Start date: ' => 'Početni datum:', + 'End date: ' => 'Krajnji datum:', + 'New due date: ' => 'Novi datum očekivanja:', + 'Start date changed: ' => 'Početni datum promijenjen:', + 'Disable personal projects' => 'Onemogući privatne projekte', + 'Do you really want to remove this custom filter: "%s"?' => 'Da li zaista želiš ukloniti ovaj prilagođeni filter "%s"?', + 'Remove a custom filter' => 'Ukloni prilagođeni filter', + 'User activated successfully.' => 'Korisnik uspješno aktiviran.', + 'Unable to enable this user.' => 'Nemoguće omogućiti ovog korisnika.', + 'User disabled successfully.' => 'Korisnik uspješno onemogućen.', + 'Unable to disable this user.' => 'Nemoguće onemogućiti ovog korisnika.', + 'All files have been uploaded successfully.' => 'Sve datoteke su uspješno dodane.', + 'The maximum allowed file size is %sB.' => 'Maksimalna dozvoljena veličina datoteke je %sB.', + 'Drag and drop your files here' => 'Povuci i spusti svoje datoteke ovdje', + 'choose files' => 'izaberi datoteke', + 'View profile' => 'Pregledaj profil', + 'Two Factor' => 'Dva faktora', + 'Disable user' => 'Onemogući korisnika', + 'Do you really want to disable this user: "%s"?' => 'Da li zaista želiš onemogućiti ovog korisnika: "%s"?', + 'Enable user' => 'Omogući korisnika', + 'Do you really want to enable this user: "%s"?' => 'Da li zaista želiš omogućiti ovog korisnika: "%s"?', + 'Download' => 'Preuzeto', + 'Uploaded: %s' => 'Dodano: %s', + 'Size: %s' => 'Veličina: %s', + 'Uploaded by %s' => 'Dodao %s', + 'Filename' => 'Ime datoteke', + 'Size' => 'Veličina', + 'Column created successfully.' => 'Kolona uspješno napravljena.', + 'Another column with the same name exists in the project' => 'Već postoji kolona s istim imenom u ovom projektu.', + 'Default filters' => 'Podrazumijevani filteri', + 'Your board doesn\'t have any columns!' => 'Tvoj panel nema ni jednu kolonu!', + 'Change column position' => 'Promijeni poziciju kolone', + 'Switch to the project overview' => 'Promijeni u pregled projekta', + 'User filters' => 'Korisnički filteri', + 'Category filters' => 'Kategorija filtera', + 'Upload a file' => 'Priloži datoteku', + 'View file' => 'Pregled datoteke', + 'Last activity' => 'Posljednja aktivnost', + 'Change subtask position' => 'Promijeni poziciju pod-zadatka', + 'This value must be greater than %d' => 'Ova vrijednost mora biti veća od %d', + 'Another swimlane with the same name exists in the project' => 'Već postoji swimlane sa istim imenom u ovom projektu', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Na primjer: https://example.kanboard.org/ (koristi se za pravljenje apsolutnog URL-a)', + 'Actions duplicated successfully.' => 'Akcije uspješno duplicirane.', + 'Unable to duplicate actions.' => 'Nemoguće duplicirati akcije.', + 'Add a new action' => 'Dodaj novu akciju', + 'Import from another project' => 'Uvezi iz drugog projekta', + 'There is no action at the moment.' => 'Trenutno nema akcija.', + 'Import actions from another project' => 'Uvezi akcije iz drugog projekta', + 'There is no available project.' => 'Trenutno nema dostupnih projekata.', + 'Local File' => 'Lokalna datoteka', + 'Configuration' => 'Konfiguracija', + 'PHP version:' => 'Verzija PHP-a:', + 'PHP SAPI:' => 'Verzija SAPI-a:', + 'OS version:' => 'Verzija OS-a:', + 'Database version:' => 'Verzija baze podataka:', + 'Browser:' => 'Pretraživač:', + 'Task view' => 'Pregled zadatka', + 'Edit task' => 'Uredi zadatak', + 'Edit description' => 'Uredi opis', + 'New internal link' => 'Nova unutrašnja veza', + 'Display list of keyboard shortcuts' => 'Prikaži listu prečica na tastaturi', + 'Avatar' => 'Avatar', + 'Upload my avatar image' => 'Dodaj sliku za moj avatar', + 'Remove my image' => 'Ukloni moju sliku', + 'The OAuth2 state parameter is invalid' => 'OAuth2 status parametar nije validan', + 'User not found.' => 'Korisnik nije pronađen.', + 'Search in activity stream' => 'Pretraži aktivnosti', + 'My activities' => 'Moje aktivnosti', + 'Activity until yesterday' => 'Aktivnosti do jučer', + 'Activity until today' => 'Aktivnosti do danas', + 'Search by creator: ' => 'Pretraga po kreatoru: ', + 'Search by creation date: ' => 'Pretraga po datumu kreiranja: ', + 'Search by task status: ' => 'Pretraga po statusu zadatka: ', + 'Search by task title: ' => 'Pretraga po naslovu zadatka: ', + 'Activity stream search' => 'Pretraga aktivnosti', + 'Projects where "%s" is manager' => 'Projekti gdje je "%s" menadžer', + 'Projects where "%s" is member' => 'Projekti gdje je "%s" član', + 'Open tasks assigned to "%s"' => 'Otvoreni zadaci dodijeljeni "%s"', + 'Closed tasks assigned to "%s"' => 'Zatvoreni zadaci dodijeljeni "%s"', + 'Assign automatically a color based on a priority' => 'Dodijeli boju automatski bazirano na prioritetu', + 'Overdue tasks for the project(s) "%s"' => 'Zadaci u kašnjenju za projekat(te) "%s"', + 'Upload files' => 'Priloži dokumente', + 'Installed Plugins' => 'Instalirani dodaci', + 'Plugin Directory' => 'Direktorij dodataka', + 'Plugin installed successfully.' => 'Dodatak je uspješno instaliran.', + 'Plugin updated successfully.' => 'Dodatak je uspješno ažuriran.', + 'Plugin removed successfully.' => 'Dodatak uspješno uklonjen.', + 'Subtask converted to task successfully.' => 'Pod-zadatak uspješno pretvoren u zadatak.', + 'Unable to convert the subtask.' => 'Nemoguće pretvoriti pod-zadatak.', + 'Unable to extract plugin archive.' => 'Nemoguće otpakovati arhivu dodatka.', + 'Plugin not found.' => 'Dodatak nije pronađen.', + 'You don\'t have the permission to remove this plugin.' => 'Nemate prava za uklanjanje ovog dodatka.', + 'Unable to download plugin archive.' => 'Nemoguće preuzeti arhivu dodatka.', + 'Unable to write temporary file for plugin.' => 'Nemoguće zapisati privremenu datoteku za dodatak.', + 'Unable to open plugin archive.' => 'Nemoguće otvoriti arhivu dodatka.', + 'There is no file in the plugin archive.' => 'Nema datoteke u arhivi dodatka.', + 'Create tasks in bulk' => 'Kreiranje zadataka u rinfuzi', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Ova Kanboard nije konfigurisan tako da podržava instalaciju dodataka kroz korisničko okruženje.', + 'There is no plugin available.' => 'Dodaci nisu dostupni.', + 'Install' => 'Instaliraj', + 'Update' => 'Ažuriraj', + 'Up to date' => 'Ažurirano', + 'Not available' => 'Nije dostupno', + 'Remove plugin' => 'Ukloni dodatak', + 'Do you really want to remove this plugin: "%s"?' => 'Da li zaista želiš ukloniti ovaj dodatak: "%s"?', + 'Uninstall' => 'Deinstaliraj', + 'Listing' => 'Spisak', + 'Metadata' => 'Metadata', + 'Manage projects' => 'Upravljanje projektima', + 'Convert to task' => 'Pretvori u zadatak', + 'Convert sub-task to task' => 'Pretvori pod-zadatak u zadatak', + 'Do you really want to convert this sub-task to a task?' => 'Da li zaista želiš pretvoriti pod-zadatak u zadatak?', + 'My task title' => 'Naslov zadatka', + 'Enter one task by line.' => 'Unesi jedan zadatak po liniji.', + 'Number of failed login:' => 'Broj neuspješnih prijava:', + 'Account locked until:' => 'Korisnički račun zaključan do:', + 'Email settings' => 'Postavke email-a', + 'Email sender address' => 'Email adresa pošiljaoca', + 'Email transport' => 'Saobraćaj emil-a', + 'Webhook token' => 'Webhook token', + 'Project tags management' => 'Upravljanje oznakama projekta', + 'Tag created successfully.' => 'Oznaka uspješno kreirana.', + 'Unable to create this tag.' => 'Nemoguće kreirati oznaku.', + 'Tag updated successfully.' => 'Oznaka uspješno ažurirana.', + 'Unable to update this tag.' => 'Nemoguće ažurirati ovu oznaku.', + 'Tag removed successfully.' => 'Oznaka uspješno uklonjena.', + 'Unable to remove this tag.' => 'Nemoguće ukloniti ovu oznaku.', + 'Global tags management' => 'Upravljanje opštih oznaka.', + 'Tags' => 'Oznake', + 'Tags management' => 'Upravljanje oznakama', + 'Add new tag' => 'Dodaj novu oznaku', + 'Edit a tag' => 'Uredi oznaku', + 'Project tags' => 'Oznake projekta', + 'There is no specific tag for this project at the moment.' => 'Trenutno nema oznaka za ovaj projekat.', + 'Tag' => 'Oznaka', + 'Remove a tag' => 'Ukloni oznaku', + 'Do you really want to remove this tag: "%s"?' => 'Da li zaista želiš ukloniti ovu oznaku: "%s"?', + 'Global tags' => 'Opšte oznake', + 'There is no global tag at the moment.' => 'Trenutno nema opštih oznaka.', + 'This field cannot be empty' => 'Ovo polje ne može biti prazno', + 'Close a task when there is no activity in a specific column' => 'Zatvori zadatak kada nema aktivnosti u odabranoj koloni', + '%s removed a subtask for the task #%d' => '%s je uklonio pod-zadatak za zadatak #%d', + '%s removed a comment on the task #%d' => '%s je uklonio komentar na zadatku #%d', + 'Comment removed on task #%d' => 'Komentar ukloljen na zadatku #%d', + 'Subtask removed on task #%d' => 'Pod-zadatak ukloljn na zadatku #%d', + 'Hide tasks in this column in the dashboard' => 'Skrij zadatke u koloni na radnoj ploči', + '%s removed a comment on the task %s' => '%s je uklonio komentar za zadatak %s', + '%s removed a subtask for the task %s' => '%s je uklonio pod-zadatak za zadatak %s', + 'Comment removed' => 'Komentar uklonjen', + 'Subtask removed' => 'Pod-zadatak uklonjen', + '%s set a new internal link for the task #%d' => '%s je dodao novu unutrašnju vezu za zadatak #%d', + '%s removed an internal link for the task #%d' => '%s je uklonio unutrašnju vezu za zadatak #%d', + 'A new internal link for the task #%d has been defined' => 'Nova unutrašnja veza za zadatak #%d je definisana', + 'Internal link removed for the task #%d' => 'Unutrašnja veza je uklonjena za zadatak #%d', + '%s set a new internal link for the task %s' => '%s je dodao novu unutrašnju vezu za zadatak %s', + '%s removed an internal link for the task %s' => '%s je uklonio unutrašnju vezu za zadatak %s', + 'Automatically set the due date on task creation' => 'Automatski dodaj datum roka izvršenja na kreiranom zadatku', + 'Move the task to another column when closed' => 'Pomjeri zadatak u drugu kolonu kada je zatvoren', + 'Move the task to another column when not moved during a given period' => 'Pomjeri zadatak u drugu kolonu kada nema pomijeranja za zadati period', + 'Dashboard for %s' => 'Radna ploča za %s', + 'Tasks overview for %s' => 'Pregled zadataka za %s', + 'Subtasks overview for %s' => 'Pregled pod-zadataka za %s', + 'Projects overview for %s' => 'Pregled projekata za %s', + 'Activity stream for %s' => 'Tok aktivnosti za %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Dodijeli boju kada je zadatak pomjeren u određenu swimlane traku', + 'Assign a priority when the task is moved to a specific swimlane' => 'Dodijeli prioritet kada je zadatak pomjeren u određenu swimlane traku', + 'User unlocked successfully.' => 'Korisnik je uspješno otključan.', + 'Unable to unlock the user.' => 'Nemoguće otključati korisnika.', + 'Move a task to another swimlane' => 'Pomjeri zadatak u drugu swimlane traku', + 'Creator Name' => 'Ime kreatora', + 'Time spent and estimated' => 'Utrošeno i očekivano vrijeme', + 'Move position' => 'Mjesto pomijeranja', + 'Move task to another position on the board' => 'Pomjeri zadataka na drugo mjesto na radnoj ploči', + 'Insert before this task' => 'Umetni prije ovog zadatka', + 'Insert after this task' => 'Umetni poslije ovog zadatka', + 'Unlock this user' => 'Otključaj ovog korisnika', + 'Custom Project Roles' => 'Prilagođne uloge projekta', + 'Add a new custom role' => 'Dodaj novu prilagođenu ulogu', + 'Restrictions for the role "%s"' => 'Ograničenja za ulogu "%s"', + 'Add a new project restriction' => 'Dodaj nova ograničenja na projektu', + 'Add a new drag and drop restriction' => 'Dodaj nova "prevuci i spusti" ograničenja', + 'Add a new column restriction' => 'Dodaj nova ograničenja za kolonu', + 'Edit this role' => 'Uredi ovu ulogu', + 'Remove this role' => 'Ukloni ovu ulogu', + 'There is no restriction for this role.' => 'Nema ograničenja za ovu ulogu.', + 'Only moving task between those columns is permitted' => 'Samo između ovih kolona je dozvoljeno pomijeranje', + 'Close a task in a specific column when not moved during a given period' => 'Zatvori zadatak u odabranoj koloni kada nema pomijeranja za zadati period', + 'Edit columns' => 'Uredi kolone', + 'The column restriction has been created successfully.' => 'Ograničenja za kolonu su uspješno kreirana.', + 'Unable to create this column restriction.' => 'Nemoguće kreirati ograničenja za kolonu.', + 'Column restriction removed successfully.' => 'Ograničenja za kolonu su uspješno uklonjena.', + 'Unable to remove this restriction.' => 'Nemoguće ukloniti ovo ograničenje.', + 'Your custom project role has been created successfully.' => 'Tvoja prilagođena uloga na projekatu je uspješno kreirana.', + 'Unable to create custom project role.' => 'Nemoguće kreirati prilagođenu ulogu na projektu.', + 'Your custom project role has been updated successfully.' => 'Tvoja prilagođena uloga na projekatu je uspješno ažurirana.', + 'Unable to update custom project role.' => 'Nemoguće ažurirati prilagođenu ulogu na projektu.', + 'Custom project role removed successfully.' => 'Prilagođena uloga na projektu uspješno uklonjena.', + 'Unable to remove this project role.' => 'Nemoguće ukloniti ovu ulogu na projektu.', + 'The project restriction has been created successfully.' => 'Ograničenja na projektu uspješno kreirana.', + 'Unable to create this project restriction.' => 'Nemoguće kreirati ovo ograničenje na projektu.', + 'Project restriction removed successfully.' => 'Ograničenje na projektu uspješno uklonjeno.', + 'You cannot create tasks in this column.' => 'Ne možete kreirati zadatak u ovoj koloni.', + 'Task creation is permitted for this column' => 'Kreiranje zadatka u ovoj koloni je zabranjeno', + 'Closing or opening a task is permitted for this column' => 'Zatvaranje ili otvaranje zadatka u ovoj koloni je zabranjeno', + 'Task creation is blocked for this column' => 'Kreiranje zadatka je onemogućeno za ovu kolonu', + 'Closing or opening a task is blocked for this column' => 'Zatvaranje ili otvaranje zadatka u ovoj koloni je onemogućeno', + 'Task creation is not permitted' => 'Kreiranje zadatka nije dozvoljeno', + 'Closing or opening a task is not permitted' => 'Zatvarnaje ili otvaranje zadatka nije dozvoljeno', + 'New drag and drop restriction for the role "%s"' => 'Novo "prevuci i spusti" ograničenje za ulogu "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Osobe kojima se dodijeli ova uloga će biti u mogućnosti pomijerati zadatke samo imeđu izvorne i odredišne kolone.', + 'Remove a column restriction' => 'Ukloni ograničenje za kolonu', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Da li zaista želiš uklonit ovo ograničenje za kolonu: "%s" u "%s"?', + 'New column restriction for the role "%s"' => 'Novo ograničenje za kolonu za ulogu "%s"', + 'Rule' => 'Uloga', + 'Do you really want to remove this column restriction?' => 'Da li zaista želiš ukloniti ovo ograničenje za kolonu?', + 'Custom roles' => 'Prilagođene uloge', + 'New custom project role' => 'Nova prilagođena uloga za projekat', + 'Edit custom project role' => 'Uredi prilagođenu ulogu za projekat', + 'Remove a custom role' => 'Ukloni prilagođenu ulogu', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Da li zaista želiš ukloniti ovu prilagođenu ulogu: "%s"? Sve osobe kojima je dodijeljena ova uloga će postati članovi projekta.', + 'There is no custom role for this project.' => 'Nema prilagođenih uloga za ovaj projekat.', + 'New project restriction for the role "%s"' => 'Novo ograničenje na projektu za ulogu "%s"', + 'Restriction' => 'Ograničenje', + 'Remove a project restriction' => 'Ukloni ograničenje na projektu', + 'Do you really want to remove this project restriction: "%s"?' => 'Da li zaista želiš ukloniti ovo ograničenje na projektu: "%s"?', + 'Duplicate to multiple projects' => 'Dupliciraj u više projekata', + 'This field is required' => 'Ovo polje je obavezno', + 'Moving a task is not permitted' => 'Pomijeranje zadatka nije dozvoljeno', + 'This value must be in the range %d to %d' => 'Ova vrijednost mora biti u rasponu od %d do %d', + 'You are not allowed to move this task.' => 'Nemaš dozvolu za pomijeranje ovog zadatka.', + 'API User Access' => 'API User Access', + 'Preview' => 'Pregled', + 'Write' => 'Piši', + 'Write your text in Markdown' => 'Upiši tekst u Markdown', + 'No personal API access token registered.' => 'Nema registrovanog ličnog pristupnog API tokena', + 'Your personal API access token is "%s"' => 'Tvoj lični pristupni API token je: "%s"', + 'Remove your token' => 'Ukloni svoj token', + 'Generate a new token' => 'Generiši novi token', + 'Showing %d-%d of %d' => 'Prikaži %d-%d od %d', + 'Outgoing Emails' => 'Izlazni email-ovi', + 'Add or change currency rate' => 'Dodaj ili izmijeni odnos valuta', + 'Reference currency: %s' => 'Referentna valuta: %s', + 'Add custom filters' => 'Dodaj prilagođene filtere', + 'Export' => 'Izvezi', + 'Add link label' => 'Dodaj etiketu na vezu', + 'Incompatible Plugins' => 'Nekompatibilni dodaci', + 'Compatibility' => 'Kompatibilnost', + 'Permissions and ownership' => 'Dozvole i vlasništvo', + 'Priorities' => 'Prioriteti', + 'Close this window' => 'Zatvori ovaj pendžer', + 'Unable to upload this file.' => 'Nemoguće dodati ovu datoteku.', + 'Import tasks' => 'Uvezi zadatke.', + 'Choose a project' => 'Izaberi projekat', + 'Profile' => 'Profil', + 'Application role' => 'Uloga na aplikaciji', + '%d invitations were sent.' => 'Poslano %d pozivnica.', + '%d invitation was sent.' => 'Poslana %d pozivnica.', + 'Unable to create this user.' => 'Nemoguće kreirati ovog korisnika.', + 'Kanboard Invitation' => 'Kanboard Pozivnica', + 'Visible on dashboard' => 'Vidljivo na radnoj ploči', + 'Created at:' => 'Kreirao:', + 'Updated at:' => 'Ažurirao:', + 'There is no custom filter.' => 'Nema prilagođenih filtera.', + 'New User' => 'Novi korisnik', + 'Authentication' => 'Autentikacija', + 'If checked, this user will use a third-party system for authentication.' => 'Ako je označeno, ovaj korisnik će koristiti "treću stranu" za autentikaciju.', + 'The password is necessary only for local users.' => 'Šifra je neophodna samo za lokalne korisnike.', + 'You have been invited to register on Kanboard.' => 'Pozvan si da se registruješ na Kanboard.', + 'Click here to join your team' => 'Klikni ovdje da se pridružiš timu', + 'Invite people' => 'Pozovi ljude', + 'Emails' => 'Email-ovi', + 'Enter one email address by line.' => 'Unesi jednu email adresu po redu.', + 'Add these people to this project' => 'Dodaj ove ljude u ovaj projekat', + 'Add this person to this project' => 'Dodaj ovu osobu u ovaj projekat', + 'Sign-up' => 'Registracija', + 'Credentials' => 'Pristupni podaci', + 'New user' => 'Novi korisnik', + 'This username is already taken' => 'Ovo korisničko ime je zauzeto', + 'Your profile must have a valid email address.' => 'Profil mora imati validnu email adresu.', + 'TRL - Turkish Lira' => 'TRL - Turska Lira', + 'The project email is optional and could be used by several plugins.' => 'Email projekta je prizvoljan i može biti korišten od strane dodataka.', + 'The project email must be unique across all projects' => 'Email projekta mora biti jedinstven za svaki projekat', + 'The email configuration has been disabled by the administrator.' => 'Uređivanje email-a je onemogućeno od strane administratora.', + 'Close this project' => 'Zatvori ovaj projekat', + 'Open this project' => 'Otvori ovaj projekat', + 'Close a project' => 'Zatvori projekat', + 'Do you really want to close this project: "%s"?' => 'Da li zaista želiš zatvoriti projekat: "%s"?', + 'Reopen a project' => 'Ponovo otvori projekat', + 'Do you really want to reopen this project: "%s"?' => 'Da li zaista želiš ponovo otvoriti projekat: "%s"?', + 'This project is open' => 'Ovaj projekat je otvoren', + 'This project is closed' => 'Ovaj projekat je zatvoren', + 'Unable to upload files, check the permissions of your data folder.' => 'Nemoguće priložiti datoteke, provjeri prava na "data" direktoriju na serveru.', + 'Another category with the same name exists in this project' => 'Kategorija sa ovim imenom već postoji na ovom projektu', + 'Comment sent by email successfully.' => 'Komentar je uspješno poslan email-om.', + 'Sent by email to "%s" (%s)' => 'Pošalji email-om "%s" (%s)', + 'Unable to read uploaded file.' => 'Nemoguće pročitati dodanu datoteku.', + 'Database uploaded successfully.' => 'Baza podataka je uspješno dodana.', + 'Task sent by email successfully.' => 'Zadatak je uspješno poslan email-om.', + 'There is no category in this project.' => 'Nema kategorija u ovom projektu.', + 'Send by email' => 'Pošalji email-om', + 'Create and send a comment by email' => 'Kreiraj i pošalji komentar email-om', + 'Subject' => 'Predmet', + 'Upload the database' => 'Priloži u bazu podataka', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Možeš priložiti prethodno preuzetu Sqlite bazu podataka (Gzip format).', + 'Database file' => 'Datoteka baze podataka', + 'Upload' => 'Priloži', + 'Your project must have at least one active swimlane.' => 'Projekat mora imati barem jednu aktivnu swimlane traku.', + 'Project: %s' => 'Projekat: %s', + 'Automatic action not found: "%s"' => 'Automatska akcija nije pronađena: "%s"', + '%d projects' => '%d projekata', + '%d project' => '%d projekat', + 'There is no project.' => 'Ovo nije projekat.', + 'Sort' => 'Sortiraj', + 'Project ID' => 'ID projekta', + 'Project name' => 'Naziv projekta', + 'Public' => 'Javno', + 'Personal' => 'Privatno', + '%d tasks' => '%d zadataka', + '%d task' => '%d zadatak', + 'Task ID' => 'ID zadatka', + 'Assign automatically a color when due date is expired' => 'Automatski dodaj boju kada je krajnje vrijeme izvršenja isteklo', + 'Total score in this column across all swimlanes' => 'Ukupni rezultat u ovoj koloni za sve swimlane trake', + 'HRK - Kuna' => 'HRK - Hrvatska Kuna', + 'ARS - Argentine Peso' => 'ARS - Argentinski pezo', + 'COP - Colombian Peso' => 'COP - Kolumbijski pezo', + '%d groups' => '%d grupa', + '%d group' => '%d grupa', + 'Group ID' => 'ID grupe', + 'External ID' => 'Vanjski ID', + '%d users' => '%d korisnika', + '%d user' => '%d korisnik', + 'Hide subtasks' => 'Sakrij pod-zadatke', + 'Show subtasks' => 'Prikaži pod-zadatke', + 'Authentication Parameters' => 'Parametri autentifikacije', + 'API Access' => 'API pristup', + 'No users found.' => 'Nema pronađenih korisnika.', + 'User ID' => 'ID korisnika', + 'Notifications are activated' => 'Obavještenja su aktivirana', + 'Notifications are disabled' => 'Obavještenja su onemogućena', + 'User disabled' => 'Korisnik onemogućen', + '%d notifications' => '%d obavještenja', + '%d notification' => '%d obavještenje', + 'There is no external integration installed.' => 'Nema instaliranih vanjskih integracija.', + 'You are not allowed to update tasks assigned to someone else.' => 'Nemaš dozvolu za ažuriranje zadataka dodijeljenih drugima.', + 'You are not allowed to change the assignee.' => 'Nemaš dozvolu za promjenu izvršioca.', + 'Task suppression is not permitted' => 'Brisanje zadatka nije dozvoljeno', + 'Changing assignee is not permitted' => 'Promjena izvršioca nije dozvoljena', + 'Update only assigned tasks is permitted' => 'Ažuriranje samo dodijeljenih zadataka je dozvoljeno', + 'Only for tasks assigned to the current user' => 'Samo za zadatke dodijeljene trenutnom korisniku', + 'My projects' => 'Moji projekti', + 'You are not a member of any project.' => 'Nisi član ni jednog projekta.', + 'My subtasks' => 'Moji pod-zadaci', + '%d subtasks' => '%d pod-zadataka', + '%d subtask' => '%d pod-zadatak', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Pomijeranje zadataka između ovih kolona je dozvoljeno samo za zadatke dodijeljene trenutnom korisniku', + '[DUPLICATE]' => '[DUPLIKAT]', + 'DKK - Danish Krona' => 'DKK - Danska kruna', + 'Remove user from group' => 'Ukloni korisnika iz grupe', + 'Assign the task to its creator' => 'Dodijeli zadatak njegovom kreatoru', + 'This task was sent by email to "%s" with subject "%s".' => 'Ovaj zadatak je poslan email-om "%s" sa predmetom "%s".', + 'Predefined Email Subjects' => 'Predefinisani predmeti email-a', + 'Write one subject by line.' => 'Napiši jedan predmet po liniji.', + 'Create another link' => 'Kreiraj drugu vezu', + 'BRL - Brazilian Real' => 'BRL - Brazilski real', + 'Add a new Kanboard task' => 'Dodaj novi Kanboard zadatak', + 'Subtask not started' => 'Pod-zadatak nije započet', + 'Subtask currently in progress' => 'Pod-zadatak trenutno u toku', + 'Subtask completed' => 'Pod-zadatak završen', + 'Subtask added successfully.' => 'Pod-zadatak uspješno dodan.', + '%d subtasks added successfully.' => '%d pod-zadataka uspješno dodano.', + 'Enter one subtask by line.' => 'Unesi jedan pod-zadatak po liniji.', + 'Predefined Contents' => 'Predefinisani sadržaji', + 'Predefined contents' => 'Predefinisani sadržaji', + 'Predefined Task Description' => 'Predefinisani opis zadatka', + 'Do you really want to remove this template? "%s"' => 'Da li zaista želiš ukloniti ovaj šablon? "%s"', + 'Add predefined task description' => 'Dodaj predefinisani opis zadatka', + 'Predefined Task Descriptions' => 'Predefinisani opisi zadataka', + 'Template created successfully.' => 'Šablon uspješno kreiran.', + 'Unable to create this template.' => 'Nemoguće kreirati ovaj šablon.', + 'Template updated successfully.' => 'Šablon uspješno ažuriran.', + 'Unable to update this template.' => 'Nemoguće ažurirati ovaj šablon.', + 'Template removed successfully.' => 'Šablon uspješno uklonjen.', + 'Unable to remove this template.' => 'Nemoguće ukloniti ovaj šablon.', + 'Template for the task description' => 'Šablon za opis zadatka', + 'The start date is greater than the end date' => 'Početni datum je veći od krajnjeg datuma', + 'Tags must be separated by a comma' => 'Oznake moraju biti odvojene zarezom', + 'Only the task title is required' => 'Samo je naslov zadatka obavezan', + 'Creator Username' => 'Korisničko ime kreatora', + 'Color Name' => 'Naziv boje', + 'Column Name' => 'Naziv kolone', + 'Swimlane Name' => 'Naziv swimlane trake', + 'Time Estimated' => 'Procijenjeno vrijeme', + 'Time Spent' => 'Utrošeno vrijeme', + 'External Link' => 'Vanjska veza', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Ova funkcionalnost omogućava iCal feed, RSS feed i javni pregled ploče.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Zaustavi tajmer svih pod-zadataka kada se zadatak premjesti u drugu kolonu', + 'Subtask Title' => 'Naslov pod-zadatka', + 'Add a subtask and activate the timer when moving a task to another column' => 'Dodaj pod-zadatak i aktiviraj tajmer kada se zadatak premjesti u drugu kolonu', + 'days' => 'dana', + 'minutes' => 'minuta', + 'seconds' => 'sekundi', + 'Assign automatically a color when preset start date is reached' => 'Automatski dodijeli boju kada se dostigne unaprijed postavljeni početni datum', + 'Move the task to another column once a predefined start date is reached' => 'Premjesti zadatak u drugu kolonu kada se dostigne unaprijed postavljeni početni datum', + 'This task is now linked to the task %s with the relation "%s"' => 'Ovaj zadatak je sada povezan sa zadatkom %s sa relacijom "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'Veza sa relacijom "%s" prema zadatku %s je uklonjena', + 'Custom Filter:' => 'Prilagođeni filter:', + 'Unable to find this group.' => 'Nemoguće pronaći ovu grupu.', + '%s moved the task #%d to the column "%s"' => '%s je premjestio zadatak #%d u kolonu "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => '%s je premjestio zadatak #%d na poziciju %d u koloni "%s"', + '%s moved the task #%d to the swimlane "%s"' => '%s je premjestio zadatak #%d u swimlane traku "%s"', + '%sh spent' => '%sh utrošeno', + '%sh estimated' => '%sh procijenjeno', + 'Select All' => 'Označi sve', + 'Unselect All' => 'Poništi sve', + 'Apply action' => 'Primijeni akciju', + 'Move selected tasks to another column or swimlane' => 'Premjesti odabrane zadatke u drugu kolonu ili swimlane traku', + 'Edit tasks in bulk' => 'Uredi zadatke u rinfuzi', + 'Choose the properties that you would like to change for the selected tasks.' => 'Izaberi svojstva koja želiš promijeniti za odabrane zadatke.', + 'Configure this project' => 'Konfiguriši ovaj projekat', + 'Start now' => 'Počni sada', + '%s removed a file from the task #%d' => '%s je uklonio datoteku sa zadatka #%d', + 'Attachment removed from task #%d: %s' => 'Prilog uklonjen sa zadatka #%d: %s', + 'No color' => 'Bez boje', + 'Attachment removed "%s"' => 'Prilog uklonjen "%s"', + '%s removed a file from the task %s' => '%s je uklonio datoteku sa zadatka %s', + 'Move the task to another swimlane when assigned to a user' => 'Premjesti zadatak u drugu swimlane traku kada je dodijeljen korisniku', + 'Destination swimlane' => 'Odredišna swimlane traka', + 'Assign a category when the task is moved to a specific swimlane' => 'Dodijeli kategoriju kada je zadatak premješten u određenu swimlane traku', + 'Move the task to another swimlane when the category is changed' => 'Premjesti zadatak u drugu swimlane traku kada je kategorija promijenjena', + 'Reorder this column by priority (ASC)' => 'Presortiraj ovu kolonu po prioritetu (ASC)', + 'Reorder this column by priority (DESC)' => 'Presortiraj ovu kolonu po prioritetu (DESC)', + 'Reorder this column by assignee and priority (ASC)' => 'Presortiraj ovu kolonu po izvršiocu i prioritetu (ASC)', + 'Reorder this column by assignee and priority (DESC)' => 'Presortiraj ovu kolonu po izvršiocu i prioritetu (DESC)', + 'Reorder this column by assignee (A-Z)' => 'Presortiraj ovu kolonu po izvršiocu (A-Z)', + 'Reorder this column by assignee (Z-A)' => 'Presortiraj ovu kolonu po izvršiocu (Z-A)', + 'Reorder this column by due date (ASC)' => 'Presortiraj ovu kolonu po datumu završetka (ASC)', + 'Reorder this column by due date (DESC)' => 'Presortiraj ovu kolonu po datumu završetka (DESC)', + 'Reorder this column by id (ASC)' => 'Presortiraj ovu kolonu po ID-u (ASC)', + 'Reorder this column by id (DESC)' => 'Presortiraj ovu kolonu po ID-u (DESC)', + '%s moved the task #%d "%s" to the project "%s"' => '%s je premjestio zadatak #%d "%s" u projekat "%s"', + 'Task #%d "%s" has been moved to the project "%s"' => 'Zadatak #%d "%s" je premješten u projekat "%s"', + 'Move the task to another column when the due date is less than a certain number of days' => 'Premjesti zadatak u drugu kolonu kada je datum završetka manji od određenog broja dana', + 'Automatically update the start date when the task is moved away from a specific column' => 'Automatski ažuriraj početni datum kada je zadatak premješten iz određene kolone', + 'HTTP Client:' => 'HTTP klijent:', + 'Assigned' => 'Dodijeljen', + 'Task limits apply to each swimlane individually' => 'Ograničenja zadataka se primjenjuju na svaku swimlane traku pojedinačno', + 'Column task limits apply to each swimlane individually' => 'Ograničenja zadataka u koloni se primjenjuju na svaku swimlane traku pojedinačno', + 'Column task limits are applied to each swimlane individually' => 'Ograničenja zadataka u koloni se primjenjuju na svaku swimlane traku pojedinačno', + 'Column task limits are applied across swimlanes' => 'Ograničenja zadataka u koloni se primjenjuju na sve swimlane trake', + 'Task limit: ' => 'Ograničenje zadatka: ', + 'Change to global tag' => 'Promijeni u globalnu oznaku', + 'Do you really want to make the tag "%s" global?' => 'Da li zaista želiš učiniti oznaku "%s" globalnom?', + 'Enable global tags for this project' => 'Omogući globalne oznake za ovaj projekat', + 'Group membership(s):' => 'Članstvo u grupi:', + '%s is a member of the following group(s): %s' => '%s je član sljedećih grupa: %s', + '%d/%d group(s) shown' => '%d/%d grupa prikazano', + 'Subtask creation or modification' => 'Kreiranje ili izmjena pod-zadatka', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Dodijeli zadatak određenom korisniku kada je zadatak premješten u određenu swimlane traku', + 'Comment' => 'Komentar', + 'Collapse vertically' => 'Skupi vertikalno', + 'Expand vertically' => 'Proširi vertikalno', + 'MXN - Mexican Peso' => 'MXN - Meksički pezo', + 'Estimated vs actual time per column' => 'Procijenjeno naspram stvarnog vremena po koloni', + 'HUF - Hungarian Forint' => 'HUF - Mađarski forint', + 'XBT - Bitcoin' => 'XBT - Bitcoin', + 'You must select a file to upload as your avatar!' => 'Morate odabrati datoteku za otpremanje kao svoj avatar!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'Datoteka koju ste otpremili nije važeća slika! (Dozvoljeni su samo *.gif, *.jpg, *.jpeg i *.png!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Automatski postavi rok kada se zadatak premjesti iz određene kolone', + 'No other projects found.' => 'Nema pronađenih drugih projekata.', + 'Tasks copied successfully.' => 'Zadaci su uspješno kopirani.', + 'Unable to copy tasks.' => 'Nije moguće kopirati zadatke.', + 'Theme' => 'Tema', + 'Theme:' => 'Tema:', + 'Light theme' => 'Svijetla tema', + 'Dark theme' => 'Tamna tema', + 'Automatic theme - Sync with system' => 'Automatska tema - Sinhronizacija sa sistemom', + 'Application managers or more' => 'Menadžeri aplikacije ili više', + 'Administrators' => 'Administratori', + 'Visibility:' => 'Vidljivost:', + 'Standard users' => 'Standardni korisnici', + 'Visibility is required' => 'Vidljivost je obavezna', + 'The visibility should be an app role' => 'Vidljivost treba biti uloga aplikacije', + 'Reply' => 'Odgovori', + '%s wrote: ' => '%s je napisao: ', + 'Number of visible tasks in this column and swimlane' => 'Broj vidljivih zadataka u ovoj koloni i plivačkoj stazi', + 'Number of tasks in this swimlane' => 'Broj zadataka u ovoj plivačkoj stazi', + 'Unable to find another subtask in progress, you can close this window.' => 'Nije moguće pronaći drugu podzadatak u toku, možete zatvoriti ovaj prozor.', + 'This theme is invalid' => 'Ova tema nije važeća', + 'This role is invalid' => 'Ova uloga nije važeća', + 'This timezone is invalid' => 'Ova vremenska zona nije važeća', + 'This language is invalid' => 'Ovaj jezik nije važeći', + 'This URL is invalid' => 'Ovaj URL nije važeći', + 'Date format invalid' => 'Nevažeći format datuma', + 'Time format invalid' => 'Nevažeći format vremena', + 'Invalid Mail transport' => 'Nevažeći mail transport', + 'Color invalid' => 'Nevažeća boja', + 'This value must be greater or equal to %d' => 'Ova vrijednost mora biti veća ili jednaka %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Dodajte BOM na početak datoteke (potrebno za Microsoft Excel)', + 'Just add these tag(s)' => 'Dodajte samo ove oznake', + 'Remove internal link(s)' => 'Uklonite interne veze', + 'Import tasks from another project' => 'Uvezite zadatke iz drugog projekta', + 'Select the project to copy tasks from' => 'Odaberite projekat iz kojeg želite kopirati zadatke', + 'The total maximum allowed attachments size is %sB.' => 'Ukupna maksimalna dozvoljena veličina priloga je %sB.', + 'Add attachments' => 'Dodajte priloge', + 'Task #%d "%s" is overdue' => 'Zadatak #%d "%s" je s iztečen сроком', + 'Enable notifications by default for all new users' => 'Omogući obavještenja po difoltu za sve nove korisnike', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Dodijeli zadatak njegovom kreatoru za određene kolone ako izvršilac nije ručno postavljen', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Dodijeli zadatak prijavljenom korisniku pri promjeni kolone na odabranu kolonu ako niko nije dodijeljen', +]; diff --git a/app/Locale/ca_ES/translations.php b/app/Locale/ca_ES/translations.php new file mode 100644 index 0000000..dbbdbd2 --- /dev/null +++ b/app/Locale/ca_ES/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => ' ', + 'None' => 'Cap', + 'Edit' => 'Edita', + 'Remove' => 'Elimina', + 'Yes' => 'Si', + 'No' => 'No', + 'cancel' => 'cancel·la', + 'or' => 'o', + 'Yellow' => 'Groc', + 'Blue' => 'Blau', + 'Green' => 'Verd', + 'Purple' => 'Lil·la', + 'Red' => 'Vermell', + 'Orange' => 'Taronja', + 'Grey' => 'Gris', + 'Brown' => 'Marró', + 'Deep Orange' => 'Taronja fosc', + 'Dark Grey' => 'Gris fosc', + 'Pink' => 'Rosa', + 'Teal' => 'Turquessa', + 'Cyan' => 'Cian', + 'Lime' => 'Lima', + 'Light Green' => 'Verd clar', + 'Amber' => 'Ambre', + 'Save' => 'Desa', + 'Login' => 'Inicia la sessió', + 'Official website:' => 'Pàgina web oficial:', + 'Unassigned' => 'Sense assignar', + 'View this task' => 'Veure aquesta tasca', + 'Remove user' => 'Eliminar usuari', + 'Do you really want to remove this user: "%s"?' => 'Vols eliminar aquest usuari: "%s"?', + 'All users' => 'Tots els usuaris', + 'Username' => 'Nom d\'usuari', + 'Password' => 'Contrasenya', + 'Administrator' => 'Administrador', + 'Sign in' => 'Accedeix', + 'Users' => 'Usuaris', + 'Forbidden' => 'Prohibit', + 'Access Forbidden' => 'Accés prohibit', + 'Edit user' => 'Edita usuaris', + 'Logout' => 'Tanca sessió', + 'Bad username or password' => 'Usuari o contrasenya no existeix', + 'Edit project' => 'Edita el projecte', + 'Name' => 'Nom', + 'Projects' => 'Projectes', + 'No project' => 'Cap projecte', + 'Project' => 'Projecte', + 'Status' => 'Estat', + 'Tasks' => 'Tasques', + 'Board' => 'Tauler', + 'Actions' => 'Accions', + 'Inactive' => 'Inactiu', + 'Active' => 'Actiu', + 'Unable to update this board.' => 'No es pot actualitzar aquest tauler.', + 'Disable' => 'Desactivar', + 'Enable' => 'Habilita', + 'New project' => 'Nou projecte', + 'Do you really want to remove this project: "%s"?' => 'Vols eliminar aquest projecte: "%s"?', + 'Remove project' => 'Elimina projecte', + 'Edit the board for "%s"' => 'Edita el tauler de "%s"', + 'Add a new column' => 'Afegeix una nova columna', + 'Title' => 'Títol', + 'Assigned to %s' => 'Assignat a %s', + 'Remove a column' => 'Eliminar una columna', + 'Unable to remove this column.' => 'No es pot eliminar aquesta columna.', + 'Do you really want to remove this column: "%s"?' => 'Vols eliminar aquesta columna: "%s"?', + 'Settings' => 'Configuració', + 'Application settings' => 'Configuració de l\'aplicació', + 'Language' => 'Idioma', + 'Webhook token:' => 'Web hook token:', + 'API token:' => 'Token de l\'API:', + 'Database size:' => 'Mida de la base de dades:', + 'Download the database' => 'Descarregar la base de dades', + 'Optimize the database' => 'Optimitzar la base de dades', + '(VACUUM command)' => '(Comanda VACUUM)', + '(Gzip compressed Sqlite file)' => '(Arxiu comprimit gzip SQLite)', + 'Close a task' => 'Tancar una tasca', + 'Column' => 'Columna', + 'Color' => 'Color', + 'Assignee' => 'Assignat a', + 'Create another task' => 'Crear una altra tasca', + 'New task' => 'Nova tasca', + 'Open a task' => 'Obrir una tasca', + 'Do you really want to open this task: "%s"?' => 'Vols obrir aquesta tasca: "%s"?', + 'Back to the board' => 'Torna al tauler', + 'There is nobody assigned' => 'No hi ha ningú assignat', + 'Column on the board:' => 'Columna al tauler:', + 'Close this task' => 'Tanca aquesta tasca', + 'Open this task' => 'Obre aquesta tasca', + 'There is no description.' => 'No hi ha cap descripció.', + 'Add a new task' => 'Afegeix una nova tasca', + 'The username is required' => 'Es requereix el nom d\'usuari', + 'The maximum length is %d characters' => 'La longitud màxima és de %d caràcters', + 'The minimum length is %d characters' => 'La longitud mínima és %d caràcters', + 'The password is required' => 'La contrasenya és necessària', + 'This value must be an integer' => 'Aquest valor ha de ser un nombre enter', + 'The username must be unique' => 'El nom d\'usuari ha de ser únic', + 'The user id is required' => 'Es requereix que l\'ID d\'usuari', + 'Passwords don\'t match' => 'Les contrasenyes no coincideixen', + 'The confirmation is required' => 'Es requereix la confirmació', + 'The project is required' => 'Es requereix el projecte', + 'The id is required' => 'Es requereix que l\'ID', + 'The project id is required' => 'Es requereix que l\'identificador del projecte', + 'The project name is required' => 'Es requereix el nom del projecte', + 'The title is required' => 'Es requereix el títol', + 'Settings saved successfully.' => 'La configuració es va guardar amb èxit.', + 'Unable to save your settings.' => 'No es pot desar la configuració.', + 'Database optimization done.' => 'Optimització de bases de dades realitza.', + 'Your project has been created successfully.' => 'El seu projecte s\'han creat amb èxit.', + 'Unable to create your project.' => 'No es pot crear el projecte.', + 'Project updated successfully.' => 'Projecte actualitzat correctament.', + 'Unable to update this project.' => 'No es pot actualitzar aquest projecte.', + 'Unable to remove this project.' => 'No es pot eliminar aquest projecte.', + 'Project removed successfully.' => 'Projecte eliminat correctament.', + 'Project activated successfully.' => 'Projecte activat correctament.', + 'Unable to activate this project.' => 'No es pot activar aquest projecte.', + 'Project disabled successfully.' => 'Projecte deshabilitat amb èxit.', + 'Unable to disable this project.' => 'No és possible desactivar aquest projecte.', + 'Unable to open this task.' => 'No es pot obrir aquesta tasca.', + 'Task opened successfully.' => 'Tasca oberta amb èxit.', + 'Unable to close this task.' => 'No es pot tancar aquesta tasca.', + 'Task closed successfully.' => 'Tasca tancada amb èxit.', + 'Unable to update your task.' => 'No es pot actualitzar la seva tasca.', + 'Task updated successfully.' => 'Tasca actualitzada correctament.', + 'Unable to create your task.' => 'No es pot crear la tasca.', + 'Task created successfully.' => 'Tasca creada correctament.', + 'User created successfully.' => 'L\'usuari ha creat correctament.', + 'Unable to create your user.' => 'No es pot crear l\'usuari.', + 'User updated successfully.' => 'Usuari actualitzat correctament.', + 'User removed successfully.' => 'Usuari eliminat correctament.', + 'Unable to remove this user.' => 'No es pot eliminar aquest usuari.', + 'Board updated successfully.' => 'Tauler actualitzat correctament.', + 'Ready' => 'Preparat', + 'Backlog' => 'Pendent', + 'Work in progress' => 'En curs', + 'Done' => 'Fet', + 'Application version:' => 'Versió de l\'aplicació:', + 'Id' => 'Identificació', + 'Public link' => 'Enllaç públic', + 'Timezone' => 'Zona horària', + 'Sorry, I didn\'t find this information in my database!' => 'No s\'ha trobat la informació a la base de dades!', + 'Page not found' => 'Pàgina no trobada', + 'Complexity' => 'Dificultat', + 'Task limit' => 'Límit de tasques', + 'Task count' => 'Recompte de tasques', + 'User' => 'Usuari', + 'Comments' => 'Comentaris', + 'Comment is required' => 'Es requereix comentari', + 'Comment added successfully.' => 'Comentari afegit amb èxit.', + 'Unable to create your comment.' => 'No es pot crear el seu comentari.', + 'Due Date' => 'Data de venciment', + 'Invalid date' => 'Data no vàlida', + 'Automatic actions' => 'Accions automàtiques', + 'Your automatic action has been created successfully.' => 'La seva acció automàtica s\'han creat amb èxit.', + 'Unable to create your automatic action.' => 'No es pot crear la seva acció automàtica.', + 'Remove an action' => 'Eliminar una acció', + 'Unable to remove this action.' => 'No es pot eliminar aquesta acció.', + 'Action removed successfully.' => 'Acció eliminat correctament.', + 'Automatic actions for the project "%s"' => 'Accions automàtiques per al projecte "%s"', + 'Add an action' => 'Afegeix una acció', + 'Event name' => 'Nom de l\'esdeveniment', + 'Action' => 'Acció', + 'Event' => 'Esdeveniment', + 'When the selected event occurs execute the corresponding action.' => 'Quan es produeix l\'esdeveniment seleccionat executar l\'acció corresponent.', + 'Next step' => 'Següent pas', + 'Define action parameters' => 'Definir paràmetres d\'acció', + 'Do you really want to remove this action: "%s"?' => 'Vols eliminar aquesta acció: "%s"?', + 'Remove an automatic action' => 'Eliminar una acció automàtica', + 'Assign the task to a specific user' => 'Assignar la tasca a un usuari específic', + 'Assign the task to the person who does the action' => 'Assignar la tasca a la persona que fa l\'acció', + 'Duplicate the task to another project' => 'Duplica la tasca a un altre projecte', + 'Move a task to another column' => 'Mou una tasca a una altra columna', + 'Task modification' => 'Modificació de tasques', + 'Task creation' => 'Creació de tasques', + 'Closing a task' => 'El tancament d\'una tasca', + 'Assign a color to a specific user' => 'Assignar un color a un usuari específic', + 'Position' => 'Posició', + 'Duplicate to project' => 'Duplica a un altre projecte', + 'Duplicate' => 'Duplica', + 'Link' => 'Enllaç', + 'Comment updated successfully.' => 'Comentari actualitzat correctament.', + 'Unable to update your comment.' => 'No es pot actualitzar el seu comentari.', + 'Remove a comment' => 'Retirar un comentari', + 'Comment removed successfully.' => 'Comentari eliminat correctament.', + 'Unable to remove this comment.' => 'No es pot eliminar aquest comentari.', + 'Do you really want to remove this comment?' => 'Vols eliminar aquest comentari?', + 'Current password for the user "%s"' => 'Contrasenya actual per a l\'usuari "%s"', + 'The current password is required' => 'Es requereix la contrasenya actual', + 'Wrong password' => 'Contrasenya incorrecta', + 'Unknown' => 'Desconegut', + 'Last logins' => 'Últims inicis de sessió', + 'Login date' => 'Data d\'inici de sessió', + 'Authentication method' => 'Mètode d\'autenticació', + 'IP address' => 'Adreça IP', + 'User agent' => 'Agent d\'usuari', + 'Persistent connections' => 'Les connexions persistents', + 'No session.' => 'Cap sessió.', + 'Expiration date' => 'Data de caducitat', + 'Remember Me' => 'Recorda\'m', + 'Creation date' => 'Data de creació', + 'Everybody' => 'Tothom', + 'Open' => 'Obert', + 'Closed' => 'Tancat', + 'Search' => 'Cerca', + 'Nothing found.' => 'No s\'ha trobat res.', + 'Due date' => 'Data de venciment', + 'Description' => 'Descripció', + '%d comments' => '%d comentaris', + '%d comment' => '%d comentari', + 'Email address invalid' => 'Adreça de correu electrònic no vàlida', + 'Your external account is not linked anymore to your profile.' => 'El seu compte extern no està vinculada més al seu perfil.', + 'Unable to unlink your external account.' => 'No és possible desvincular el compte extern.', + 'External authentication failed' => 'L\'autenticació externa va fallar', + 'Your external account is linked to your profile successfully.' => 'El seu compte extern està vinculada al seu perfil correctament.', + 'Email' => 'E-mail', + 'Task removed successfully.' => 'Tasca eliminat correctament.', + 'Unable to remove this task.' => 'No es pot eliminar aquesta tasca.', + 'Remove a task' => 'Treure una tasca', + 'Do you really want to remove this task: "%s"?' => 'Vols eliminar aquesta tasca: "%s"?', + 'Assign automatically a color based on a category' => 'Assignar automàticament un color basat en una categoria', + 'Assign automatically a category based on a color' => 'Assignar automàticament una categoria basada en un color', + 'Task creation or modification' => 'Creació o modificació de tasques', + 'Category' => 'Categoria', + 'Category:' => 'Categoria:', + 'Categories' => 'Categories', + 'Your category has been created successfully.' => 'La seva categoria s\'han creat amb èxit.', + 'This category has been updated successfully.' => 'Aquesta categoria s\'ha actualitzat correctament.', + 'Unable to update this category.' => 'No es pot actualitzar aquesta categoria.', + 'Remove a category' => 'Eliminar una categoria', + 'Category removed successfully.' => 'Categoria eliminat correctament.', + 'Unable to remove this category.' => 'No es pot eliminar aquesta categoria.', + 'Category modification for the project "%s"' => 'Modificació de la categoria pel projecte "%s"', + 'Category Name' => 'Nom de categoria', + 'Add a new category' => 'Afegeix una nova categoria', + 'Do you really want to remove this category: "%s"?' => 'Vols eliminar aquesta categoria: "%s"?', + 'All categories' => 'Totes les categories', + 'No category' => 'sense categoria', + 'The name is required' => 'Es requereix el nom', + 'Remove a file' => 'Suprimir un fitxer', + 'Unable to remove this file.' => 'No es pot eliminar aquest arxiu.', + 'File removed successfully.' => 'Arxiu eliminat amb èxit.', + 'Attach a document' => 'Adjunta un document', + 'Do you really want to remove this file: "%s"?' => 'Vols eliminar aquesta imatge: "%s"?', + 'Attachments' => 'Adjunts', + 'Edit the task' => 'Edita la tasca', + 'Add a comment' => 'Afegeix un comentari', + 'Edit a comment' => 'Edita un comentari', + 'Summary' => 'Resum', + 'Time tracking' => 'Control d\'hores', + 'Estimate:' => 'Estimació:', + 'Spent:' => 'Utilitzat:', + 'Do you really want to remove this sub-task?' => 'Realment voleu eliminar aquesta sub-tasca?', + 'Remaining:' => 'Restant:', + 'hours' => 'hores', + 'estimated' => 'estimat', + 'Sub-Tasks' => 'Subtasques', + 'Add a sub-task' => 'Afegeix una subtasca', + 'Original estimate' => 'Estimació original', + 'Create another sub-task' => 'Crear una altra subtasca', + 'Time spent' => 'El temps dedicat', + 'Edit a sub-task' => 'Editar una subtasca', + 'Remove a sub-task' => 'Suprimir una subtasca', + 'The time must be a numeric value' => 'El temps ha de ser un valor numèric', + 'Todo' => 'Fer', + 'In progress' => 'En progrés', + 'Sub-task removed successfully.' => 'Subtasca eliminat correctament.', + 'Unable to remove this sub-task.' => 'No es pot eliminar aquesta sub-tasques.', + 'Sub-task updated successfully.' => 'Subtasca actualitzat correctament.', + 'Unable to update your sub-task.' => 'No es pot actualitzar el seu sub-tasques.', + 'Unable to create your sub-task.' => 'No es pot crear el sub-tasques.', + 'Maximum size: ' => 'Mida màxima: ', + 'Display another project' => 'Mostra un altre projecte', + 'Created by %s' => 'Creat per %s', + 'Tasks Export' => 'Exportació de tasques', + 'Start Date' => 'Data d\'inici', + 'Execute' => 'Executar', + 'Task Id' => 'ID de tasca', + 'Creator' => 'Creador', + 'Modification date' => 'Data de modificació', + 'Completion date' => 'Data de finalització', + 'Clone' => 'Clon', + 'Project cloned successfully.' => 'Projecte clonat amb èxit.', + 'Unable to clone this project.' => 'No és possible clonar aquest projecte.', + 'Enable email notifications' => 'Activa notificacions per correu electrònic', + 'Task position:' => 'Posició de la tasca:', + 'The task #%d has been opened.' => 'La tasca #%d s\'ha obert.', + 'The task #%d has been closed.' => 'La tasca #%d s\'ha tancat.', + 'Sub-task updated' => 'Subtasca actualitza', + 'Title:' => 'Títol:', + 'Status:' => 'Estat:', + 'Assignee:' => 'Assignat a:', + 'Time tracking:' => 'Control d\'hores:', + 'New sub-task' => 'Nova subtasca', + 'New attachment added "%s"' => 'Nou adjunt afegit "%s"', + 'New comment posted by %s' => 'Nou comentari Publicat per %s', + 'New comment' => 'Nou comentari', + 'Comment updated' => 'Comentari actualitzat', + 'New subtask' => 'Nova subtasca', + 'I only want to receive notifications for these projects:' => 'Vull rebre notificacions només per a aquells projectes:', + 'view the task on Kanboard' => 'Veure la tasca en Kanboard', + 'Public access' => 'Accés públic', + 'Disable public access' => 'Deshabilitar l\'accés del públic', + 'Enable public access' => 'Permetre l\'accés del públic', + 'Public access disabled' => 'L\'accés públic deshabilitat', + 'Move the task to another project' => 'Mou la tasca a un altre projecte', + 'Move to project' => 'Mou a un altre projecte', + 'Do you really want to duplicate this task?' => 'És el que realment desitja duplicar aquesta tasca?', + 'Duplicate a task' => 'Duplica una tasca', + 'External accounts' => 'Els comptes externes', + 'Account type' => 'Tipus de compte', + 'Local' => 'Local', + 'Remote' => 'Remot', + 'Enabled' => 'Habilitat', + 'Disabled' => 'Inhabilitat', + 'Login:' => 'Iniciar Sessió:', + 'Full Name:' => 'Nom complet:', + 'Email:' => 'E-mail:', + 'Notifications:' => 'Notificacions:', + 'Notifications' => 'Notificacions', + 'Account type:' => 'Tipus de compte:', + 'Edit profile' => 'Editar el perfil', + 'Change password' => 'Canvia la contrasenya', + 'Password modification' => 'Modificació de la contrasenya', + 'External authentications' => 'Autenticacions externes', + 'Never connected.' => 'Mai connectada.', + 'No external authentication enabled.' => 'No s\'habilita l\'autenticació externa.', + 'Password modified successfully.' => 'Contrasenya modificat correctament.', + 'Unable to change the password.' => 'No es pot canviar la contrasenya.', + 'Change category' => 'Canvia la categoria', + '%s updated the task %s' => '%s ha actualitzat la tasca %s', + '%s opened the task %s' => '%s ha obert la tasca %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s ha mogut la tasca %s a la posició #%d de la columna "%s"', + '%s moved the task %s to the column "%s"' => '%s ha mogut la tasca %s a la columna "%s"', + '%s created the task %s' => '%s creat la tasca %s', + '%s closed the task %s' => '%s tancada la tasca %s', + '%s created a subtask for the task %s' => '%s creat una subtasca de la tasca %s', + '%s updated a subtask for the task %s' => '%s actualitzada una subtasca de la tasca %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Assignat a %s amb un temps estimat de %s/%sh', + 'Not assigned, estimate of %sh' => 'No assignat, estimat de %sh', + '%s updated a comment on the task %s' => '%s ha actualitzat un comentari a la tasca %s', + '%s commented the task %s' => '%s ha comentat la tasca %s', + '%s\'s activity' => 'L\'activitat de %s', + 'RSS feed' => 'RSS feed', + '%s updated a comment on the task #%d' => '%s s\'actualitzen un comentari a la tasca #%d', + '%s commented on the task #%d' => '%s comentat a la tasca #%d', + '%s updated a subtask for the task #%d' => '%s s\'actualitzen un subtasca de la tasca #%d', + '%s created a subtask for the task #%d' => '%s creat una subtasca de la tasca #%d', + '%s updated the task #%d' => '%s actualitzada la tasca #%d', + '%s created the task #%d' => '%s creat la tasca #%d', + '%s closed the task #%d' => '%s van tancar la tasca #%d', + '%s opened the task #%d' => '%s va obrir la tasca #%d', + 'Activity' => 'Activitat', + 'Default values are "%s"' => 'Els valors per defecte son "%s"', + 'Default columns for new projects (Comma-separated)' => 'Columnes predeterminades per a nous projectes (separats per comes)', + 'Task assignee change' => 'Tasca de canvi del assignat', + '%s changed the assignee of the task #%d to %s' => '%s va canviar el assignat de la tasca #%d de %s', + '%s changed the assignee of the task %s to %s' => '%s va canviar el assignat de la tasca %s %s', + 'New password for the user "%s"' => 'Nova contrasenya per a l\'usuari "%s"', + 'Choose an event' => 'Tria un esdeveniment', + 'Create a task from an external provider' => 'Crear una tasca d\'un proveïdor extern', + 'Change the assignee based on an external username' => 'Canviar el assignat basat en un nom d\'usuari extern', + 'Change the category based on an external label' => 'Canviar la categoria en funció d\'una etiqueta externa', + 'Reference' => 'Referència', + 'Label' => 'Etiqueta', + 'Database' => 'Base de dades', + 'About' => 'Quant a', + 'Database driver:' => 'Controlador de base de dades:', + 'Board settings' => 'Paràmetres del tauler', + 'Webhook settings' => 'Configuració web hook', + 'Reset token' => 'Reinicia el token', + 'API endpoint:' => 'API de punt final:', + 'Refresh interval for personal board' => 'Interval d\'actualització per al tauler privada', + 'Refresh interval for public board' => 'Interval d\'actualització per al tauler pública', + 'Task highlight period' => 'Període culminant de tasques', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Període (en segons) per considerar una tasca es va modificar recentment (0 per desactivar, 2 dies de defecte)', + 'Frequency in second (60 seconds by default)' => 'Freqüència en segons (60 segons per defecte)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Freqüència en segons (0 per desactivar aquesta característica, 10 segons per defecte)', + 'Application URL' => 'URL de l\'aplicació', + 'Token regenerated.' => 'Token regenerat.', + 'Date format' => 'Format de dates', + 'ISO format is always accepted, example: "%s" and "%s"' => 'Sempre s\'accepta el format ISO, exemple "%s" i "%s"', + 'New personal project' => 'Nou projecte privat', + 'This project is personal' => 'Aquest projecte és privat', + 'Add' => 'Afegeix', + 'Start date' => 'Data d\'inici', + 'Time estimated' => 'Temps estimat', + 'There is nothing assigned to you.' => 'No tens res assignat.', + 'My tasks' => 'Les meves tasques', + 'Activity stream' => 'Fluxe d\'activitat', + 'Dashboard' => 'Panell', + 'Confirmation' => 'Confirmació', + 'Webhooks' => 'WebHooks', + 'API' => 'API', + 'Create a comment from an external provider' => 'Crear un comentari d\'un proveïdor extern', + 'Project management' => 'Gestió de projectes', + 'Columns' => 'Columnes', + 'Task' => 'Tasca', + 'Percentage' => 'Percentatge', + 'Number of tasks' => 'Nombre de tasques', + 'Task distribution' => 'Distribució de tasques', + 'Analytics' => 'Analítica', + 'Subtask' => 'Subtasca', + 'User repartition' => 'Repartiment d\'usuari', + 'Clone this project' => 'Clonar aquest projecte', + 'Column removed successfully.' => 'Columna eliminat correctament.', + 'Not enough data to show the graph.' => 'No hi ha dades suficients per mostrar el gràfic.', + 'Previous' => 'Anterior', + 'The id must be an integer' => 'L\'identificador ha de ser un nombre enter', + 'The project id must be an integer' => 'L\'identificador de projecte ha de ser un enter', + 'The status must be an integer' => 'L\'estat ha de ser un enter', + 'The subtask id is required' => 'Es requereix que l\'ID subtasca', + 'The subtask id must be an integer' => 'L\'identificador de subtasca ha de ser un enter', + 'The task id is required' => 'Es requereix que l\'ID de tasca', + 'The task id must be an integer' => 'L\'identificador de tasca ha de ser un enter', + 'The user id must be an integer' => 'L\'identificador d\'usuari ha de ser un nombre enter', + 'This value is required' => 'Aquest valor és necessari', + 'This value must be numeric' => 'Aquest valor ha de ser numèric', + 'Unable to create this task.' => 'No es pot crear aquesta tasca.', + 'Cumulative flow diagram' => 'Diagrama de flux acumulat', + 'Daily project summary' => 'Resum diari projecte', + 'Daily project summary export' => 'Exportació del resum diari del projecte', + 'Exports' => 'Exportacions', + 'This export contains the number of tasks per column grouped per day.' => 'Aquesta exportació conté el nombre de tasques per columna agrupada per dia.', + 'Active swimlanes' => 'Swimlanes actius', + 'Add a new swimlane' => 'Afegeix un nou swimlane', + 'Default swimlane' => 'Swimlane per defecte', + 'Do you really want to remove this swimlane: "%s"?' => 'Segur que vols eliminar la swimlane: "%s"', + 'Inactive swimlanes' => 'Swimlanes inactius', + 'Remove a swimlane' => 'Traieu un carril', + 'Swimlane modification for the project "%s"' => 'Modificació del swimlane pel projecte "%s"', + 'Swimlane removed successfully.' => 'Swimlane eliminat correctament.', + 'Swimlanes' => 'Swimlanes', + 'Swimlane updated successfully.' => 'Swimlane actualitzat correctament.', + 'Unable to remove this swimlane.' => 'No es pot eliminar aquest swimlane.', + 'Unable to update this swimlane.' => 'No es pot actualitzar aquest swimlane.', + 'Your swimlane has been created successfully.' => 'La seva swimlane s\'han creat amb èxit.', + 'Example: "Bug, Feature Request, Improvement"' => 'Exemple: "Bug, Comanda de funcions, Millora"', + 'Default categories for new projects (Comma-separated)' => 'Categories per defecte per a nous projectes (separats per comes)', + 'Integrations' => 'Integracions', + 'Integration with third-party services' => 'Integració amb els serveis de tercers', + 'Subtask Id' => 'ID de subtasca', + 'Subtasks' => 'Subtasques', + 'Subtasks Export' => 'Exportació de subtasques', + 'Task Title' => 'Títol de la tasca', + 'Untitled' => 'Sense títol', + 'Application default' => 'Per defecte de l\'aplicació', + 'Language:' => 'Llengua:', + 'Timezone:' => 'Zona horària:', + 'All columns' => 'Totes les columnes', + 'Next' => 'Pròxim', + '#%d' => '#%d', + 'All swimlanes' => 'Tots swimlanes', + 'All colors' => 'Tots els colors', + 'Moved to column %s' => 'Traslladat a la columna %s', + 'User dashboard' => 'Tauler d\'usuari', + 'Allow only one subtask in progress at the same time for a user' => 'Permetre només una subtasca en curs al mateix temps per a un usuari', + 'Edit column "%s"' => 'Modifica la columna "%s"', + 'Select the new status of the subtask: "%s"' => 'Sel·lecciona el nou estat per la subtasca: "%s"', + 'Subtask timesheet' => 'Part d\'hores de subtasca', + 'There is nothing to show.' => 'Res per mostrar.', + 'Time Tracking' => 'Control d\'hores', + 'You already have one subtask in progress' => 'Ja tens una subtasca en curs', + 'Which parts of the project do you want to duplicate?' => 'Quines parts del projecte vols duplicar?', + 'Disallow login form' => 'No permetre formulari d\'accés', + 'Start' => 'Començar', + 'End' => 'Final', + 'Task age in days' => 'L\'edat de tasques en dies', + 'Days in this column' => 'Dies en aquesta columna', + '%dd' => '%dd', + 'Add a new link' => 'Afegeix un nou enllaç', + 'Do you really want to remove this link: "%s"?' => 'Segur que vols eliminar aquest enllaç: "%s"?', + 'Do you really want to remove this link with task #%d?' => 'Segur que vols eliminar aquest enllaç amb la tasca #%d?', + 'Field required' => 'Camp requerit', + 'Link added successfully.' => 'Enllaç afegit correctament.', + 'Link updated successfully.' => 'Enllaç actualitzat correctament.', + 'Link removed successfully.' => 'Enllaç eliminat amb èxit.', + 'Link labels' => 'Etiquetes d\'enllaç', + 'Link modification' => 'Modificació d\'enllaç', + 'Opposite label' => 'Etiqueta oposada', + 'Remove a link' => 'Suprimir un enllaç', + 'The labels must be different' => 'Les etiquetes han de ser diferents', + 'There is no link.' => 'No hi ha cap relació.', + 'This label must be unique' => 'Aquest avís ha de ser únic', + 'Unable to create your link.' => 'No es pot crear l\'enllaç.', + 'Unable to update your link.' => 'No es pot actualitzar el seu enllaç.', + 'Unable to remove this link.' => 'No es pot eliminar aquest enllaç.', + 'relates to' => 'es refereix a', + 'blocks' => 'bloqueja', + 'is blocked by' => 'està bloquejada per', + 'duplicates' => 'duplica', + 'is duplicated by' => 'es duplica per', + 'is a child of' => 'és fill de', + 'is a parent of' => 'és pare de', + 'targets milestone' => 'objectiu de la fita', + 'is a milestone of' => 'és una fita de', + 'fixes' => 'corregeix', + 'is fixed by' => 'corregit per', + 'This task' => 'Aquesta tasca', + '<1h' => '<1h', + '%dh' => '%dh', + 'Expand tasks' => 'Amplia les tasques', + 'Collapse tasks' => 'Col·lapsa les tasques', + 'Expand/collapse tasks' => 'Amplia les tasques / col·lapsa', + 'Close dialog box' => 'Quadre de diàleg tancar', + 'Submit a form' => 'Envia un formulari', + 'Board view' => 'Mostra el tauler', + 'Keyboard shortcuts' => 'Dreceres de teclat', + 'Open board switcher' => 'Obre commutador de tauler', + 'Application' => 'Aplicació', + 'Compact view' => 'Vista compacta', + 'Horizontal scrolling' => 'Desplaçament horitzontal', + 'Compact/wide view' => 'Vista compacta / ample', + 'Currency' => 'Moneda', + 'Personal project' => 'Projecte privat', + 'AUD - Australian Dollar' => 'MXN - Dòlar australià', + 'CAD - Canadian Dollar' => 'CAD - Dòlar canadenc', + 'CHF - Swiss Francs' => 'CHF - Franc suís', + 'Custom Stylesheet' => 'Estil personalitzat', + 'EUR - Euro' => 'EUR - Euro', + 'GBP - British Pound' => 'GBP - Lliura britànica', + 'INR - Indian Rupee' => 'INR - Rupia índia', + 'JPY - Japanese Yen' => 'JPY - El ien japonès', + 'NZD - New Zealand Dollar' => 'NZD - Dòlar de Nova Zelanda', + 'PEN - Peruvian Sol' => 'PEN - Sol Peruà', + 'RSD - Serbian dinar' => 'RSD - Dinar serbi', + 'CNY - Chinese Yuan' => 'CNY - Yuan xinès', + 'USD - US Dollar' => 'USD - El dòlar dels EUA', + 'VES - Venezuelan Bolívar' => 'VES - Bolívar Veneçolà', + 'Destination column' => 'Columna de destinació', + 'Move the task to another column when assigned to a user' => 'Mou la tasca a una altra columna quan s\'assigna a un usuari', + 'Move the task to another column when assignee is cleared' => 'Mou la tasca a una altra columna quan s\'esborra assignat', + 'Source column' => 'Columna d\'origen', + 'Transitions' => 'Transicions', + 'Executer' => 'Executor', + 'Time spent in the column' => 'El temps emprat a la columna', + 'Task transitions' => 'transicions de tasques', + 'Task transitions export' => 'Tasca transicions d\'exportació', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Aquest informe conté tots els moviments de columna per a cada tasca amb la data, l\'usuari i el temps per a cada transició.', + 'Currency rates' => 'Monedes', + 'Rate' => 'Preu', + 'Change reference currency' => 'Moneda de canvi de referència', + 'Reference currency' => 'Moneda de referència', + 'The currency rate has been added successfully.' => 'El tipus de canvi s\'ha afegit amb èxit.', + 'Unable to add this currency rate.' => 'No es pot afegeix aquest tipus de moneda.', + 'Webhook URL' => 'URL web hook', + '%s removed the assignee of the task %s' => '%s ha eliminat l\'assignat de la tasca %s', + 'Information' => 'Informació', + 'Check two factor authentication code' => 'Comprovar el codi d\'autenticació de dos factors', + 'The two factor authentication code is not valid.' => 'El codi d\'autenticació de dos factors no és vàlid.', + 'The two factor authentication code is valid.' => 'El codi d\'autenticació de dos factors és vàlid.', + 'Code' => 'Codi', + 'Two factor authentication' => 'Autenticació de dos factors', + 'This QR code contains the key URI: ' => 'Aquest codi QR conté la URI de la clau: ', + 'Check my code' => 'Comprovar el meu codi', + 'Secret key: ' => 'Clau secreta: ', + 'Test your device' => 'Prova el teu dispositiu', + 'Assign a color when the task is moved to a specific column' => 'Assignar un color quan la tasca es mou a una columna específica', + '%s via Kanboard' => '%s a través del Kanboard', + 'Burndown chart' => 'Burndown chart', + 'This chart show the task complexity over the time (Work Remaining).' => 'Aquesta gràfica mostra la dificultat de la tasca en el temps (Treball restant).', + 'Screenshot taken %s' => 'Imatge presa %s', + 'Add a screenshot' => 'Afegeix una captura de pantalla', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Prengui una captura de pantalla i premeu CTRL + V o ⌘ + V per enganxar aquí.', + 'Screenshot uploaded successfully.' => 'Captura carregat correctament.', + 'SEK - Swedish Krona' => 'SEK - Corona sueca', + 'Identifier' => 'Identificador', + 'Disable two factor authentication' => 'Desactiva l\'autenticació de dos factors', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Realment vols desactivar l\'autenticació de doble factor per a aquest usuari: "%s"?', + 'Edit link' => 'Edita enllaç', + 'Start to type task title...' => 'Comenceu a títol de la tasca de tipus ...', + 'A task cannot be linked to itself' => 'Una tasca no pot vincular-se a si mateix', + 'The exact same link already exists' => 'La mateixa relació exacta ja existeix', + 'Recurrent task is scheduled to be generated' => 'La tasca recurrent està programada per ser generada', + 'Score' => 'Puntuació', + 'The identifier must be unique' => 'L\'identificador ha de ser únic', + 'This linked task id doesn\'t exists' => 'Aquest ID de tasca enllaçada no existeix', + 'This value must be alphanumeric' => 'Aquest valor ha de ser alfanumèric', + 'Edit recurrence' => 'Edita recurrència', + 'Generate recurrent task' => 'Generar tasca recurrent', + 'Trigger to generate recurrent task' => 'Desencadenar per generar tasca recurrent', + 'Factor to calculate new due date' => 'Factor per calcular la nova data de venciment', + 'Timeframe to calculate new due date' => 'Marc de temps per calcular la nova data de venciment', + 'Base date to calculate new due date' => 'Data de referència per al càlcul de la nova data de venciment', + 'Action date' => 'data d\'acció', + 'Base date to calculate new due date: ' => 'Data base per a calcular nova data de venciment: ', + 'This task has created this child task: ' => 'Aquesta tasca ha creat aquesta tasca filla: ', + 'Day(s)' => 'Dia (Dies)', + 'Existing due date' => 'Data de venciment existent', + 'Factor to calculate new due date: ' => 'Factor per calcular la nova data de venciment: ', + 'Month(s)' => 'Mes (Mesos)', + 'This task has been created by: ' => 'Tasca creada per: ', + 'Recurrent task has been generated:' => 'La tasca recurrent s\'ha generat:', + 'Timeframe to calculate new due date: ' => 'Període de temps per calcular la nova data de venciment: ', + 'Trigger to generate recurrent task: ' => 'Activador per generar tasques recurrents: ', + 'When task is closed' => 'Quan la tasca està tancada', + 'When task is moved from first column' => 'Quan la tasca es mou de la primera columna', + 'When task is moved to last column' => 'Quan la tasca es mou a última columna', + 'Year(s)' => 'Any (s)', + 'Project settings' => 'Configuració del projecte', + 'Automatically update the start date' => 'Actualitzar automàticament la data d\'inici', + 'iCal feed' => 'Origen iCal', + 'Preferences' => 'Configuració', + 'Security' => 'Seguretat', + 'Two factor authentication disabled' => 'L\'autenticació de dos factors deshabilitat', + 'Two factor authentication enabled' => 'Autenticació de dos factors activada', + 'Unable to update this user.' => 'No es pot actualitzar aquest usuari.', + 'There is no user management for personal projects.' => 'No hi ha una gestió d\'usuaris per a projectes privats.', + 'User that will receive the email' => 'L\'usuari que rebrà el correu electrònic', + 'Email subject' => 'assumpte del correu electrònic', + 'Date' => 'Data', + 'Add a comment log when moving the task between columns' => 'Afegeix un registre comentari en moure la tasca entre les columnes', + 'Move the task to another column when the category is changed' => 'Mou la tasca a una altra columna quan es canvia la categoria', + 'Send a task by email to someone' => 'Envia una tasca per correu electrònic a algú', + 'Reopen a task' => 'Torneu a obrir una tasca', + 'Notification' => 'Notificació', + '%s moved the task #%d to the first swimlane' => '%s van moure la tasca #%d per al primer swimlane', + 'Swimlane' => 'Swimlane', + '%s moved the task %s to the first swimlane' => '%s es va traslladar la tasca %s per al primer swimlane', + '%s moved the task %s to the swimlane "%s"' => '%s va moure la tasca %s al carril "%s"', + 'This report contains all subtasks information for the given date range.' => 'Aquest informe conté tota la informació subtasques per al període de temps.', + 'This report contains all tasks information for the given date range.' => 'Aquest informe conté tota la informació de tasques per al període de temps.', + 'Project activities for %s' => 'Les activitats del projecte per a %s', + 'view the board on Kanboard' => 'Veure el tauler en Kanboard', + 'The task has been moved to the first swimlane' => 'La tasca ha estat mogut a la primera swimlane', + 'The task has been moved to another swimlane:' => 'La tasca s\'han traslladat a un altre swimlane:', + 'New title: %s' => 'Nou títol: %s', + 'The task is not assigned anymore' => 'La tasca no s\'ha assignat més', + 'New assignee: %s' => 'Nova assignat:%s', + 'There is no category now' => 'No hi ha una categoria ara', + 'New category: %s' => 'Nova categoria:%s', + 'New color: %s' => 'Nou color: %s', + 'New complexity: %d' => 'Nova dificultat: %d', + 'The due date has been removed' => 'La data de venciment s\'han eliminat', + 'There is no description anymore' => 'No hi ha una descripció més', + 'Recurrence settings has been modified' => 'ajustos de recurrència s\'han modificat', + 'Time spent changed: %sh' => 'Temps invertit canviat: %sh', + 'Time estimated changed: %sh' => 'Temps estimat canviat: %sh', + 'The field "%s" has been updated' => 'El camp "%s" ha estat actualitzat', + 'The description has been modified:' => 'La descripció s\'ha modificat:', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Vols tancar la tasca "%s", així com totes les subtasques?', + 'I want to receive notifications for:' => 'Vull rebre notificacions per:', + 'All tasks' => 'totes les tasques', + 'Only for tasks assigned to me' => 'Només per a les tasques que se m\'ha s\'assignin', + 'Only for tasks created by me' => 'Només per a les tasques creades per mi', + 'Only for tasks created by me and tasks assigned to me' => 'Només per a tasques creades per mi i em assignin', + '%%Y-%%m-%%d' => '%%Y-%%m-%%d', + 'Total for all columns' => 'Total per a totes les columnes', + 'You need at least 2 days of data to show the chart.' => 'Es necessita com a mínim 2 dies de dades per mostrar el gràfic.', + '<15m' => '<15m', + '<30m' => '<30m', + 'Stop timer' => 'Atura el temporitzador', + 'Start timer' => 'Inicia el temporitzador', + 'My activity stream' => 'El meu fluxe d\'activitat', + 'Search tasks' => 'Cerca de tasques', + 'Reset filters' => 'Restableix filtres', + 'My tasks due tomorrow' => 'Les meves tasques per demà', + 'Tasks due today' => 'Tasques que vencen avui', + 'Tasks due tomorrow' => 'Tasques per demà', + 'Tasks due yesterday' => 'Tasques a causa ahir', + 'Closed tasks' => 'Tasques tancades', + 'Open tasks' => 'Tasques obertes', + 'Not assigned' => 'No assignat', + 'View advanced search syntax' => 'Veure sintaxi de cerca avançada', + 'Overview' => 'Visió de conjunt', + 'Board/Calendar/List view' => 'Tauler / Calendari / Vista de llista', + 'Switch to the board view' => 'Canviar a la vista de tauler', + 'Switch to the list view' => 'Canviar a la vista de llista', + 'Go to the search/filter box' => 'Anar al quadre de cerca / filtre', + 'There is no activity yet.' => 'No hi ha cap activitat encara.', + 'No tasks found.' => 'No s\'han trobat tasques.', + 'Keyboard shortcut: "%s"' => 'Drecera de teclat: "%s"', + 'List' => 'Llista', + 'Filter' => 'Filtre', + 'Advanced search' => 'Cerca avançada', + 'Example of query: ' => 'Exemple de consulta: ', + 'Search by project: ' => 'Cerca per projecte: ', + 'Search by column: ' => 'Cerca per columna: ', + 'Search by assignee: ' => 'Cerca per assignat: ', + 'Search by color: ' => 'Cerca per color: ', + 'Search by category: ' => 'Cerca per categoria: ', + 'Search by description: ' => 'Cerca per descripció: ', + 'Search by due date: ' => 'Cerca per data de venciment: ', + 'Average time spent in each column' => 'Temps mitjà de permanència en cada columna', + 'Average time spent' => 'Temps mitjà de permanència', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Aquest gràfic mostra el temps mitjà de permanència en cada columna per a les últimes %d tasques.', + 'Average Lead and Cycle time' => 'El plom i la mitjana del temps de cicle', + 'Average lead time: ' => 'Mitjana de temps de lliurament: ', + 'Average cycle time: ' => 'Mitjana de temps de cicle: ', + 'Cycle Time' => 'Temps de cicle', + 'Lead Time' => 'Temps de lliurament', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Aquesta taula mostra la mitjana de plom i el temps de cicle per a les últimes tasques %d respecte al temps.', + 'Average time into each column' => 'Temps mitjà en cada columna', + 'Lead and cycle time' => 'Temps de lliurament i cicle', + 'Lead time: ' => 'Temps de lliurament: ', + 'Cycle time: ' => 'Temps del cicle: ', + 'Time spent in each column' => 'El temps invertit en cada columna', + 'The lead time is the duration between the task creation and the completion.' => 'El termini d\'execució és la durada entre la creació de la tasca i la finalització.', + 'The cycle time is the duration between the start date and the completion.' => 'El temps de cicle és la durada entre la data d\'inici i la finalització.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Si la tasca no està tancat el moment actual s\'utilitza en lloc de la data de finalització.', + 'Set the start date automatically' => 'S\'ajusta automàticament la data d\'inici', + 'Edit Authentication' => 'Edita autenticació', + 'Remote user' => 'Usuari remot', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Els usuaris remots no emmagatzemen la contrasenya a la base de dades Kanboard, exemples: els comptes LDAP, Google i Github.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Si marca la casella "No permetre formulari d\'accés", s\'ignoraran les credencials introduïdes al formulari d\'inici de sessió.', + 'Default task color' => 'Color per defecte de les tasques', + 'This feature does not work with all browsers.' => 'Aquesta característica no funciona amb tots els navegadors.', + 'There is no destination project available.' => 'No hi ha cap projecte de destinació disponibles.', + 'Trigger automatically subtask time tracking' => 'Desencadenar automàticament control d\'hores de subtasca', + 'Include closed tasks in the cumulative flow diagram' => 'Incloure tasques tancades en el diagrama de flux acumulat', + 'Current swimlane: %s' => 'Swimlane actual: %s', + 'Current column: %s' => 'Columna actual: %s', + 'Current category: %s' => 'Categoria actual: %s', + 'no category' => 'cap categoria', + 'Current assignee: %s' => 'Assignat actual: %s', + 'not assigned' => 'no assignat', + 'Author:' => 'Autor:', + 'contributors' => 'contribuents', + 'License:' => 'Llicència:', + 'License' => 'Llicència', + 'Enter the text below' => 'Introduïu el text a continuació', + 'Start date:' => 'Data d\'inici:', + 'Due date:' => 'Data de venciment:', + 'People who are project managers' => 'Les persones que són caps de projecte', + 'People who are project members' => 'Les persones que són membres del projecte', + 'NOK - Norwegian Krone' => 'NOK - Corona noruega', + 'Show this column' => 'Mostra aquesta columna', + 'Hide this column' => 'Amaga aquesta columna', + 'End date' => 'Data de finalització', + 'Users overview' => 'Visió general dels usuaris', + 'Members' => 'Membres', + 'Shared project' => 'Projecte compartit', + 'Project managers' => 'Els gerents de projecte', + 'Projects list' => 'Llista de projectes', + 'End date:' => 'Data de finalització:', + 'Change task color when using a specific task link' => 'Canviar de color quan la tasca utilitzant un enllaç tasca específica', + 'Task link creation or modification' => 'La creació o modificació de l\'enllaç de tasques', + 'Milestone' => 'Fita', + 'Reset the search/filter box' => 'Restablir el quadre de cerca / filtre', + 'Documentation' => 'Documentació', + 'Author' => 'Autor', + 'Version' => 'Versió', + 'Plugins' => 'Connectors', + 'There is no plugin loaded.' => 'No hi ha cap complement carregat.', + 'My notifications' => 'Les meves notificacions', + 'Custom filters' => 'Filtres personalitzats', + 'Your custom filter has been created successfully.' => 'El seu filtre a mida s\'han creat amb èxit.', + 'Unable to create your custom filter.' => 'No es pot crear el filtre personalitzat.', + 'Custom filter removed successfully.' => 'Filtre a mida eliminat correctament.', + 'Unable to remove this custom filter.' => 'No es pot eliminar aquest filtre personalitzat.', + 'Edit custom filter' => 'Edita filtre a mida', + 'Your custom filter has been updated successfully.' => 'El seu filtre a mida s\'han actualitzat correctament.', + 'Unable to update custom filter.' => 'No es pot actualitzar filtre a mida.', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Nou adjunt a la tasca #%d: %s', + 'New comment on task #%d' => 'Nou comentari a la tasca #%d', + 'Comment updated on task #%d' => 'Comentari actualitzat a la tasca #%d', + 'New subtask on task #%d' => 'Nova subtasca a la tasca #%d', + 'Subtask updated on task #%d' => 'Subtasca s\'actualitza a la tasca #%d', + 'New task #%d: %s' => 'Nova tasca #%d: %s', + 'Task updated #%d' => 'Tasca actualitzada #%d', + 'Task #%d closed' => 'Tasca #%d tancada', + 'Task #%d opened' => 'Tasca #%d obrir', + 'Column changed for task #%d' => 'Columna canviada per la tasca #%d', + 'New position for task #%d' => 'Nova posició per la tasca #%d', + 'Swimlane changed for task #%d' => 'El swimlane va canviat per la tasca #%d', + 'Assignee changed on task #%d' => 'L\'assignat va canviar a la tasca #%d', + '%d overdue tasks' => '%d tasques endarrerides', + 'No notification.' => 'Sense notificacions.', + 'Mark all as read' => 'Marca totes com llegides', + 'Mark as read' => 'Marca com llegida', + 'Total number of tasks in this column across all swimlanes' => 'Nombre total de tasques en aquesta columna a través de tots swimlanes', + 'Collapse swimlane' => 'Col·lapsa swimlane', + 'Expand swimlane' => 'Ampliar swimlane', + 'Add a new filter' => 'Afegeix un nou filtre', + 'Share with all project members' => 'Compartir amb tots els membres del projecte', + 'Shared' => 'Compartit', + 'Owner' => 'Propietari', + 'Unread notifications' => 'Notificacions no llegides', + 'Notification methods:' => 'Els mètodes de notificació:', + 'Unable to read your file' => 'No es pot llegir el fitxer', + '%d task(s) have been imported successfully.' => '%d tasca(s) s\'han importat correctament.', + 'Nothing has been imported!' => 'Res s\'han importat!', + 'Import users from CSV file' => 'Importa usuaris des d\'arxiu CSV', + '%d user(s) have been imported successfully.' => '%d usuari(s) s\'han importat correctament.', + 'Comma' => 'Coma', + 'Semi-colon' => 'Punt i coma', + 'Tab' => 'Llengüeta', + 'Vertical bar' => 'Barra vertical', + 'Double Quote' => 'Cometes dobles', + 'Single Quote' => 'Cometes simples', + '%s attached a file to the task #%d' => '%s ha adjuntat un arxiu a la tasca #%d', + 'There is no column or swimlane activated in your project!' => 'No hi ha cap columna o swimlane activat en el seu projecte!', + 'Append filter (instead of replacement)' => 'Annexar filtre (en lloc de reemplaçament)', + 'Append/Replace' => 'Annexar / Reemplaçar', + 'Append' => 'Annexar', + 'Replace' => 'Reemplaçar', + 'Import' => 'Importació', + 'Change sorting' => 'Canvia la ordenació', + 'Tasks Importation' => 'Importació de tasques', + 'Delimiter' => 'Delimitador', + 'Enclosure' => 'Recinte', + 'CSV File' => 'Fitxer CSV', + 'Instructions' => 'Instruccions', + 'Your file must use the predefined CSV format' => 'El seu arxiu ha d\'utilitzar el format CSV predefinit', + 'Your file must be encoded in UTF-8' => 'El seu arxiu ha d\'estar codificat en UTF-8', + 'The first row must be the header' => 'La primera fila ha de ser la capçalera', + 'Duplicates are not verified for you' => 'Els duplicats no es verifiquen per a vostè', + 'The due date must use the ISO format: YYYY-MM-DD' => 'La data de venciment ha d\'utilitzar el format ISO: AAAA-MM-DD', + 'Download CSV template' => 'Descarregar plantilla CSV', + 'No external integration registered.' => 'No es va registrar la integració externa.', + 'Duplicates are not imported' => 'Els duplicats no s\'importen', + 'Usernames must be lowercase and unique' => 'Els noms d\'usuari han d\'estar en minúscules i únic', + 'Passwords will be encrypted if present' => 'Les contrasenyes es xifraran si està present', + '%s attached a new file to the task %s' => '%s adjunta un arxiu nou a la tasca %s', + 'Link type' => 'Tipus d\'enllaç', + 'Assign automatically a category based on a link' => 'Assignar automàticament una categoria basada en un enllaç', + 'BAM - Konvertible Mark' => 'BAM - Konvertible Mark', + 'Assignee Username' => 'Nom d\'usuari assignat', + 'Assignee Name' => 'Nom assignat', + 'Groups' => 'Grups', + 'Members of %s' => 'Els membres de %s', + 'New group' => 'Nou grup', + 'Group created successfully.' => 'Grup creat correctament.', + 'Unable to create your group.' => 'No es pot crear el grup.', + 'Edit group' => 'Edita grup', + 'Group updated successfully.' => 'Grup actualitzat correctament.', + 'Unable to update your group.' => 'No es pot actualitzar el seu grup.', + 'Add group member to "%s"' => 'Afegir membre del grup a "%s"', + 'Group member added successfully.' => 'Membre del grup afegit correctament.', + 'Unable to add group member.' => 'No es pot afegeix membre del grup.', + 'Remove user from group "%s"' => 'Eliminar usuari del grup "%s"', + 'User removed successfully from this group.' => 'Usuari eliminat amb èxit d\'aquest grup.', + 'Unable to remove this user from the group.' => 'No es pot eliminar aquest usuari del grup.', + 'Remove group' => 'Elimina el grup', + 'Group removed successfully.' => 'Grup eliminat correctament.', + 'Unable to remove this group.' => 'No es pot eliminar aquest grup.', + 'Project Permissions' => 'Permisos de projectes', + 'Manager' => 'Gerent', + 'Project Manager' => 'Gerent de projectes', + 'Project Member' => 'Membres del projecte', + 'Project Viewer' => 'Visor de projectes', + 'Your account is locked for %d minutes' => 'El seu compte està bloquejada per %d minuts', + 'Invalid captcha' => 'codi d\'imatge no vàlida', + 'The name must be unique' => 'El nom ha de ser únic', + 'View all groups' => 'Veure tots els grups', + 'There is no user available.' => 'No hi ha cap usuari disponible.', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Realment vols eliminar l\'usuari "%s" del grup "%s"?', + 'There is no group.' => 'No hi ha cap grup.', + 'Add group member' => 'Afegeix membre del grup', + 'Do you really want to remove this group: "%s"?' => 'Realment vols eliminar aquest grup: "%s"?', + 'There is no user in this group.' => 'No hi ha cap usuari en aquest grup.', + 'Permissions' => 'Permisos', + 'Allowed Users' => 'Els usuaris autoritzats', + 'No specific user has been allowed.' => 'Cap usuari s\'hagi establert.', + 'Role' => 'Rol', + 'Enter user name...' => 'Introdueix el nom d\'usuari ...', + 'Allowed Groups' => 'Grups permesos', + 'No group has been allowed.' => 'Cap grup s\'hagi establert.', + 'Group' => 'Grup', + 'Group Name' => 'Nom del grup', + 'Enter group name...' => 'Introduïu el nom del grup ...', + 'Role:' => 'Rol:', + 'Project members' => 'Els membres del projecte', + '%s mentioned you in the task #%d' => '%s t\'ha esmentat a la tasca #%d', + '%s mentioned you in a comment on the task #%d' => '%s t\'ha mencionat en un comentari a la tasca #%d', + 'You were mentioned in the task #%d' => 'Se t\'ha esmentat a la tasca #%d', + 'You were mentioned in a comment on the task #%d' => 'Se t\'ha mencionat en un comentari sobre la tasca #%d', + 'Estimated hours: ' => 'Hores estimades: ', + 'Actual hours: ' => 'Hores reals: ', + 'Hours Spent' => 'Les hores invertides', + 'Hours Estimated' => 'hores estimat', + 'Estimated Time' => 'Temps estimat', + 'Actual Time' => 'temps real', + 'Estimated vs actual time' => 'Estimat en funció del temps real', + 'RUB - Russian Ruble' => 'RUB - Russia rus', + 'Assign the task to the person who does the action when the column is changed' => 'Assignar la tasca a la persona que fa l\'acció quan es canvia la columna', + 'Close a task in a specific column' => 'Tancar una tasca en una columna específica', + 'Time-based One-time Password Algorithm' => 'Algoritme de contrasenya basat en el temps d\'una sola vegada', + 'Two-Factor Provider: ' => 'Proveïdor de doble factor: ', + 'Disable two-factor authentication' => 'Deshabilita l\'autenticació de dos factors', + 'Enable two-factor authentication' => 'Habilitar l\'autenticació de dos factors', + 'There is no integration registered at the moment.' => 'No hi ha integració registrada en el moment.', + 'Password Reset for Kanboard' => 'Restableix contrasenya per Kanboard', + 'Forgot password?' => 'Has oblidat la contrasenya?', + 'Enable "Forget Password"' => 'Habilita "he oblidat la contrasenya"', + 'Password Reset' => 'Restablir contrasenya', + 'New password' => 'Nova contrasenya', + 'Change Password' => 'Canvia la contrasenya', + 'To reset your password click on this link:' => 'Per restablir la contrasenya, feu clic en aquest enllaç:', + 'Last Password Reset' => 'Darrer restabliment de contrasenya', + 'The password has never been reinitialized.' => 'La contrasenya mai ha estat reiniciada.', + 'Creation' => 'Creació', + 'Expiration' => 'Caducitat', + 'Password reset history' => 'Històric de restabliment de contrasenya', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Totes les tasques de la columna "%s" i del carril "%s" s\'han tancat correctament.', + 'Do you really want to close all tasks of this column?' => 'Vols tancar totes les tasques d\'aquesta columna?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d tasca(es) a la columna "%s" i al carril "%s" es tancarà(n).', + 'Close all tasks in this column and this swimlane' => 'Tanqueu totes les tasques d\'aquesta columna', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'No s\'ha registrat plugin d\'un mètode de notificació del projecte. Encara es pot configurar les notificacions individuals en el seu perfil d\'usuari.', + 'My dashboard' => 'El meu tauler', + 'My profile' => 'El meu perfil', + 'Project owner: ' => 'Propietari del projecte: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'L\'identificador de projecte és opcional i ha de ser alfanumèric, exemple: MYPROJECT.', + 'Project owner' => 'Propietari del projecte', + 'Personal projects do not have users and groups management.' => 'projectes privats no tenen els usuaris i grups de gestió.', + 'There is no project member.' => 'No hi ha cap membre del projecte.', + 'Priority' => 'Prioritat', + 'Task priority' => 'Prioritat de la tasca', + 'General' => 'General', + 'Dates' => 'Dates', + 'Default priority' => 'Prioritat per defecte', + 'Lowest priority' => 'La prioritat més baixa', + 'Highest priority' => 'La prioritat més alta', + 'Close a task when there is no activity' => 'Tancar una tasca quan no hi ha activitat', + 'Duration in days' => 'Durada en dies', + 'Send email when there is no activity on a task' => 'Envia correu electrònic quan no hi ha activitat en una tasca', + 'Unable to fetch link information.' => 'No és possible obtenir informació d\'enllaç.', + 'Daily background job for tasks' => 'Feina en segon pla diaria per a les tasques', + 'Auto' => 'Auto', + 'Related' => 'Relacionat', + 'Attachment' => 'Adjunt', + 'Web Link' => 'Enllaç web', + 'External links' => 'Enllaços externs', + 'Add external link' => 'Afegeix un enllaç extern', + 'Type' => 'Tipus', + 'Dependency' => 'Dependència', + 'Add internal link' => 'Afegeix un enllaç intern', + 'Add a new external link' => 'Afegeix un nou enllaç extern', + 'Edit external link' => 'Edita enllaç extern', + 'External link' => 'URL', + 'Copy and paste your link here...' => 'Copia i enganxa l\'enllaç aquí ...', + 'URL' => 'URL', + 'Internal links' => 'Els enllaços interns', + 'Assign to me' => 'Assignar a mi', + 'Me' => 'jo', + 'Do not duplicate anything' => 'No dupliquis res', + 'Projects management' => 'Gestió de projectes', + 'Users management' => 'Gestió d\'usuaris', + 'Groups management' => 'Gestió de grups', + 'Create from another project' => 'Crea a partir d\'un altre projecte', + 'open' => 'obert', + 'closed' => 'tancat', + 'Priority:' => 'Prioritat:', + 'Reference:' => 'Referència:', + 'Complexity:' => 'Dificultat:', + 'Swimlane:' => 'Swimlane:', + 'Column:' => 'Columna:', + 'Position:' => 'Posició:', + 'Creator:' => 'Creador:', + 'Time estimated:' => 'Temps estimat:', + '%s hours' => '%s hora', + 'Time spent:' => 'El temps passat:', + 'Created:' => 'Creat:', + 'Modified:' => 'Modificat:', + 'Completed:' => 'Completat:', + 'Started:' => 'Començat:', + 'Moved:' => 'Mogut:', + 'Task #%d' => 'Tasca #%d', + 'Time format' => 'Format d\'hora', + 'Start date: ' => 'Data d\'inici: ', + 'End date: ' => 'Data de fi: ', + 'New due date: ' => 'Nova data de venciment: ', + 'Start date changed: ' => 'Data d\'inici canviada: ', + 'Disable personal projects' => 'Desactivar els projectes privats', + 'Do you really want to remove this custom filter: "%s"?' => 'Realment vols eliminar aquest filtre personalitzat: "%s"?', + 'Remove a custom filter' => 'Treure un filtre a mida', + 'User activated successfully.' => 'L\'usuari ha activat correctament.', + 'Unable to enable this user.' => 'No és possible permetre que aquest usuari.', + 'User disabled successfully.' => 'L\'usuari deshabilitat amb èxit.', + 'Unable to disable this user.' => 'No és possible desactivar aquest usuari.', + 'All files have been uploaded successfully.' => 'Tots els arxius s\'hagin carregat correctament.', + 'The maximum allowed file size is %sB.' => 'La mida màxima de fitxer permesa és %sB.', + 'Drag and drop your files here' => 'Arrossegar i deixar anar els arxius aquí', + 'choose files' => 'triar els arxius', + 'View profile' => 'Veure el perfil', + 'Two Factor' => 'dos factors', + 'Disable user' => 'desactivar usuari', + 'Do you really want to disable this user: "%s"?' => 'Realment vols desactivar aquest usuari: "%s"?', + 'Enable user' => 'Permetre a l\'usuari', + 'Do you really want to enable this user: "%s"?' => 'Realment vols habilitar aquest usuari: "%s"?', + 'Download' => 'Descarregar', + 'Uploaded: %s' => 'Pujat: %s', + 'Size: %s' => 'Mida: %s', + 'Uploaded by %s' => 'Pujada per %s', + 'Filename' => 'Nom de l\'arxiu', + 'Size' => 'Mida', + 'Column created successfully.' => 'Columna creat correctament.', + 'Another column with the same name exists in the project' => 'Una altra columna amb el mateix nom existeix en el projecte', + 'Default filters' => 'filtres predeterminats', + 'Your board doesn\'t have any columns!' => 'El teu tauler no té columnes!', + 'Change column position' => 'Canvi de posició de columna', + 'Switch to the project overview' => 'Canviar a la visió general del projecte', + 'User filters' => 'Els filtres d\'usuari', + 'Category filters' => 'filtres de categories', + 'Upload a file' => 'Pujar un arxiu', + 'View file' => 'Veure arxiu', + 'Last activity' => 'Última activitat', + 'Change subtask position' => 'Subtasca posició de canvi', + 'This value must be greater than %d' => 'Aquest valor ha de ser més gran que %d', + 'Another swimlane with the same name exists in the project' => 'Una altra swimlane amb el mateix nom existeix en el projecte', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Exemple: https://example.kanboard.org/ (utilitzat per generar URL absoluta)', + 'Actions duplicated successfully.' => 'Accions dupliquen amb èxit.', + 'Unable to duplicate actions.' => 'No s\'ha pogut duplicar accions.', + 'Add a new action' => 'Afegeix una nova acció', + 'Import from another project' => 'Importa d\'un altre projecte', + 'There is no action at the moment.' => 'No hi ha cap acció en aquest moment.', + 'Import actions from another project' => 'accions d\'importació d\'un altre projecte', + 'There is no available project.' => 'No hi ha un projecte disponible.', + 'Local File' => 'Fitxer local', + 'Configuration' => 'Configuració', + 'PHP version:' => 'Versió de PHP:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'Versió del sistema operatiu:', + 'Database version:' => 'Versió de la base de dades:', + 'Browser:' => 'Navegador:', + 'Task view' => 'Vista de tasques', + 'Edit task' => 'Edició de tasques', + 'Edit description' => 'Modifica la descripció', + 'New internal link' => 'Nou enllaç intern', + 'Display list of keyboard shortcuts' => 'Visualització de la llista de dreceres de teclat', + 'Avatar' => 'Avatar', + 'Upload my avatar image' => 'Puja la imatge meu avatar', + 'Remove my image' => 'Traieu la meva imatge', + 'The OAuth2 state parameter is invalid' => 'El paràmetre d\'estat OAuth2 no és vàlid', + 'User not found.' => 'Usuari no trobat.', + 'Search in activity stream' => 'Buscar en el Tràfic d\'activitat', + 'My activities' => 'Les meves activitats', + 'Activity until yesterday' => 'Activitat fins ahir', + 'Activity until today' => 'Activitat fins avui', + 'Search by creator: ' => 'Cerca per creador: ', + 'Search by creation date: ' => 'Cerca per data de creació: ', + 'Search by task status: ' => 'Cerca per estat de la tasca: ', + 'Search by task title: ' => 'Cerca per títol de la tasca: ', + 'Activity stream search' => 'Cerca al fluxe d\'activitat', + 'Projects where "%s" is manager' => 'Projectes on "%s" és gestor', + 'Projects where "%s" is member' => 'Projectes on "%s" és membre', + 'Open tasks assigned to "%s"' => 'Tasques obertes assignades a "%s"', + 'Closed tasks assigned to "%s"' => 'Tasques tancades assignades a "%s"', + 'Assign automatically a color based on a priority' => 'Assignar automàticament un color basat en una prioritat', + 'Overdue tasks for the project(s) "%s"' => 'Tasques endarrerides per al(s) projecte(s) "%s"', + 'Upload files' => 'Pujar arxius', + 'Installed Plugins' => 'Els connectors instal·lats', + 'Plugin Directory' => 'Directori de complements', + 'Plugin installed successfully.' => 'Plug-in instal·lat correctament.', + 'Plugin updated successfully.' => 'Plugin actualitzat correctament.', + 'Plugin removed successfully.' => 'Plugin eliminat correctament.', + 'Subtask converted to task successfully.' => 'Subtasca converteix en tasca amb èxit.', + 'Unable to convert the subtask.' => 'No es pot convertir la subtasca.', + 'Unable to extract plugin archive.' => 'No es pot extreure arxiu connector.', + 'Plugin not found.' => 'Plugin no trobat.', + 'You don\'t have the permission to remove this plugin.' => 'No tens permís per eliminar aquest connector.', + 'Unable to download plugin archive.' => 'No es pot descarregar arxiu connector.', + 'Unable to write temporary file for plugin.' => 'No es pot escriure el fitxer temporal per al connector.', + 'Unable to open plugin archive.' => 'No es pot obrir arxiu de plugin.', + 'There is no file in the plugin archive.' => 'No hi ha cap arxiu a l\'arxiu connector.', + 'Create tasks in bulk' => 'Crear tasques a granel', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'La instància Kanboard no està configurat per a instal·lar plugins des de la interfície d\'usuari.', + 'There is no plugin available.' => 'No hi ha cap complement disponible.', + 'Install' => 'Instal·la', + 'Update' => 'Actualitza', + 'Up to date' => 'Actualitzat', + 'Not available' => 'No disponible', + 'Remove plugin' => 'Eliminar connector', + 'Do you really want to remove this plugin: "%s"?' => 'Realment vols eliminar aquest connector: "%s"?', + 'Uninstall' => 'Suprimeix', + 'Listing' => 'Llistat', + 'Metadata' => 'Metadades', + 'Manage projects' => 'Gestió de projectes', + 'Convert to task' => 'Convertir la tasca', + 'Convert sub-task to task' => 'Converteix sub-tasca a una altra', + 'Do you really want to convert this sub-task to a task?' => 'Vols convertir aquesta sub-tasca a una tasca?', + 'My task title' => 'Títol de la tasca', + 'Enter one task by line.' => 'Introduïu una tasca per línia.', + 'Number of failed login:' => 'Nombre d\'inici de sessió fallit:', + 'Account locked until:' => 'Compte bloquejada fins que:', + 'Email settings' => 'Configuració de correu electrònic', + 'Email sender address' => 'Adreça de correu electrònic del remitent', + 'Email transport' => 'Transport de correu electrònic', + 'Webhook token' => 'Símbol web hook', + 'Project tags management' => 'Gestió de projectes etiquetes', + 'Tag created successfully.' => 'Tag creat correctament.', + 'Unable to create this tag.' => 'No es pot crear aquesta etiqueta.', + 'Tag updated successfully.' => 'Tag actualitzat correctament.', + 'Unable to update this tag.' => 'No es pot actualitzar aquesta etiqueta.', + 'Tag removed successfully.' => 'Tag eliminat correctament.', + 'Unable to remove this tag.' => 'No es pot eliminar aquesta etiqueta.', + 'Global tags management' => 'Gestió global d\'etiquetes', + 'Tags' => 'Etiquetes', + 'Tags management' => 'Gestió d\'etiquetes', + 'Add new tag' => 'Afegeix nova etiqueta', + 'Edit a tag' => 'Edita una etiqueta', + 'Project tags' => 'Variables del projecte', + 'There is no specific tag for this project at the moment.' => 'No hi ha una etiqueta específica per a aquest projecte en aquest moment.', + 'Tag' => 'Etiqueta', + 'Remove a tag' => 'Elimina una etiqueta', + 'Do you really want to remove this tag: "%s"?' => 'Realment vols eliminar aquesta etiqueta: "%s"?', + 'Global tags' => 'etiquetes globals', + 'There is no global tag at the moment.' => 'No hi ha cap variable global en el moment.', + 'This field cannot be empty' => 'Aquest camp no pot estar buit', + 'Close a task when there is no activity in a specific column' => 'Tancar una tasca quan no hi ha activitat en una columna específica', + '%s removed a subtask for the task #%d' => '%s extret una subtasca per a la tasca #%d', + '%s removed a comment on the task #%d' => '%s remogut un comentari a la tasca #%d', + 'Comment removed on task #%d' => 'Comentari eliminat a la tasca #%d', + 'Subtask removed on task #%d' => 'Subtasca eliminat a la tasca #%d', + 'Hide tasks in this column in the dashboard' => 'Amaga tasques en aquesta columna al tauler d\'instruments', + '%s removed a comment on the task %s' => '%s remogut un comentari a la tasca %s', + '%s removed a subtask for the task %s' => '%s extret una subtasca per a la tasca %s', + 'Comment removed' => 'Es va eliminar el comentari', + 'Subtask removed' => 'subtasca retira', + '%s set a new internal link for the task #%d' => '%s va establir un nou enllaç intern per a la tasca #%d', + '%s removed an internal link for the task #%d' => '%s eliminat un enllaç intern per a la tasca #%d', + 'A new internal link for the task #%d has been defined' => 'Un nou enllaç intern per a la tasca #%d s\'han definit', + 'Internal link removed for the task #%d' => 'Enllaç intern eliminat la tasca #%d', + '%s set a new internal link for the task %s' => '%s va establir un nou enllaç intern per a la tasca %s', + '%s removed an internal link for the task %s' => '%s eliminat un enllaç intern per a la tasca %s', + 'Automatically set the due date on task creation' => 'S\'ajusta automàticament la data de venciment a la creació de tasques', + 'Move the task to another column when closed' => 'Mou la tasca a una altra columna quan es tanca', + 'Move the task to another column when not moved during a given period' => 'Mou la tasca a una altra columna quan no es va moure durant un període determinat', + 'Dashboard for %s' => 'Tauler de %s', + 'Tasks overview for %s' => 'Resum de tasques de %s', + 'Subtasks overview for %s' => 'Visió general de subtasques de %s', + 'Projects overview for %s' => 'Visió general del projecte de %s', + 'Activity stream for %s' => 'Fluxe d\'activitat per a %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Assignar un color quan la tasca es mou a un carril específic', + 'Assign a priority when the task is moved to a specific swimlane' => 'Assignar una prioritat quan la tasca es mou a un carril específic', + 'User unlocked successfully.' => 'Usuari desbloquejat amb èxit.', + 'Unable to unlock the user.' => 'No és possible desbloquejar l\'usuari.', + 'Move a task to another swimlane' => 'Mou una tasca a una altra swimlane', + 'Creator Name' => 'nom creador', + 'Time spent and estimated' => 'El temps dedicat i estima', + 'Move position' => 'Mou la posició', + 'Move task to another position on the board' => 'Mou tasca a una altra posició al tauler', + 'Insert before this task' => 'Insereix abans aquesta tasca', + 'Insert after this task' => 'Inserir, després d\'aquesta tasca', + 'Unlock this user' => 'Desbloquejar aquest usuari', + 'Custom Project Roles' => 'Rols d\'encàrrec del projecte', + 'Add a new custom role' => 'Afegeix un nou rol', + 'Restrictions for the role "%s"' => 'Restriccions per al rol "%s"', + 'Add a new project restriction' => 'Afegeix una nova restricció projecte', + 'Add a new drag and drop restriction' => 'Afegeix un nou arrossegar i deixar anar restricció', + 'Add a new column restriction' => 'Afegeix una nova restricció de columna', + 'Edit this role' => 'Edita aquest rol', + 'Remove this role' => 'Elimina aquest rol', + 'There is no restriction for this role.' => 'No hi ha cap restricció per a aquest rol.', + 'Only moving task between those columns is permitted' => 'Només es mou tasques entre aquestes columnes es permet', + 'Close a task in a specific column when not moved during a given period' => 'Tancar una tasca en una columna específica quan no es va moure durant un període determinat', + 'Edit columns' => 'Edita columnes', + 'The column restriction has been created successfully.' => 'La restricció de la columna s\'ha creat correctament.', + 'Unable to create this column restriction.' => 'No es pot crear aquesta restricció columna.', + 'Column restriction removed successfully.' => 'restricció Columna eliminat correctament.', + 'Unable to remove this restriction.' => 'No es pot eliminar aquesta restricció.', + 'Your custom project role has been created successfully.' => 'El seu rol projecte personalitzat s\'ha creat correctament.', + 'Unable to create custom project role.' => 'No es pot crear el rol de projecte personalitzat.', + 'Your custom project role has been updated successfully.' => 'El seu rol projecte personalitzat s\'ha actualitzat correctament.', + 'Unable to update custom project role.' => 'No es pot actualitzar rol projecte personalitzat.', + 'Custom project role removed successfully.' => 'rol projecte personalitzat eliminat correctament.', + 'Unable to remove this project role.' => 'No es pot treure aquest rol projecte.', + 'The project restriction has been created successfully.' => 'La restricció projecte ha estat creat amb èxit.', + 'Unable to create this project restriction.' => 'No es pot crear aquesta restricció projecte.', + 'Project restriction removed successfully.' => 'Projecte restricció eliminat correctament.', + 'You cannot create tasks in this column.' => 'No es poden crear tasques en aquesta columna.', + 'Task creation is permitted for this column' => 'Es permet la creació de tasques per a aquesta columna', + 'Closing or opening a task is permitted for this column' => 'Tancant o obrint una tasca està permesa per a aquesta columna', + 'Task creation is blocked for this column' => 'creació de la tasca està bloquejat per a aquesta columna', + 'Closing or opening a task is blocked for this column' => 'Tancant o obrint una tasca està bloquejat per a aquesta columna', + 'Task creation is not permitted' => 'No es permet la creació de tasques', + 'Closing or opening a task is not permitted' => 'No es permet tancar o obrir una tasca', + 'New drag and drop restriction for the role "%s"' => 'Nova restricció d\'arrossegar i deixar anar per al rol "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Les persones que pertanyen a aquest rol seran capaços de moure les tasques només entre la font i la columna de destinació.', + 'Remove a column restriction' => 'Eliminar una restricció columna', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Realment vols eliminar aquesta restricció de columna: "%s" a "%s"?', + 'New column restriction for the role "%s"' => 'Nova restricció de columna per al rol "%s"', + 'Rule' => 'regla', + 'Do you really want to remove this column restriction?' => 'Vols eliminar aquest criteri de la columna?', + 'Custom roles' => 'Rols personalitzats', + 'New custom project role' => 'Nou rol projecte personalitzat', + 'Edit custom project role' => 'Edita rol projecte personalitzat', + 'Remove a custom role' => 'Eliminar una funció personalitzada', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Realment vols eliminar aquest rol personalitzat: "%s"? Totes les persones assignades a aquest rol es convertiran en membres del projecte.', + 'There is no custom role for this project.' => 'No hi ha una funció personalitzada per a aquest projecte.', + 'New project restriction for the role "%s"' => 'Nova restricció de projecte per al rol "%s"', + 'Restriction' => 'Restricció', + 'Remove a project restriction' => 'Eliminar una restricció projecte', + 'Do you really want to remove this project restriction: "%s"?' => 'Realment vols eliminar aquesta restricció de projecte: "%s"?', + 'Duplicate to multiple projects' => 'Duplica a múltiples projectes', + 'This field is required' => 'Aquest camp és obligatori', + 'Moving a task is not permitted' => 'No està permès moure una tasca', + 'This value must be in the range %d to %d' => 'Aquest valor ha d\'estar en el rang %d a %d', + 'You are not allowed to move this task.' => 'No se li permet moure a aquesta tasca.', + 'API User Access' => 'L\'accés d\'usuari d\'API', + 'Preview' => 'Vista prèvia', + 'Write' => 'escriure', + 'Write your text in Markdown' => 'Escriure el text en Markdown', + 'No personal API access token registered.' => 'Sense accés a l\'API personals símbol registrat.', + 'Your personal API access token is "%s"' => 'La teva clau d\'accés API personal és "%s"', + 'Remove your token' => 'Traieu el testimoni', + 'Generate a new token' => 'Generar un nou token', + 'Showing %d-%d of %d' => 'Mostrant %d-%d de %d', + 'Outgoing Emails' => 'Els correus electrònics sortints', + 'Add or change currency rate' => 'Afegeix o canviar el valor de moneda', + 'Reference currency: %s' => 'moneda de referència: %s', + 'Add custom filters' => 'Afegeix filtres personalitzats', + 'Export' => 'exportació', + 'Add link label' => 'Afegeix etiqueta d\'enllaç', + 'Incompatible Plugins' => 'Connectors incompatibles', + 'Compatibility' => 'Compatibilitat', + 'Permissions and ownership' => 'Permisos i propietat', + 'Priorities' => 'Prioritats', + 'Close this window' => 'Tanca aquesta finestra', + 'Unable to upload this file.' => 'No es pot carregar aquest arxiu.', + 'Import tasks' => 'Importació de tasques', + 'Choose a project' => 'Tria un projecte', + 'Profile' => 'Perfil', + 'Application role' => 'Rol d\'aplicació', + '%d invitations were sent.' => 'S\'han enviat %d invitacions.', + '%d invitation was sent.' => 'S\'han enviat %d invitacions.', + 'Unable to create this user.' => 'No es pot crear aquest usuari.', + 'Kanboard Invitation' => 'Invitació Kanboard', + 'Visible on dashboard' => 'Visible al tauler', + 'Created at:' => 'Creat el:', + 'Updated at:' => 'Actualitzat a:', + 'There is no custom filter.' => 'No hi ha cap filtre personal.', + 'New User' => 'Nou usuari', + 'Authentication' => 'Autenticació', + 'If checked, this user will use a third-party system for authentication.' => 'Si s\'activa, aquest usuari utilitzarà un sistema de tercers per a l\'autenticació.', + 'The password is necessary only for local users.' => 'La contrasenya és necessària només per als usuaris locals.', + 'You have been invited to register on Kanboard.' => 'T\'han convidat a inscriure\'t al Kanboard.', + 'Click here to join your team' => 'Fes clic aquí per unir-te a l\'equip', + 'Invite people' => 'Convidar a més gent', + 'Emails' => 'Correus electrònics', + 'Enter one email address by line.' => 'Introdueix una adreça de correu electrònic per línia.', + 'Add these people to this project' => 'Afegeix a aquestes persones a aquest projecte', + 'Add this person to this project' => 'Afegeix aquesta persona a aquest projecte', + 'Sign-up' => 'Registra\'t', + 'Credentials' => 'Credencials', + 'New user' => 'Nou usuari', + 'This username is already taken' => 'Aquest nom d\'usuari ja està en ús', + 'Your profile must have a valid email address.' => 'El teu perfil ha de tenir una adreça de correu electrònic vàlida.', + 'TRL - Turkish Lira' => 'TRL - Lira turca', + 'The project email is optional and could be used by several plugins.' => 'El correu electrònic del projecte és opcional i pot ser utilitzat per diversos connectors.', + 'The project email must be unique across all projects' => 'El correu electrònic del projecte ha de ser únic en tots els projectes', + 'The email configuration has been disabled by the administrator.' => 'La configuració de correu electrònic ha estat desactivat per l\'administrador.', + 'Close this project' => 'Tanca aquest projecte', + 'Open this project' => 'Obre aquest projecte', + 'Close a project' => 'Tanca un projecte', + 'Do you really want to close this project: "%s"?' => 'Realment vols tancar aquest projecte: "%s"?', + 'Reopen a project' => 'Torna a obrir un projecte', + 'Do you really want to reopen this project: "%s"?' => 'Realment vols reobrir aquest projecte: "%s"?', + 'This project is open' => 'Aquest projecte està obert', + 'This project is closed' => 'Aquest projecte està tancat', + 'Unable to upload files, check the permissions of your data folder.' => 'No es pot pujar arxius, comprovar els permisos de la carpeta de dades.', + 'Another category with the same name exists in this project' => 'Una altra categoria amb el mateix nom existeix en aquest projecte', + 'Comment sent by email successfully.' => 'Comentar enviat per correu electrònic amb èxit.', + 'Sent by email to "%s" (%s)' => 'Enviat per correu electrònic a "%s" (%s)', + 'Unable to read uploaded file.' => 'No es pot llegir el fitxer pujat.', + 'Database uploaded successfully.' => 'Base de dades carregat correctament.', + 'Task sent by email successfully.' => 'Tasca enviat per correu electrònic amb èxit.', + 'There is no category in this project.' => 'No hi ha una categoria en aquest projecte.', + 'Send by email' => 'Envia per correu electrònic', + 'Create and send a comment by email' => 'Per crear i enviar un comentari per correu electrònic', + 'Subject' => 'Tema', + 'Upload the database' => 'Puja la base de dades', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Es podia pujar la base de dades SQLite prèviament descarregat (format gzip).', + 'Database file' => 'arxiu de base', + 'Upload' => 'Pujar', + 'Your project must have at least one active swimlane.' => 'El seu projecte ha de tenir com a mínim un swimlane actiu.', + 'Project: %s' => 'Projecte: %s', + 'Automatic action not found: "%s"' => 'Acció automàtica no trobada: "%s"', + '%d projects' => '%d projectes', + '%d project' => '%d projecte', + 'There is no project.' => 'No hi ha cap projecte.', + 'Sort' => 'Ordena', + 'Project ID' => 'identificació del projecte', + 'Project name' => 'Nom del projecte', + 'Public' => 'Públic', + 'Personal' => 'Privat', + '%d tasks' => '%d tasques', + '%d task' => '%d tasca', + 'Task ID' => 'ID de tasca', + 'Assign automatically a color when due date is expired' => 'Assignar automàticament un color quan ha caducat la data de venciment', + 'Total score in this column across all swimlanes' => 'Puntuació total d\'aquesta columna a través de tots swimlanes', + 'HRK - Kuna' => 'HRK - Kuna', + 'ARS - Argentine Peso' => 'ARS - Pes Argentí', + 'COP - Colombian Peso' => 'COP - Pes colombià', + '%d groups' => '%d grups', + '%d group' => '%d grup', + 'Group ID' => 'ID de grup', + 'External ID' => 'Identificació externa', + '%d users' => '%d usuaris', + '%d user' => '%d usuari', + 'Hide subtasks' => 'Amaga subtasques', + 'Show subtasks' => 'Mostra subtasques', + 'Authentication Parameters' => 'Paràmetres d\'autenticació', + 'API Access' => 'Accés API', + 'No users found.' => 'No es van trobar usuaris.', + 'User ID' => 'ID d\'usuari', + 'Notifications are activated' => 'Notificacions habilitades', + 'Notifications are disabled' => 'Notificacions inhabilitades', + 'User disabled' => 'Usuari deshabilitat', + '%d notifications' => '%d notificacions', + '%d notification' => '%d notificació', + 'There is no external integration installed.' => 'No hi ha integració externa instal·lada.', + 'You are not allowed to update tasks assigned to someone else.' => 'No se li permet posar al dia les tasques assignades a una altra persona.', + 'You are not allowed to change the assignee.' => 'No se li permet canviar el assignat.', + 'Task suppression is not permitted' => 'No es permet la supressió de tasques', + 'Changing assignee is not permitted' => 'No es permet el canvi de assignatari', + 'Update only assigned tasks is permitted' => 'Actualitzar les tasques assignades només es permet', + 'Only for tasks assigned to the current user' => 'Només per a les tasques assignades a l\'usuari actual', + 'My projects' => 'Els meus projectes', + 'You are not a member of any project.' => 'No és membre de cap projecte.', + 'My subtasks' => 'Les meves subtasques', + '%d subtasks' => '%d subtasques', + '%d subtask' => '%d subtasca', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Only moving task between those columns is permitted for tasks assigned to the current user', + '[DUPLICATE]' => '[DUPLICATE]', + 'DKK - Danish Krona' => 'DKK - Corona Danesa', + 'Remove user from group' => 'Eliminar usuari del grup', + 'Assign the task to its creator' => 'Assignar la tasca al seu creador', + 'This task was sent by email to "%s" with subject "%s".' => 'Aquesta tasca s\'ha enviat per correu electrònic a "%s" amb l\'assumpte "%s".', + 'Predefined Email Subjects' => 'Assumptes de correu electrònic predefinits', + 'Write one subject by line.' => 'Escriu un assumpte per línia.', + 'Create another link' => 'Crear un altre enllaç', + 'BRL - Brazilian Real' => 'BRL - Real Brasiler', + 'Add a new Kanboard task' => 'Afegir una nova tasca de Kanboard', + 'Subtask not started' => 'Subtasca no iniciada', + 'Subtask currently in progress' => 'Subtasca actualment en curs', + 'Subtask completed' => 'Subtasca completada', + 'Subtask added successfully.' => 'Subtasca afegida correctament.', + '%d subtasks added successfully.' => '%d subtasques afegides correctament.', + 'Enter one subtask by line.' => 'Introdueix una subtasca per línia.', + 'Predefined Contents' => 'Continguts predefinits', + 'Predefined contents' => 'Continguts predefinits', + 'Predefined Task Description' => 'Descripció de la tasca predefinida', + 'Do you really want to remove this template? "%s"' => 'Realment vols eliminar aquesta plantilla? "%s"', + 'Add predefined task description' => 'Afegir descripció de tasca predefinida', + 'Predefined Task Descriptions' => 'Descripcions de tasques predefinides', + 'Template created successfully.' => 'Plantilla creada correctament.', + 'Unable to create this template.' => 'No s\'ha pogut crear aquesta plantilla.', + 'Template updated successfully.' => 'Plantilla actualitzada correctament.', + 'Unable to update this template.' => 'No s\'ha pogut actualitzar aquesta plantilla.', + 'Template removed successfully.' => 'Plantilla eliminada correctament.', + 'Unable to remove this template.' => 'No s\'ha pogut eliminar aquesta plantilla.', + 'Template for the task description' => 'Plantilla per a la descripció de la tasca', + 'The start date is greater than the end date' => 'La data d\'inici és posterior a la data de finalització', + 'Tags must be separated by a comma' => 'Les etiquetes s\'han de separar per una coma', + 'Only the task title is required' => 'Només el títol de la tasca és obligatori', + 'Creator Username' => 'Nom d\'usuari del creador', + 'Color Name' => 'Nom del color', + 'Column Name' => 'Nom de la columna', + 'Swimlane Name' => 'Nom del carril', + 'Time Estimated' => 'Temps estimat', + 'Time Spent' => 'Temps dedicat', + 'External Link' => 'Enllaç extern', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Aquesta funció habilita el canal iCal, el canal RSS i la vista pública del tauler.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Atura el temporitzador de totes les subtasques quan es mou una tasca a una altra columna', + 'Subtask Title' => 'Títol de la subtasca', + 'Add a subtask and activate the timer when moving a task to another column' => 'Afegir una subtasca i activar el temporitzador quan es mou una tasca a una altra columna', + 'days' => 'dies', + 'minutes' => 'minuts', + 'seconds' => 'segons', + 'Assign automatically a color when preset start date is reached' => 'Assigna automàticament un color quan s\'arriba a la data d\'inici preestablerta', + 'Move the task to another column once a predefined start date is reached' => 'Mou la tasca a una altra columna un cop s\'arriba a una data d\'inici predefinida', + 'This task is now linked to the task %s with the relation "%s"' => 'Aquesta tasca ara està enllaçada a la tasca %s amb la relació "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'S\'ha eliminat l\'enllaç amb la relació "%s" a la tasca %s', + 'Custom Filter:' => 'Filtre personalitzat:', + 'Unable to find this group.' => 'No s\'ha pogut trobar aquest grup.', + '%s moved the task #%d to the column "%s"' => '%s va moure la tasca #%d a la columna "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => '%s va moure la tasca #%d a la posició %d de la columna "%s"', + '%s moved the task #%d to the swimlane "%s"' => '%s va moure la tasca #%d al carril "%s"', + '%sh spent' => '%sh dedicades', + '%sh estimated' => '%sh estimades', + 'Select All' => 'Seleccionar tot', + 'Unselect All' => 'Deseleccionar tot', + 'Apply action' => 'Aplicar acció', + 'Move selected tasks to another column or swimlane' => 'Mou les tasques seleccionades a una altra columna o carril', + 'Edit tasks in bulk' => 'Edita tasques en massa', + 'Choose the properties that you would like to change for the selected tasks.' => 'Trieu les propietats que voleu canviar per a les tasques seleccionades.', + 'Configure this project' => 'Configura aquest projecte', + 'Start now' => 'Comença ara', + '%s removed a file from the task #%d' => '%s ha eliminat un fitxer de la tasca #%d', + 'Attachment removed from task #%d: %s' => 'Adjunt eliminat de la tasca #%d: %s', + 'No color' => 'Sense color', + 'Attachment removed "%s"' => 'Adjunt eliminat "%s"', + '%s removed a file from the task %s' => '%s ha eliminat un fitxer de la tasca %s', + 'Move the task to another swimlane when assigned to a user' => 'Mou la tasca a un altre carril quan se l\'assigna a un usuari', + 'Destination swimlane' => 'Carril de destí', + 'Assign a category when the task is moved to a specific swimlane' => 'Assigna una categoria quan la tasca es mou a un carril específic', + 'Move the task to another swimlane when the category is changed' => 'Mou la tasca a un altre carril quan es canvia la categoria', + 'Reorder this column by priority (ASC)' => 'Reordena aquesta columna per prioritat (ASC)', + 'Reorder this column by priority (DESC)' => 'Reordena aquesta columna per prioritat (DESC)', + 'Reorder this column by assignee and priority (ASC)' => 'Reordena aquesta columna per assignat i prioritat (ASC)', + 'Reorder this column by assignee and priority (DESC)' => 'Reordena aquesta columna per assignat i prioritat (DESC)', + 'Reorder this column by assignee (A-Z)' => 'Reordena aquesta columna per assignat (A-Z)', + 'Reorder this column by assignee (Z-A)' => 'Reordena aquesta columna per assignat (Z-A)', + 'Reorder this column by due date (ASC)' => 'Reordena aquesta columna per data de venciment (ASC)', + 'Reorder this column by due date (DESC)' => 'Reordena aquesta columna per data de venciment (DESC)', + 'Reorder this column by id (ASC)' => 'Reordena aquesta columna per id (ASC)', + 'Reorder this column by id (DESC)' => 'Reordena aquesta columna per id (DESC)', + '%s moved the task #%d "%s" to the project "%s"' => '%s va moure la tasca #%d "%s" al projecte "%s"', + 'Task #%d "%s" has been moved to the project "%s"' => 'La tasca #%d "%s" s\'ha mogut al projecte "%s"', + 'Move the task to another column when the due date is less than a certain number of days' => 'Mou la tasca a una altra columna quan la data de venciment sigui inferior a un cert nombre de dies', + 'Automatically update the start date when the task is moved away from a specific column' => 'Actualitza automàticament la data d\'inici quan la tasca es mou fora d\'una columna específica', + 'HTTP Client:' => 'Client HTTP:', + 'Assigned' => 'Assignat', + 'Task limits apply to each swimlane individually' => 'Els límits de tasques s\'apliquen a cada carril individualment', + 'Column task limits apply to each swimlane individually' => 'Els límits de tasques de columna s\'apliquen a cada carril individualment', + 'Column task limits are applied to each swimlane individually' => 'Els límits de tasques de columna s\'apliquen a cada carril individualment', + 'Column task limits are applied across swimlanes' => 'Els límits de tasques de columna s\'apliquen a tots els carrils', + 'Task limit: ' => 'Límit de tasques:', + 'Change to global tag' => 'Canvia a etiqueta global', + 'Do you really want to make the tag "%s" global?' => 'Realment vols que l\'etiqueta "%s" sigui global?', + 'Enable global tags for this project' => 'Habilita etiquetes globals per a aquest projecte', + 'Group membership(s):' => 'Membre(s) del grup:', + '%s is a member of the following group(s): %s' => '%s és membre del(s) següent(s) grup(s): %s', + '%d/%d group(s) shown' => '%d/%d grup(s) mostrat(s)', + 'Subtask creation or modification' => 'Creació o modificació de subtasques', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Assigna la tasca a un usuari concret quan es mou a un carril específic', + 'Comment' => 'Comentari', + 'Collapse vertically' => 'Replega verticalment', + 'Expand vertically' => 'Expandeix verticalment', + 'MXN - Mexican Peso' => 'MXN - Peso Mexicà', + 'Estimated vs actual time per column' => 'Temps estimat vs temps real per columna', + 'HUF - Hungarian Forint' => 'HUF - Fòrint Hongarès', + 'XBT - Bitcoin' => 'XBT - Bitcoin', + 'You must select a file to upload as your avatar!' => 'Has de seleccionar un fitxer per pujar com a avatar!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'El fitxer que has pujat no és una imatge vàlida! (Només es permeten *.gif, *.jpg, *.jpeg i *.png!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Estableix automàticament la data de venciment quan la tasca es mou fora d\'una columna específica', + 'No other projects found.' => 'No s\'han trobat altres projectes.', + 'Tasks copied successfully.' => 'Tasques copiades correctament.', + 'Unable to copy tasks.' => 'No s\'han pogut copiar les tasques.', + 'Theme' => 'Tema', + 'Theme:' => 'Tema:', + 'Light theme' => 'Tema clar', + 'Dark theme' => 'Tema fosc', + 'Automatic theme - Sync with system' => 'Tema automàtic - Sincronitza amb el sistema', + 'Application managers or more' => 'Gestors d\'aplicació o més', + 'Administrators' => 'Administradors', + 'Visibility:' => 'Visibilitat:', + 'Standard users' => 'Usuaris estàndard', + 'Visibility is required' => 'La visibilitat és obligatòria', + 'The visibility should be an app role' => 'La visibilitat hauria de ser una funció de l\'aplicació', + 'Reply' => 'Respondre', + '%s wrote: ' => '%s va escriure: ', + 'Number of visible tasks in this column and swimlane' => 'Nombre de tasques visibles en aquesta columna i carril', + 'Number of tasks in this swimlane' => 'Nombre de tasques en aquest carril', + 'Unable to find another subtask in progress, you can close this window.' => 'No es pot trobar una altra subtasca en curs, podeu tancar aquesta finestra.', + 'This theme is invalid' => 'Aquest tema no és vàlid', + 'This role is invalid' => 'Aquest rol no és vàlid', + 'This timezone is invalid' => 'Aquesta zona horària no és vàlida', + 'This language is invalid' => 'Aquesta llengua no és vàlida', + 'This URL is invalid' => 'Aquesta URL no és vàlida', + 'Date format invalid' => 'Format de data no vàlid', + 'Time format invalid' => 'Format d\'hora no vàlid', + 'Invalid Mail transport' => 'Transport de correu no vàlid', + 'Color invalid' => 'Color no vàlid', + 'This value must be greater or equal to %d' => 'Aquest valor ha de ser major o igual que %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Afegeix un BOM al principi del fitxer (requerit per a Microsoft Excel)', + 'Just add these tag(s)' => 'Només afegeix aquestes etiqueta(es)', + 'Remove internal link(s)' => 'Eliminar enllaç(os) intern(s)', + 'Import tasks from another project' => 'Importa tasques d\'un altre projecte', + 'Select the project to copy tasks from' => 'Selecciona el projecte del qual copiar les tasques', + 'The total maximum allowed attachments size is %sB.' => 'La mida màxima total permesa per als adjunts és %sB.', + 'Add attachments' => 'Afegir adjunts', + 'Task #%d "%s" is overdue' => 'Tasca #%d "%s" ha caducat', + 'Enable notifications by default for all new users' => 'Habilitar notificacions per defecte per a tots els nous usuaris', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Assigna la tasca al seu creador per a columnes específiques si no s\'ha definit cap responsable manualment', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Assigna una tasca a l\'usuari connectat en canviar de columna a la columna especificada si no hi ha cap usuari assignat', +]; diff --git a/app/Locale/cs_CZ/translations.php b/app/Locale/cs_CZ/translations.php new file mode 100644 index 0000000..bef3c46 --- /dev/null +++ b/app/Locale/cs_CZ/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => '.', + 'None' => 'Žádné', + 'Edit' => 'Editovat', + 'Remove' => 'Odstranit', + 'Yes' => 'Ano', + 'No' => 'Ne', + 'cancel' => 'Zrušit', + 'or' => 'nebo', + 'Yellow' => 'Žlutá', + 'Blue' => 'Modrá', + 'Green' => 'Zelená', + 'Purple' => 'Fialová', + 'Red' => 'Červená', + 'Orange' => 'Oranžová', + 'Grey' => 'Šedá', + 'Brown' => 'Hnědá', + 'Deep Orange' => 'Tmavě oranžová', + 'Dark Grey' => 'Tmavě šedá', + 'Pink' => 'Růžová', + 'Teal' => 'Modrozelená', + 'Cyan' => 'Tyrkysová', + 'Lime' => 'Limetková', + 'Light Green' => 'Světle zelená', + 'Amber' => 'Jantarová', + 'Save' => 'Uložit', + 'Login' => 'Přihlásit se', + 'Official website:' => 'Oficiální stránky:', + 'Unassigned' => 'Nepřiřazeno', + 'View this task' => 'Zobrazit úkol', + 'Remove user' => 'Odebrat uživatele', + 'Do you really want to remove this user: "%s"?' => 'Opravdu chcete odebrat uživatele: "%s"?', + 'All users' => 'Všichni uživatelé', + 'Username' => 'Uživatelské jméno', + 'Password' => 'Heslo', + 'Administrator' => 'Administrátor', + 'Sign in' => 'Přihlásit', + 'Users' => 'Uživatelé', + 'Forbidden' => 'Zakázáno', + 'Access Forbidden' => 'Přístup zakázán', + 'Edit user' => 'Upravit uživatele', + 'Logout' => 'Odhlásit', + 'Bad username or password' => 'Chybné uživatelské jméno nebo heslo', + 'Edit project' => 'Editovat projekt', + 'Name' => 'Jméno', + 'Projects' => 'Projekty', + 'No project' => 'Žádný projekt', + 'Project' => 'Projekt', + 'Status' => 'Stav', + 'Tasks' => 'Úkoly', + 'Board' => 'Nástěnka', + 'Actions' => 'Akce', + 'Inactive' => 'Neaktivní', + 'Active' => 'Aktivní', + 'Unable to update this board.' => 'Nástěnku není možné aktualizovat', + 'Disable' => 'Zakázat', + 'Enable' => 'Povolit', + 'New project' => 'Nový projekt', + 'Do you really want to remove this project: "%s"?' => 'Opravdu chcete odstranit projekt: "%s"?', + 'Remove project' => 'Odstranit projekt', + 'Edit the board for "%s"' => 'Editace nástěnky pro "%s" ', + 'Add a new column' => 'Přidat nový sloupec', + 'Title' => 'Název', + 'Assigned to %s' => 'Přiřazeno uživateli: %s', + 'Remove a column' => 'Odstranit sloupec', + 'Unable to remove this column.' => 'Tento sloupec nelze odstranit', + 'Do you really want to remove this column: "%s"?' => 'Opravdu chcete odstranit tento sloupec: "%s"?', + 'Settings' => 'Nastavení', + 'Application settings' => 'Nastavení aplikace', + 'Language' => 'Čeština', + 'Webhook token:' => 'Webhook token:', + 'API token:' => 'API token:', + 'Database size:' => 'Velikost databáze:', + 'Download the database' => 'Stáhnout databázi', + 'Optimize the database' => 'Optimalizovat databázi', + '(VACUUM command)' => '(příkaz VACUUM)', + '(Gzip compressed Sqlite file)' => '(Gzip komprimovaný soubor Sqlite)', + 'Close a task' => 'Zavřít úkol', + 'Column' => 'Sloupec', + 'Color' => 'Barva', + 'Assignee' => 'Přiřazeno uživateli', + 'Create another task' => 'Vytvořit další úkol', + 'New task' => 'Nový úkol', + 'Open a task' => 'Otevřít úkol', + 'Do you really want to open this task: "%s"?' => 'Opravdu chcete znovuotevřít tento úkol: "%s"?', + 'Back to the board' => 'Zpět na nástěnku', + 'There is nobody assigned' => 'Není přiřazeno žádnému uživateli', + 'Column on the board:' => 'Sloupec na tabuli:', + 'Close this task' => 'Uzavřít úkol', + 'Open this task' => 'Otevřít tento úkol', + 'There is no description.' => 'Bez popisu', + 'Add a new task' => 'Přidat nový úkol', + 'The username is required' => 'Uživatelské jméno je vyžadováno', + 'The maximum length is %d characters' => 'Maximální délka je %d znaků', + 'The minimum length is %d characters' => 'Minimální délka je %d znaků', + 'The password is required' => 'Heslo je vyžadováno', + 'This value must be an integer' => 'Je vyžadována číselná hodnota', + 'The username must be unique' => 'Uživatelské jméno musí být jedinečné', + 'The user id is required' => 'Uživatelské ID je vyžadováno', + 'Passwords don\'t match' => 'Heslo se neshoduje', + 'The confirmation is required' => 'Je vyžadováno potvrzení', + 'The project is required' => 'Projekt je vyžadován', + 'The id is required' => 'ID je vyžadováno', + 'The project id is required' => 'ID projektu je vyžadováno', + 'The project name is required' => 'Jméno projektu je vyžadováno', + 'The title is required' => 'Nadpis je vyžadován', + 'Settings saved successfully.' => 'Nastavení bylo úspěšně uloženo', + 'Unable to save your settings.' => 'Vaše nastavení nelze uložit.', + 'Database optimization done.' => 'Optimalizace databáze byla provedena.', + 'Your project has been created successfully.' => 'Projekt byl úspěšně vytvořen.', + 'Unable to create your project.' => 'Projekt nelze vytvořit.', + 'Project updated successfully.' => 'Projekt byl úspěšně aktualizován', + 'Unable to update this project.' => 'Projekt nebylo možné aktualizovat.', + 'Unable to remove this project.' => 'Projekt nebylo možné odstranit.', + 'Project removed successfully.' => 'Projekt byl odstraněn.', + 'Project activated successfully.' => 'Projekt byl povolen.', + 'Unable to activate this project.' => 'Aktivace projektu selhala.', + 'Project disabled successfully.' => 'Projekt byl zakázán.', + 'Unable to disable this project.' => 'Zakázání projektu selhalo.', + 'Unable to open this task.' => 'Nelze otevření tento úkol.', + 'Task opened successfully.' => 'Úkol byl úspěšně otevřen.', + 'Unable to close this task.' => 'Nelze uzavřít tento úkol.', + 'Task closed successfully.' => 'Úkol byl úspěšně uzavřen.', + 'Unable to update your task.' => 'Aktualizace úkolu se nezdařila.', + 'Task updated successfully.' => 'Úkol byl úspěšně aktualizován.', + 'Unable to create your task.' => 'Úkol nelze vytvořit.', + 'Task created successfully.' => 'Úkol byl úspěšně vytvořen.', + 'User created successfully.' => 'Uživatel byl úspěšně vytvořen.', + 'Unable to create your user.' => 'Uživatele nebylo možné vytvořit.', + 'User updated successfully.' => 'Uživatel byl úspěšně aktualizován.', + 'User removed successfully.' => 'Uživatel byl vymazán.', + 'Unable to remove this user.' => 'Uživatele nebylo možné odebrat.', + 'Board updated successfully.' => 'Nástěnka byla úspěšně aktualizována.', + 'Ready' => 'Připraveno', + 'Backlog' => 'Nevyřízené', + 'Work in progress' => 'V řešení', + 'Done' => 'Dokončeno', + 'Application version:' => 'Verze aplikace:', + 'Id' => 'ID', + 'Public link' => 'Veřejný odkaz', + 'Timezone' => 'Časová zóna', + 'Sorry, I didn\'t find this information in my database!' => 'Omlouváme se, tuto informaci nelze nalézt!', + 'Page not found' => 'Stránka nenalezena', + 'Complexity' => 'Složitost', + 'Task limit' => 'Maximální počet úkolů', + 'Task count' => 'Počet úkolů', + 'User' => 'Uživatel', + 'Comments' => 'Komentáře', + 'Comment is required' => 'Komentář je vyžadován', + 'Comment added successfully.' => 'Komentář byl úspěšně přidán.', + 'Unable to create your comment.' => 'Komentář nelze vytvořit.', + 'Due Date' => 'Datum splnění', + 'Invalid date' => 'Neplatné datum', + 'Automatic actions' => 'Automaticky vykonávané akce', + 'Your automatic action has been created successfully.' => 'Vaše akce byla úspěšně vytvořena.', + 'Unable to create your automatic action.' => 'Vaší akci nebylo možné vytvořit.', + 'Remove an action' => 'Odstranit akci', + 'Unable to remove this action.' => 'Tuto akci nelze odstranit.', + 'Action removed successfully.' => 'Akce byla úspěšně odstraněna.', + 'Automatic actions for the project "%s"' => 'Automaticky vykonávané akce pro projekt "%s"', + 'Add an action' => 'Přidat akci', + 'Event name' => 'Název události', + 'Action' => 'Akce', + 'Event' => 'Událost', + 'When the selected event occurs execute the corresponding action.' => 'Kdykoliv se vybraná událost objeví, vykonat odpovídající akci.', + 'Next step' => 'Další krok', + 'Define action parameters' => 'Definovat parametry akce', + 'Do you really want to remove this action: "%s"?' => 'Skutečně chcete odebrat tuto akci: "%s"?', + 'Remove an automatic action' => 'Odebrat automaticky prováděnou akci', + 'Assign the task to a specific user' => 'Přiřadit tento úkol konkrétnímu uživateli', + 'Assign the task to the person who does the action' => 'Přiřadit úkol osobě, která akci provádí', + 'Duplicate the task to another project' => 'Duplikovat úkol do jiného projektu', + 'Move a task to another column' => 'Přesun úkolu do jiného sloupce', + 'Task modification' => 'Modifikace úkolu', + 'Task creation' => 'Vytváření úkolu', + 'Closing a task' => 'Uzavření úkolu', + 'Assign a color to a specific user' => 'Přiřadit barvu konkrétnímu uživateli', + 'Position' => 'Pozice', + 'Duplicate to project' => 'Vytvořit kopii v jiném projektu', + 'Duplicate' => 'Vytvořit kopii', + 'Link' => 'Odkaz', + 'Comment updated successfully.' => 'Komentář byl úspěšně aktualizován.', + 'Unable to update your comment.' => 'Nelze upravit Váš komentář.', + 'Remove a comment' => 'Odebrat komentář', + 'Comment removed successfully.' => 'Komentář byl smazán.', + 'Unable to remove this comment.' => 'Komentář nelze odebrat.', + 'Do you really want to remove this comment?' => 'Skutečně chcete odebrat tento komentář?', + 'Current password for the user "%s"' => 'Aktuální heslo pro uživatele "%s"', + 'The current password is required' => 'Heslo je vyžadováno', + 'Wrong password' => 'Neplatné heslo', + 'Unknown' => 'Neznámý', + 'Last logins' => 'Poslední přihlášení', + 'Login date' => 'Datum přihlášení', + 'Authentication method' => 'Autentifikační metoda', + 'IP address' => 'IP adresa', + 'User agent' => 'User Agent', + 'Persistent connections' => 'Trvalé připojení', + 'No session.' => 'doposud žádná relace.', + 'Expiration date' => 'Datum expirace', + 'Remember Me' => 'Zapamatovat si', + 'Creation date' => 'Datum vytvoření', + 'Everybody' => 'Kdokoliv', + 'Open' => 'Otevřené', + 'Closed' => 'Uzavřené', + 'Search' => 'Vyhledat', + 'Nothing found.' => 'Nenalezena žádná položka.', + 'Due date' => 'Plánovaný termín', + 'Description' => 'Podrobný popis', + '%d comments' => '%d komentářů', + '%d comment' => '%d komentář', + 'Email address invalid' => 'Neplatná e-mailová adresa', + 'Your external account is not linked anymore to your profile.' => 'Váš externí účet již není propojen s vaším profilem.', + 'Unable to unlink your external account.' => 'Nelze odpojit externí účet.', + 'External authentication failed' => 'Selhalo externí ověřování', + 'Your external account is linked to your profile successfully.' => 'Váš externí účet je úspěšně propojen s vaším profilem.', + 'Email' => 'E-Mail', + 'Task removed successfully.' => 'Úkol byl úspěšně odebrán.', + 'Unable to remove this task.' => 'Tento úkol nelze odebrat.', + 'Remove a task' => 'Odebrat úkol', + 'Do you really want to remove this task: "%s"?' => 'Opravdu chcete odebrat úkol: "%s"?', + 'Assign automatically a color based on a category' => 'Automaticky přiřadit barvu v závislosti na kategorii', + 'Assign automatically a category based on a color' => 'Automaticky přiřadit kategorii v závislosti na barvě', + 'Task creation or modification' => 'Vytváření nebo úprava úkolu', + 'Category' => 'Kategorie', + 'Category:' => 'Kategorie:', + 'Categories' => 'Kategorie', + 'Your category has been created successfully.' => 'Kategorie byla úspěšně vytvořena.', + 'This category has been updated successfully.' => 'Kategorie byla úspěšně aktualizována.', + 'Unable to update this category.' => 'Kategorii nelze aktualizovat.', + 'Remove a category' => 'Odstranit kategorii', + 'Category removed successfully.' => 'Kategorie byla odstraněna.', + 'Unable to remove this category.' => 'Kategorie nemhla být odstraněna.', + 'Category modification for the project "%s"' => 'Aktualizace kategoire pro projekt "%s" ', + 'Category Name' => 'Název kategorie', + 'Add a new category' => 'Přidat kategorii', + 'Do you really want to remove this category: "%s"?' => 'Skutečně chcete odebrat kategorii: "%s"?', + 'All categories' => 'Všechny kategorie', + 'No category' => 'Žádná kategorie', + 'The name is required' => 'Název je vyžadován', + 'Remove a file' => 'Odstranit sougor', + 'Unable to remove this file.' => 'Soubor nelze odebrat.', + 'File removed successfully.' => 'Soubor byl úspěšně odebrán.', + 'Attach a document' => 'Vložit dokument', + 'Do you really want to remove this file: "%s"?' => 'Skutečně chcete odebrat soubor: "%s"?', + 'Attachments' => 'Přílohy', + 'Edit the task' => 'Upravit úkol', + 'Add a comment' => 'Přidat komentář', + 'Edit a comment' => 'Upravit komentář', + 'Summary' => 'Souhrn', + 'Time tracking' => 'Sledování času', + 'Estimate:' => 'Odhad:', + 'Spent:' => 'Stráveno:', + 'Do you really want to remove this sub-task?' => 'Skutečně chcete odebrat dílčí úkol?', + 'Remaining:' => 'Zbývá:', + 'hours' => 'hodin', + 'estimated' => 'odhadnuto', + 'Sub-Tasks' => 'Dílčí úkoly', + 'Add a sub-task' => 'Přidat dílčí úkol', + 'Original estimate' => 'Časový odhad', + 'Create another sub-task' => 'Vytvořit další dílčí úkol', + 'Time spent' => 'Strávený čas', + 'Edit a sub-task' => 'Upravid dílčí úkol', + 'Remove a sub-task' => 'Odstranit dílčí úkol', + 'The time must be a numeric value' => 'Zadejte numerickou hodnotu času', + 'Todo' => 'Seznam úkolů', + 'In progress' => 'Zpracováváme', + 'Sub-task removed successfully.' => 'Dílčí úkol byl smazán.', + 'Unable to remove this sub-task.' => 'Tento dílčí úkol nelze odebrat.', + 'Sub-task updated successfully.' => 'Dílčí úkol byl aktualizován.', + 'Unable to update your sub-task.' => 'Nelze aktualizovat dílčí úkol.', + 'Unable to create your sub-task.' => 'Nelze vytvořit dílčí úkol.', + 'Maximum size: ' => 'Maximální velikost: ', + 'Display another project' => 'Zobrazit jiný projekt', + 'Created by %s' => 'Vytvořeno uživatelem %s', + 'Tasks Export' => 'Export úkolů', + 'Start Date' => 'Počáteční datum', + 'Execute' => 'Spustit', + 'Task Id' => 'Úkol ID', + 'Creator' => 'Vlastník', + 'Modification date' => 'Datum úpravy', + 'Completion date' => 'Datum dokončení', + 'Clone' => 'Kopie', + 'Project cloned successfully.' => 'Kopie projektu byla úspěšně vytvořena.', + 'Unable to clone this project.' => 'Kopii projektu nelze vytvořit.', + 'Enable email notifications' => 'Povolit upozornění pomocí e-mailů', + 'Task position:' => 'Pořadí úkolu:', + 'The task #%d has been opened.' => 'Úkol #%d byl znovu otevřen.', + 'The task #%d has been closed.' => 'Úkol #%d byl uzavřen.', + 'Sub-task updated' => 'Dílčí úkol byl aktualizován', + 'Title:' => 'Nadpis:', + 'Status:' => 'Stav:', + 'Assignee:' => 'Přiřazeno:', + 'Time tracking:' => 'Sledování času:', + 'New sub-task' => 'Nový dílčí úkol', + 'New attachment added "%s"' => 'Byla přidána nová příloha "%s".', + 'New comment posted by %s' => 'Nový komentář publikovaný uživatelem %s', + 'New comment' => 'Nový komentář', + 'Comment updated' => 'Komentář byl aktualizován.', + 'New subtask' => 'Nový dílčí úkol', + 'I only want to receive notifications for these projects:' => 'Přeji si dostávat upozornění pouze pro následující projekty:', + 'view the task on Kanboard' => 'Zobrazit úkol na Kanboard', + 'Public access' => 'Veřejný přístup', + 'Disable public access' => 'Zakázat veřejný přístup', + 'Enable public access' => 'Povolit veřejný přístup', + 'Public access disabled' => 'Veřejný přístup zakázán', + 'Move the task to another project' => 'Přesunout úkol do jiného projektu', + 'Move to project' => 'Přesunout do jiného projektu', + 'Do you really want to duplicate this task?' => 'Opravdu chcete vytořit kopii tohoto úkolu?', + 'Duplicate a task' => 'Vytvořit kopii úkolu', + 'External accounts' => 'Externí účty', + 'Account type' => 'typ účtu', + 'Local' => 'Lokální', + 'Remote' => 'Vzdálený', + 'Enabled' => 'Povoleno', + 'Disabled' => 'Zakázáno', + 'Login:' => 'Uživatelské jméno:', + 'Full Name:' => 'Jméno:', + 'Email:' => 'e-mail', + 'Notifications:' => 'Upozornění:', + 'Notifications' => 'Upozornění', + 'Account type:' => 'Typ účtu:', + 'Edit profile' => 'Upravit profil', + 'Change password' => 'Změnit heslo', + 'Password modification' => 'Změna hesla', + 'External authentications' => 'Vzdálená autorizace', + 'Never connected.' => 'Zatím nikdy nespojen.', + 'No external authentication enabled.' => 'Není povolena žádná vzdálená autorizace.', + 'Password modified successfully.' => 'Heslo bylo úspěšně změněno.', + 'Unable to change the password.' => 'Nelze změnit heslo.', + 'Change category' => 'Změnit kategorii', + '%s updated the task %s' => '%s aktualizoval úkol %s ', + '%s opened the task %s' => '%s znovu otevřel úkol %s ', + '%s moved the task %s to the position #%d in the column "%s"' => '%s přesunul úkol %s na pozici #%d ve sloupci "%s" ', + '%s moved the task %s to the column "%s"' => '%s přesunul úkol %s do sloupce "%s" ', + '%s created the task %s' => '%s vytvořil úkol %s ', + '%s closed the task %s' => '%s uzavřel úkol %s ', + '%s created a subtask for the task %s' => '%s vytvořil dílčí úkol pro úkol %s ', + '%s updated a subtask for the task %s' => '%s aktualizoval dílčí úkol pro úkol %s ', + 'Assigned to %s with an estimate of %s/%sh' => 'Přiřazeno uživateli %s s časovým odhadem práce %s/%s dní', + 'Not assigned, estimate of %sh' => 'Nepřiřazeno, časový odhad práce je %s dní', + '%s updated a comment on the task %s' => '%s aktualizoval komentář k úkolu %s ', + '%s commented the task %s' => '%s přidal komentář k úkolu %s ', + '%s\'s activity' => 'Aktivity projektu %s', + 'RSS feed' => 'RSS kanál', + '%s updated a comment on the task #%d' => '%s aktualizoval komnetář k úkolu #%d ', + '%s commented on the task #%d' => '%s přidal komentář k úkolu #%d ', + '%s updated a subtask for the task #%d' => '%s aktualizoval dílčí úkol úkolu #%d ', + '%s created a subtask for the task #%d' => '%s vytvořil dílčí úkol úkolu #%d ', + '%s updated the task #%d' => '%s aktualizoval úkol #%d ', + '%s created the task #%d' => '%s vytvořil úkol #%d ', + '%s closed the task #%d' => '%s uzavřel úkol #%d ', + '%s opened the task #%d' => '%s znovu otevřel úkol #%d ', + 'Activity' => 'Aktivity', + 'Default values are "%s"' => 'Standardní hodnoty jsou: "%s"', + 'Default columns for new projects (Comma-separated)' => 'Výchozí sloupce pro nové projekty (odděleny čárkou)', + 'Task assignee change' => 'Změna přiřazení uživatelů', + '%s changed the assignee of the task #%d to %s' => '%s změnil přidělení úkolu #%d na uživatele %s', + '%s changed the assignee of the task %s to %s' => '%s změnil přidělení úkolu %s na uživatele %s', + 'New password for the user "%s"' => 'Nové heslo pro uživatele "%s"', + 'Choose an event' => 'Vybrat událost', + 'Create a task from an external provider' => 'Vytvořit úkol externím poskytovatelem', + 'Change the assignee based on an external username' => 'Změnit přiřazení uživatele závislé na externím uživateli', + 'Change the category based on an external label' => 'Změnit kategorii závislou na externím popisku', + 'Reference' => 'Reference', + 'Label' => 'Značka', + 'Database' => 'Databáze', + 'About' => 'O projektu', + 'Database driver:' => 'Databázový ovladač:', + 'Board settings' => 'Nastavení nástěnky', + 'Webhook settings' => 'Webhook nastavení', + 'Reset token' => 'Token reset', + 'API endpoint:' => 'API endpoint', + 'Refresh interval for personal board' => 'Interval automatického obnovování pro osobní nástěnky', + 'Refresh interval for public board' => 'Interval automatického obnovování pro veřejné nástěnky', + 'Task highlight period' => 'Úkol zvýrazněného období', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Interval (v sekundách), ve kterém je považovány úpravy úkolů za aktuální (0 pro zakázání, 2 dny ve výchozím nastavení)', + 'Frequency in second (60 seconds by default)' => 'Frekvence v sekundách (60 sekund ve výchozím nastavení)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frekvence v sekundách (0 pro zákaz této vlastnosti, 10 sekund ve výchozím nastavení)', + 'Application URL' => 'URL aplikace', + 'Token regenerated.' => 'Token byl opětovně generován.', + 'Date format' => 'Formát data', + 'ISO format is always accepted, example: "%s" and "%s"' => 'ISO formát je vždy akceptován, například: "%s" a "%s"', + 'New personal project' => 'Nový osobní projekt', + 'This project is personal' => 'Tento projekt je osobní', + 'Add' => 'Přidat', + 'Start date' => 'Počáteční datum', + 'Time estimated' => 'Odhadovaný čas', + 'There is nothing assigned to you.' => 'Nemáte přiřazenou žádnou položku.', + 'My tasks' => 'Moje úkoly', + 'Activity stream' => 'Přehled aktivit', + 'Dashboard' => 'Nástěnka', + 'Confirmation' => 'Potvrzení', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Create a comment from an external provider' => 'Vytvořit komentář pomocí externího poskytovatele', + 'Project management' => 'Správa projektů', + 'Columns' => 'Sloupce', + 'Task' => 'Úkol', + 'Percentage' => 'Procenta', + 'Number of tasks' => 'Počet úkolů', + 'Task distribution' => 'Rozdělení úkolů', + 'Analytics' => 'Analýza', + 'Subtask' => 'Dílčí úkoly', + 'User repartition' => 'Rozdělení podle uživatelů', + 'Clone this project' => 'Duplokovat projekt', + 'Column removed successfully.' => 'Sloupec byl odstraněn.', + 'Not enough data to show the graph.' => 'Pro zobrazení grafu není dostatek dat.', + 'Previous' => 'Předchozí', + 'The id must be an integer' => 'ID musí být celé číslo', + 'The project id must be an integer' => 'ID projektu musí být celé číslo', + 'The status must be an integer' => 'Status musí být celé číslo', + 'The subtask id is required' => 'Je požadováno id dílčího úkolu', + 'The subtask id must be an integer' => 'ID dílčího úkolu musí být číslo', + 'The task id is required' => 'ID úkolu je povinné', + 'The task id must be an integer' => 'ID úkolu musí být číslo', + 'The user id must be an integer' => 'ID uživatele musí být číslo', + 'This value is required' => 'Hodnota je povinná', + 'This value must be numeric' => 'Hodnota musí být číselná', + 'Unable to create this task.' => 'Nelze vytvořit tento úkol', + 'Cumulative flow diagram' => 'Kumulativní diagram', + 'Daily project summary' => 'Denní přehledy', + 'Daily project summary export' => 'Export denních přehledů', + 'Exports' => 'Exporty', + 'This export contains the number of tasks per column grouped per day.' => 'Tento export obsahuje počet úkolů pro jednotlivé sloupce seskupených podle dní.', + 'Active swimlanes' => 'Aktivní dráhy', + 'Add a new swimlane' => 'Přidat novou dráhu', + 'Default swimlane' => 'Výchozí dráha', + 'Do you really want to remove this swimlane: "%s"?' => 'Opravdu si přejete odstranit tuto dráhu: "%s"?', + 'Inactive swimlanes' => 'Neaktivní dráha', + 'Remove a swimlane' => 'Odstranit dráhu', + 'Swimlane modification for the project "%s"' => 'Změny dráhy pro projekt "%s"', + 'Swimlane removed successfully.' => 'Dráha byla odstraněna.', + 'Swimlanes' => 'Dráhy', + 'Swimlane updated successfully.' => 'Dráha byla upravena.', + 'Unable to remove this swimlane.' => 'Tuto dráhu nelze odstranit.', + 'Unable to update this swimlane.' => 'Tuto dráhu nelze upravit.', + 'Your swimlane has been created successfully.' => 'Dráha byla vytvořena.', + 'Example: "Bug, Feature Request, Improvement"' => 'Například: "Chyba", "Nápad", "Požadavek"...', + 'Default categories for new projects (Comma-separated)' => 'Výchozí kategorie pro nové projekty (oddělené čárkou)', + 'Integrations' => 'Integrace', + 'Integration with third-party services' => 'Integrace se službami třetích stran', + 'Subtask Id' => 'Dílčí úkol Id', + 'Subtasks' => 'Dílčí úkoly', + 'Subtasks Export' => 'Export dílčích úkolů', + 'Task Title' => 'Název úkolu', + 'Untitled' => 'bez názvu', + 'Application default' => 'Standardní hodnoty', + 'Language:' => 'Jazyk:', + 'Timezone:' => 'Časová zóna:', + 'All columns' => 'Všechny sloupce', + 'Next' => 'Další', + '#%d' => '#%d', + 'All swimlanes' => 'Alle Swimlanes', + 'All colors' => 'Všechny barvy', + 'Moved to column %s' => 'Přesunuto do sloupce %s ', + 'User dashboard' => 'Nástěnka uživatele', + 'Allow only one subtask in progress at the same time for a user' => 'Umožnit uživateli práci pouze na jednom dílčím úkolu ve stejném čase', + 'Edit column "%s"' => 'Upravit sloupec "%s" ', + 'Select the new status of the subtask: "%s"' => 'Vyberte nový stav pro dílčí úkol: "%s"', + 'Subtask timesheet' => 'Časový rozvrh dílčích úkolů', + 'There is nothing to show.' => 'Žádná položka k zobrazení', + 'Time Tracking' => 'Sledování času', + 'You already have one subtask in progress' => 'Jeden dílčí úkol již aktuálně řešíte', + 'Which parts of the project do you want to duplicate?' => 'Které části projektu chcete duplikovat?', + 'Disallow login form' => 'Zakázat přihlašovací formulář', + 'Start' => 'Začátek', + 'End' => 'Konec', + 'Task age in days' => 'Doba trvání úkolu ve dnech', + 'Days in this column' => 'Dní v tomto sloupci', + '%dd' => '%dd', + 'Add a new link' => 'Přidat nový odkaz', + 'Do you really want to remove this link: "%s"?' => 'Opravdu chcete odstranit odkaz "%s"?', + 'Do you really want to remove this link with task #%d?' => 'Opravdu chcete odstranit odkaz na úkol #%d ?', + 'Field required' => 'Povinné pole', + 'Link added successfully.' => 'Propojení bylo úspěšně přidáno.', + 'Link updated successfully.' => 'Propojení bylo úspěšně aktualizováno.', + 'Link removed successfully.' => 'Propojení bylo úspěšně odebráno.', + 'Link labels' => 'Seznam odkazů', + 'Link modification' => 'Úpravy odkazů', + 'Opposite label' => 'Opačný text', + 'Remove a link' => 'Odstranit odkaz', + 'The labels must be different' => 'názvy musí být odlišné', + 'There is no link.' => 'Nejsou zde žádné odkazy', + 'This label must be unique' => 'Tento název musí být jedinečný', + 'Unable to create your link.' => 'Nelze vytvořit toto propojení.', + 'Unable to update your link.' => 'Nelze aktualizovat toto propojení.', + 'Unable to remove this link.' => 'Nelze odstranit toto propojení', + 'relates to' => 'souvisí s', + 'blocks' => 'blokuje', + 'is blocked by' => 'je blokován', + 'duplicates' => 'duplikuje', + 'is duplicated by' => 'je duplikován', + 'is a child of' => 'je podřízený', + 'is a parent of' => 'je nadřízený', + 'targets milestone' => 'patří k milníku', + 'is a milestone of' => 'je milníkem', + 'fixes' => 'nahrazuje', + 'is fixed by' => 'je nahrazen', + 'This task' => 'Tento úkol', + '<1h' => '<1h', + '%dh' => '%dh', + 'Expand tasks' => 'Rozbalit úkoly', + 'Collapse tasks' => 'Sbalit úkoly', + 'Expand/collapse tasks' => 'Rozbalit / sbalit úkoly', + 'Close dialog box' => 'Zavřít dialogové okno', + 'Submit a form' => 'Odeslat formulář', + 'Board view' => 'Zobrazení nástěnky', + 'Keyboard shortcuts' => 'Klávesnicové zkratky', + 'Open board switcher' => 'Otevřít přepínač nástěnek', + 'Application' => 'Aplikace', + 'Compact view' => 'Kompaktní zobrazení', + 'Horizontal scrolling' => 'Horizontální rolování', + 'Compact/wide view' => 'Kompaktní/plné zobrazení', + 'Currency' => 'Měna', + 'Personal project' => 'Osobní projekt', + 'AUD - Australian Dollar' => 'AUD - Australský dolar', + 'CAD - Canadian Dollar' => 'CAD - kanadský dolar', + 'CHF - Swiss Francs' => 'CHF - švýcarský frank', + 'Custom Stylesheet' => 'Vlastní šablony stylů', + 'EUR - Euro' => 'EUR - Euro', + 'GBP - British Pound' => 'GBP - Britská Libra', + 'INR - Indian Rupee' => 'INR - Indická rupie', + 'JPY - Japanese Yen' => 'JPY - Japonský jen', + 'NZD - New Zealand Dollar' => 'NZD - Novozélandský dolar', + 'PEN - Peruvian Sol' => 'PEN - Peruánský sol', + 'RSD - Serbian dinar' => 'RSD - Srbský dinár', + 'CNY - Chinese Yuan' => 'CNY - čínský jüan', + 'USD - US Dollar' => 'USD - Americký dolar', + 'VES - Venezuelan Bolívar' => 'VES - Venezuelský bolívar', + 'Destination column' => 'Cílový sloupec', + 'Move the task to another column when assigned to a user' => 'Přesunout úkol do jiného sloupce, když je úkol přiřazen uživateli.', + 'Move the task to another column when assignee is cleared' => 'Přesunout úkol do jiného sloupce, když je pověření uživatele vymazáno.', + 'Source column' => 'Zdrojový sloupec', + 'Transitions' => 'Změny etap', + 'Executer' => 'Vykonavatel', + 'Time spent in the column' => 'Trvání jednotlivých etap', + 'Task transitions' => 'Přesuny úkolů', + 'Task transitions export' => 'Export přesunů mezi sloupci', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Tento seznam obsahuje všechny pohyby úkolů s daty, uživateli a časy strávenými na úkolu.', + 'Currency rates' => 'Aktuální kurzy', + 'Rate' => 'Kurz', + 'Change reference currency' => 'Změnit referenční měnu', + 'Reference currency' => 'Referenční měna', + 'The currency rate has been added successfully.' => 'Směnný kurz byl úspěšně přidán.', + 'Unable to add this currency rate.' => 'Nelze přidat tento směnný kurz', + 'Webhook URL' => 'Webhook URL', + '%s removed the assignee of the task %s' => '%s odstranil přiřazení úkolu %s ', + 'Information' => 'Informace', + 'Check two factor authentication code' => 'Zkontrolujte dvouúrovňový autentifikační klíč', + 'The two factor authentication code is not valid.' => 'Dvouúrovňový autentifikační klíč není platný.', + 'The two factor authentication code is valid.' => 'Dvouúrovňový autentifikační klíč je platný.', + 'Code' => 'Klíč', + 'Two factor authentication' => 'Dvouúrovňová autorizace', + 'This QR code contains the key URI: ' => 'Tento QR kód obsahuje adresu s klíčem: ', + 'Check my code' => 'Kontrola mého kódu', + 'Secret key: ' => 'Tajný klíč: ', + 'Test your device' => 'Test Vašeho zařízení', + 'Assign a color when the task is moved to a specific column' => 'Přiřadit barvu, když je úkol přesunut do konkrétního sloupce', + '%s via Kanboard' => '%s via Kanboard', + 'Burndown chart' => 'Burndown-Chart', + 'This chart show the task complexity over the time (Work Remaining).' => 'Graf zobrazuje složitost úkolů v čase (Zbývající práce).', + 'Screenshot taken %s' => 'Snímek obrazovky pořízen %s ', + 'Add a screenshot' => 'Přidat snímek obrazovky', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Pořiďte snímek obrazovky a v tomto poli stiskněte Ctrl+V nebo ⌘+V ', + 'Screenshot uploaded successfully.' => 'Snímek obrazovky byl úspěšně nahrán.', + 'SEK - Swedish Krona' => 'SEK - Schwedische Kronen', + 'Identifier' => 'Identifikátor', + 'Disable two factor authentication' => 'Zrušit dvouúrovňovou autorizaci', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Opravdu chcete vypnout dvouúrovňovou autentifikaci pro uživatele: "%s"?', + 'Edit link' => 'Upravit odkaz', + 'Start to type task title...' => 'Začněte zadávat název úkolu ...', + 'A task cannot be linked to itself' => 'Úkol nemůže být napojen na sebe', + 'The exact same link already exists' => 'Stejný odkaz již existuje', + 'Recurrent task is scheduled to be generated' => 'Plánované generování opakovaného úkolu', + 'Score' => 'Skóre', + 'The identifier must be unique' => 'Identifikátor musí být unikátní', + 'This linked task id doesn\'t exists' => 'Tento odkazovaný úkol neexistuje', + 'This value must be alphanumeric' => 'Tato hodnota musí být alfanumerická', + 'Edit recurrence' => 'Upravit opakování', + 'Generate recurrent task' => 'Generovat opakující se úkol', + 'Trigger to generate recurrent task' => 'Spouštěč pro generování opakujícího se úkolu', + 'Factor to calculate new due date' => 'Faktor pro výpočet nového data splnění', + 'Timeframe to calculate new due date' => 'Časové okno pro výpočet nového data splnění', + 'Base date to calculate new due date' => 'Výchozí datum pro výpočet nového data splnění', + 'Action date' => 'Datum akce', + 'Base date to calculate new due date: ' => 'Výchozí datum pro výpočet nového data splnění: ', + 'This task has created this child task: ' => 'Tento úkol vygeneroval následující dílčí úkol: ', + 'Day(s)' => 'Dny', + 'Existing due date' => 'Existující datum splnění', + 'Factor to calculate new due date: ' => 'Faktor pro výpočet nového data splnění: ', + 'Month(s)' => 'Měsíce', + 'This task has been created by: ' => 'Tento úkol vytvořil: ', + 'Recurrent task has been generated:' => 'Opakující se úkol vygeneroval: ', + 'Timeframe to calculate new due date: ' => 'Časové okno pro výpočet nového data splnění: ', + 'Trigger to generate recurrent task: ' => 'Spouštěč pro generování opakujícího se úkolu: ', + 'When task is closed' => 'Když je úkol uzavřen', + 'When task is moved from first column' => 'Když je úkol přesunut z prvního sloupce', + 'When task is moved to last column' => 'Když je úkol přesunut do posleního sloupce', + 'Year(s)' => 'Rok(y)', + 'Project settings' => 'Nastavení projektu', + 'Automatically update the start date' => 'Automaticky aktualizovat počáteční datum', + 'iCal feed' => 'iCal feed', + 'Preferences' => 'Předvolby', + 'Security' => 'Zabezpečení ', + 'Two factor authentication disabled' => 'Dvouúrovňová autorizace zakázána.', + 'Two factor authentication enabled' => 'Dvouúrovňová autorizace povolena.', + 'Unable to update this user.' => 'Uživatele nelze aktualizovat.', + 'There is no user management for personal projects.' => 'Pro osobní projekty není aplikována správa uživatelů.', + 'User that will receive the email' => 'Uživatel, který dostane E-mail', + 'Email subject' => 'E-mail Předmět', + 'Date' => 'Datum', + 'Add a comment log when moving the task between columns' => 'Přidat komentář když je úkol přesouván mezi sloupci', + 'Move the task to another column when the category is changed' => 'Přesunout úkol do jiného sloupce když je změněna kategorie', + 'Send a task by email to someone' => 'Poslat někomu úkol poštou', + 'Reopen a task' => 'Znovu otevřít úkol', + 'Notification' => 'Upozornění', + '%s moved the task #%d to the first swimlane' => '%s přesunul úkol #%d do první dráhy', + 'Swimlane' => 'Dráha', + '%s moved the task %s to the first swimlane' => '%s přesunul úkol %s do první dráhy', + '%s moved the task %s to the swimlane "%s"' => '%s přesunul úkol %s do dráhy "%s"', + 'This report contains all subtasks information for the given date range.' => 'Report obsahuje všechny informace o dílčích úkolech pro daný časový úsek', + 'This report contains all tasks information for the given date range.' => 'Report obsahuje informace o všech úkolech pro daný časový úsek.', + 'Project activities for %s' => 'Aktivity projektu %s', + 'view the board on Kanboard' => 'Zobrazit nástěnku', + 'The task has been moved to the first swimlane' => 'Úkol byl přesunut do první dráhy', + 'The task has been moved to another swimlane:' => 'Úkol byl přesunut do další dráhy', + 'New title: %s' => 'Nový název: %s', + 'The task is not assigned anymore' => 'Úkol již není přidělen', + 'New assignee: %s' => 'přidělení: %s', + 'There is no category now' => 'Nyní neexistuje žádná kategorie', + 'New category: %s' => 'Nová kategorie: %s', + 'New color: %s' => 'Nová barva: %s', + 'New complexity: %d' => 'Nová složitost: %d', + 'The due date has been removed' => 'Datum dokončení byl odstraněn', + 'There is no description anymore' => 'Ještě neexistuje žádný popis', + 'Recurrence settings has been modified' => 'Nastavení opakování bylo změněno', + 'Time spent changed: %sh' => 'Strávený čas se změnil: %sh', + 'Time estimated changed: %sh' => 'Odhadovaný čas se změnil: %sh', + 'The field "%s" has been updated' => 'Sloupec "%s" byl upraven', + 'The description has been modified:' => 'Popis byl upraven:', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Opravdu si přejete úkol "%s" uzavřít? (stejně jako všechny dílčí úkoly)', + 'I want to receive notifications for:' => 'Chci dostávat upozornění na:', + 'All tasks' => 'Všechny úkoly', + 'Only for tasks assigned to me' => 'pouze pro moje úkoly', + 'Only for tasks created by me' => 'pouze pro mnou vytvořené úkoly', + 'Only for tasks created by me and tasks assigned to me' => 'pouze pro mnou vytvořené a mě přiřazené úkoly', + '%%Y-%%m-%%d' => '%%Y-%%m-%%d', + 'Total for all columns' => 'Celkem pro všechny sloupce', + 'You need at least 2 days of data to show the chart.' => 'Potřebujete nejméně data ze dvou dnů pro zobrazení grafu', + '<15m' => '<15min.', + '<30m' => '<30min.', + 'Stop timer' => 'Zastavit časovač', + 'Start timer' => 'Spustit časovač', + 'My activity stream' => 'Přehled mých aktivit', + 'Search tasks' => 'Hledání úkolů', + 'Reset filters' => 'Resetovat filtry', + 'My tasks due tomorrow' => 'Moje zítřejší úkoly', + 'Tasks due today' => 'Dnešní úkoly', + 'Tasks due tomorrow' => 'Zítřejší úkoly', + 'Tasks due yesterday' => 'Včerejší úkoly', + 'Closed tasks' => 'Uzavřené úkoly', + 'Open tasks' => 'Otevřené úkoly', + 'Not assigned' => 'Nepřiřazené', + 'View advanced search syntax' => 'Zobrazit syntaxi rozšířeného vyhledávání', + 'Overview' => 'Přehled', + 'Board/Calendar/List view' => 'Nástěnka/Kalendář/Zobrazení seznamu', + 'Switch to the board view' => 'Přepnout na nástěnku', + 'Switch to the list view' => 'Přepnout na seznam zobrazení', + 'Go to the search/filter box' => 'Zobrazit vyhledávání/filtrování', + 'There is no activity yet.' => 'Doposud nejsou žádné aktivity.', + 'No tasks found.' => 'Nenalezen žádný úkol.', + 'Keyboard shortcut: "%s"' => 'Klávesová zkratka: "%s"', + 'List' => 'Seznam', + 'Filter' => 'Filtr', + 'Advanced search' => 'Rozšířené hledání', + 'Example of query: ' => 'Příklad dotazu: ', + 'Search by project: ' => 'Hledat podle projektu: ', + 'Search by column: ' => 'Hledat podle sloupce: ', + 'Search by assignee: ' => 'Hledat podle přiřazené osoby: ', + 'Search by color: ' => 'Hledat podle barvy: ', + 'Search by category: ' => 'Hledat podle kategorie: ', + 'Search by description: ' => 'Hledat podle popisu: ', + 'Search by due date: ' => 'Hledat podle termínu: ', + 'Average time spent in each column' => 'Průměrná doba strávená v každé fázi', + 'Average time spent' => 'Průměrná strávená doba', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Tento graf ukazuje průměrný čas strávený v každém sloupci pro poslední úkoly %d.', + 'Average Lead and Cycle time' => 'Průměrná dodací lhůta a doba cyklu', + 'Average lead time: ' => 'Průměrná dodací lhůta: ', + 'Average cycle time: ' => 'Průměrná doba cyklu: ', + 'Cycle Time' => 'Doba cyklu', + 'Lead Time' => 'Dodací lhůta', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Graf ukazuje průměrnou dodací lhůtu a dobu cyklu pro posledních %d úkolů v průběhu času', + 'Average time into each column' => 'Průměrná doba v každé fázi', + 'Lead and cycle time' => 'Dodací lhůta a doba cyklu', + 'Lead time: ' => 'Dodací lhůta: ', + 'Cycle time: ' => 'Doba cyklu: ', + 'Time spent in each column' => 'Čas strávený v každé fázi', + 'The lead time is the duration between the task creation and the completion.' => 'Lead time (dodací lhůta) je čas od založení úkolu do jeho dokončení.', + 'The cycle time is the duration between the start date and the completion.' => 'Doba cyklu je doba trvání mezi zahájením a dokončením úkolu.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Jestliže není úkol uzavřen, místo termínu dokončení je použit aktuální čas.', + 'Set the start date automatically' => 'Nastavit automaticky počáteční datum', + 'Edit Authentication' => 'Upravit ověřování', + 'Remote user' => 'Vzdálený uživatel', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Hesla vzdáleným uživatelům se neukládají do databáze Kanboard. Naříklad: LDAP, Google a Github účty.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Pokud zaškrtnete políčko "Zakázat přihlašovací formulář", budou pověření zadané do přihlašovacího formuláře ignorovány.', + 'Default task color' => 'Výchozí barva úkolu', + 'This feature does not work with all browsers.' => 'Tato funkcionalita nefunguje ve všech prohlížečích.', + 'There is no destination project available.' => 'Není dostupný žádný cílový projekt.', + 'Trigger automatically subtask time tracking' => 'Spouštět automaticky dílčí úkol sledování času', + 'Include closed tasks in the cumulative flow diagram' => 'začlenit dokončené úkoly do kumulativního flow diagramu', + 'Current swimlane: %s' => 'Aktuální swimlane: %s', + 'Current column: %s' => 'Aktuální fáze: %s', + 'Current category: %s' => 'Aktuální kategorie: %s', + 'no category' => 'kategorie nenastavena', + 'Current assignee: %s' => 'Aktuálně přiřazený uživatel: %s', + 'not assigned' => 'nepřiřazeno', + 'Author:' => 'Autor:', + 'contributors' => 'přispěvatelé', + 'License:' => 'Licence:', + 'License' => 'Licence', + 'Enter the text below' => 'Zadejte text níže', + 'Start date:' => 'Termín zahájení:', + 'Due date:' => 'Termín dokončení:', + 'People who are project managers' => 'Lidé, kteří jsou správci projektu', + 'People who are project members' => 'Lidé, kteří jsou členové projektu', + 'NOK - Norwegian Krone' => 'NOK - Norská koruna', + 'Show this column' => 'Zobrazit tento sloupec', + 'Hide this column' => 'Skrýt tento sloupec', + 'End date' => 'Datum konce', + 'Users overview' => 'Přehled uživatelů', + 'Members' => 'Členové', + 'Shared project' => 'Sdílený projekt', + 'Project managers' => 'Správci projektu', + 'Projects list' => 'Seznam projektů', + 'End date:' => 'Datum konce: ', + 'Change task color when using a specific task link' => 'Změnit barvu úkolu při použití konkrétního odkazu na úkol', + 'Task link creation or modification' => 'Vytvoření, nebo změna odkazu na úkol', + 'Milestone' => 'Milník', + 'Reset the search/filter box' => 'Vyresetovat pole pro vyhledávání / filtrování', + 'Documentation' => 'Dokumentace', + 'Author' => 'Autor', + 'Version' => 'Verze', + 'Plugins' => 'Pluginy', + 'There is no plugin loaded.' => 'Není nainstalován plugin.', + 'My notifications' => 'Moje oznámení', + 'Custom filters' => 'Vlastní filtry', + 'Your custom filter has been created successfully.' => 'Vlastní filtr byl úspěšně vytvořen.', + 'Unable to create your custom filter.' => 'Nelze vytvořit vlastní filtr.', + 'Custom filter removed successfully.' => 'Vlastní filtr byl úspěšně odebrán.', + 'Unable to remove this custom filter.' => 'Nelze odebrat tento vlastní filtr.', + 'Edit custom filter' => 'Upravit vlastní filtr', + 'Your custom filter has been updated successfully.' => 'Vlastní filtr byl úspěšně aktualizován.', + 'Unable to update custom filter.' => 'Nelze aktualizovat vlastní filtr.', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Nová příloha u úkolu #%d: %s', + 'New comment on task #%d' => 'Nový komentář u úkolu #%d', + 'Comment updated on task #%d' => 'Komentář u úkolu #%d byl aktualizován', + 'New subtask on task #%d' => 'Nový dílčí úkol u úkolu #%d', + 'Subtask updated on task #%d' => 'Dílčí úkol u úkolu #%d byl aktualizován', + 'New task #%d: %s' => 'Nový úkol #%d: %s', + 'Task updated #%d' => 'Úkol #%d byl aktualizován', + 'Task #%d closed' => 'Úkol #%d byl uzavřen', + 'Task #%d opened' => 'Úkol #%d byl otevřen', + 'Column changed for task #%d' => 'Sloupec úkolu #%d byl změněn', + 'New position for task #%d' => 'Nová pozice úkolu #%d', + 'Swimlane changed for task #%d' => 'Dráha úkolu #%d byla změněna', + 'Assignee changed on task #%d' => 'Vlastník úkolu #%d byl změněn', + '%d overdue tasks' => '%d úkolů po datu splnění', + 'No notification.' => 'Žádná notifikace.', + 'Mark all as read' => 'Označit vše jako přečtené', + 'Mark as read' => 'Označit jako přečtené', + 'Total number of tasks in this column across all swimlanes' => 'Celkový počet úkolů v tomto sloupci napříč všemi dráhami', + 'Collapse swimlane' => 'Sbalit dráhu', + 'Expand swimlane' => 'Rozbalit dráhu', + 'Add a new filter' => 'Přidat nový filtr', + 'Share with all project members' => 'Sdílet se všemi členy projektu', + 'Shared' => 'Sdíleno', + 'Owner' => 'Vlastník', + 'Unread notifications' => 'Nepřečtené notifikace', + 'Notification methods:' => 'Způsoby notifikací:', + 'Unable to read your file' => 'Nelze přečíst soubor', + '%d task(s) have been imported successfully.' => '%d úkol(y) byly úspěšně importovány.', + 'Nothing has been imported!' => 'Nic nebylo importováno!', + 'Import users from CSV file' => 'Import uživatele ze souboru CSV', + '%d user(s) have been imported successfully.' => '%d uživatel(é) byl úspěšně importován.', + 'Comma' => 'Čárka', + 'Semi-colon' => 'Středník', + 'Tab' => 'Záložka', + 'Vertical bar' => 'Vertikální pruh', + 'Double Quote' => 'Dvojitá nabídka', + 'Single Quote' => 'Jediná nabídka', + '%s attached a file to the task #%d' => '%s připojil soubor k úkolu #%d', + 'There is no column or swimlane activated in your project!' => 'Ve vašem projektu není aktivován žádný sloupec ani dráha!', + 'Append filter (instead of replacement)' => 'Přidat filtr (místo nahrazení)', + 'Append/Replace' => 'Přidat / Nahradit', + 'Append' => 'Přidat', + 'Replace' => 'Nahradit', + 'Import' => 'Import', + 'Change sorting' => 'Změnit třídění', + 'Tasks Importation' => 'Import úkolů', + 'Delimiter' => 'Oddělovač', + 'Enclosure' => 'Příloha', + 'CSV File' => 'Soubor CSV', + 'Instructions' => 'Instrukce', + 'Your file must use the predefined CSV format' => 'Váš soubor musí používat předdefinovaný formát CSV', + 'Your file must be encoded in UTF-8' => 'Soubor musí být zakódován v UTF-8', + 'The first row must be the header' => 'První řádek musí být záhlaví', + 'Duplicates are not verified for you' => 'Duplikáty pro vás nejsou ověřeny', + 'The due date must use the ISO format: YYYY-MM-DD' => 'Datum vypršení musí být ve formátu ISO: RRRR-MM-DD', + 'Download CSV template' => 'Stáhnout šablonu CSV', + 'No external integration registered.' => 'Není registrována žádná externí integrace.', + 'Duplicates are not imported' => 'Duplikáty nejsou importovány', + 'Usernames must be lowercase and unique' => 'Uživatelská jména musí být malá a jedinečná', + 'Passwords will be encrypted if present' => 'Hesla budou šifrována, pokud jsou přítomna', + '%s attached a new file to the task %s' => '%s připojil nový úkol k úkolu %s', + 'Link type' => 'Typ odkazu', + 'Assign automatically a category based on a link' => 'Automaticky přiřadit kategorii na základě odkazu', + 'BAM - Konvertible Mark' => 'BAM - Konvertible Mark', + 'Assignee Username' => 'Uživatelské jméno postupníka', + 'Assignee Name' => 'Jméno postupníka', + 'Groups' => 'Skupiny', + 'Members of %s' => 'Členové %s', + 'New group' => 'Nová skupina', + 'Group created successfully.' => 'Skupina byla úspěšně vytvořena.', + 'Unable to create your group.' => 'Nelze vytvořit vaši skupinu.', + 'Edit group' => 'Upravit skupinu', + 'Group updated successfully.' => 'Skupina byla úspěšně aktualizována.', + 'Unable to update your group.' => 'Skupinu nelze aktualizovat.', + 'Add group member to "%s"' => 'Přidat člena skupiny do "%s"', + 'Group member added successfully.' => 'Člen skupiny byl úspěšně přidán.', + 'Unable to add group member.' => 'Nelze přidat člena skupiny.', + 'Remove user from group "%s"' => 'Odebrat uživatele ze skupiny "%s"', + 'User removed successfully from this group.' => 'Uživatel byl úspěšně odebrán z této skupiny.', + 'Unable to remove this user from the group.' => 'Nelze odebrat tohoto uživatele ze skupiny.', + 'Remove group' => 'Odebrat skupinu', + 'Group removed successfully.' => 'Skupina byla úspěšně odebrána.', + 'Unable to remove this group.' => 'Tuto skupinu nelze odebrat.', + 'Project Permissions' => 'Oprávnění projektu', + 'Manager' => 'Správce', + 'Project Manager' => 'Správce projektu', + 'Project Member' => 'Člen projektu', + 'Project Viewer' => 'Čtenář projektu', + 'Your account is locked for %d minutes' => 'Váš účet je uzamčen na %d minut', + 'Invalid captcha' => 'Neplatná captcha', + 'The name must be unique' => 'Název musí být jedinečný', + 'View all groups' => 'Zobrazit všechny skupiny', + 'There is no user available.' => 'Uživatel není k dispozici.', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Opravdu chcete odstranit uživatele "%s" ze skupiny "%s"?', + 'There is no group.' => 'Neexistuje žádná skupina.', + 'Add group member' => 'Přidat člena skupiny', + 'Do you really want to remove this group: "%s"?' => 'Opravdu chcete tuto skupinu odstranit: "%s"?', + 'There is no user in this group.' => 'V této skupině není žádný uživatel.', + 'Permissions' => 'Oprávnění', + 'Allowed Users' => 'Povolení uživatelé', + 'No specific user has been allowed.' => 'Žádný uživatel nebyl výslovně povolen.', + 'Role' => 'Role', + 'Enter user name...' => 'Zadejte uživatelské jméno...', + 'Allowed Groups' => 'Povolené skupiny', + 'No group has been allowed.' => 'Žádná skupina nebyla povolena.', + 'Group' => 'Skupina', + 'Group Name' => 'Jméno skupiny', + 'Enter group name...' => 'Zadejte název skupiny ...', + 'Role:' => 'Role:', + 'Project members' => 'Členové projektu', + '%s mentioned you in the task #%d' => '%s vás uvedl v úkolu #%d', + '%s mentioned you in a comment on the task #%d' => '%s vás uvedl v komentáři k úkolu #%d', + 'You were mentioned in the task #%d' => 'Byli jste zmíněni v úkolu #%d', + 'You were mentioned in a comment on the task #%d' => 'Byli jste zmíněni v komentáři k úkolu #%d', + 'Estimated hours: ' => 'Odhad hodin: ', + 'Actual hours: ' => 'Aktuální hodiny: ', + 'Hours Spent' => 'Hodin stráveno', + 'Hours Estimated' => 'Hodin odhadováno', + 'Estimated Time' => 'Odhadovaný čas', + 'Actual Time' => 'Aktuální čas', + 'Estimated vs actual time' => 'Rozdíl mezi odhadovaným a aktuálním časem', + 'RUB - Russian Ruble' => 'RUB - ruský rubl', + 'Assign the task to the person who does the action when the column is changed' => 'Úkol přiřadit osobě, která provede akci při změně sloupce', + 'Close a task in a specific column' => 'Ukončete úlohu ve specifickém sloupci', + 'Time-based One-time Password Algorithm' => 'Algoritmus na časově závislé jednorázové heslo', + 'Two-Factor Provider: ' => 'Poskytovatel dvou faktorů: ', + 'Disable two-factor authentication' => 'Zakázat dvoufaktorovou autentizaci', + 'Enable two-factor authentication' => 'Povolit dvoufaktorovou autentizaci', + 'There is no integration registered at the moment.' => 'V tuto chvíli není registrována žádná integrace.', + 'Password Reset for Kanboard' => 'Reset hesla pro Kanboard', + 'Forgot password?' => 'Zapomenuté heslo?', + 'Enable "Forget Password"' => 'Povolit zapomenuté heslo', + 'Password Reset' => 'Resetovat heslo', + 'New password' => 'Nové heslo', + 'Change Password' => 'Změnit heslo', + 'To reset your password click on this link:' => 'Chcete-li heslo obnovit, klikněte na tento odkaz:', + 'Last Password Reset' => 'Poslední reset hesla', + 'The password has never been reinitialized.' => 'Heslo nebylo nikdy znovu inicializováno.', + 'Creation' => 'Vytvoření', + 'Expiration' => 'Vypršení', + 'Password reset history' => 'Historie resetování hesla', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Všechny úkoly ve sloupci "%s" a dráze "%s" byly úspěšně uzavřeny.', + 'Do you really want to close all tasks of this column?' => 'Opravdu chcete zavřít všechny úkoly tohoto sloupce?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d úkol(y) ve sloupci "%s" a dráha "%s" bude uzavřena.', + 'Close all tasks in this column and this swimlane' => 'Uzavřít všechny úkoly v tomto sloupci', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Žádný plugin není registrován na metodu oznamování projektu. Stále můžete konfigurovat jednotlivá oznámení v profilu uživatele.', + 'My dashboard' => 'Moje nástěnka', + 'My profile' => 'Můj profil', + 'Project owner: ' => 'Vlastník projektu: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'Identifikátor projektu je nepovinný a musí být alfanumerický, například: MYPROJECT.', + 'Project owner' => 'Vlastník projektu', + 'Personal projects do not have users and groups management.' => 'Osobní projekty nemají správu uživatelů a skupin', + 'There is no project member.' => 'Projekt nemá žádného člena', + 'Priority' => 'Priorita', + 'Task priority' => 'Priorita úkolu', + 'General' => 'Všeobecné', + 'Dates' => 'Termíny', + 'Default priority' => 'Výchozí priorita', + 'Lowest priority' => 'Nejnižší priorita', + 'Highest priority' => 'Nejvyšší priorita', + 'Close a task when there is no activity' => 'Uzavřít úkol pokud není žádná aktivita', + 'Duration in days' => 'Trvání ve dnech', + 'Send email when there is no activity on a task' => 'Odeslat mail pokud není žádná aktivita u úkolu', + 'Unable to fetch link information.' => 'Nelze načíst informace o odkazu.', + 'Daily background job for tasks' => 'Denní práce na pozadí pro úkoly', + 'Auto' => 'Auto', + 'Related' => 'Související', + 'Attachment' => 'Příloha', + 'Web Link' => 'Webový odkaz', + 'External links' => 'Externí odkazy', + 'Add external link' => 'Přidat externí odkaz', + 'Type' => 'Typ', + 'Dependency' => 'Závislost', + 'Add internal link' => 'Přidat interní odkaz', + 'Add a new external link' => 'Přidat nový externí odkaz', + 'Edit external link' => 'Upravit externí odkaz', + 'External link' => 'Externí odkaz', + 'Copy and paste your link here...' => 'Zde vložte váš odkaz...', + 'URL' => 'URL', + 'Internal links' => 'Interní odkazy', + 'Assign to me' => 'Přiřadit to mně', + 'Me' => 'Mně', + 'Do not duplicate anything' => 'Nekopírovat nic', + 'Projects management' => 'Správa projektů', + 'Users management' => 'Správa uživatelů', + 'Groups management' => 'Správa skupin', + 'Create from another project' => 'Vytvořit z jiného projektu', + 'open' => 'otevřeno', + 'closed' => 'uzavřeno', + 'Priority:' => 'Priorita:', + 'Reference:' => 'Odkaz:', + 'Complexity:' => 'Složitost:', + 'Swimlane:' => 'Dráha:', + 'Column:' => 'Sloupec:', + 'Position:' => 'Pozice:', + 'Creator:' => 'Autor:', + 'Time estimated:' => 'Odhadovaný čas:', + '%s hours' => '%s hodin', + 'Time spent:' => 'Strávený čas:', + 'Created:' => 'Vytvořeno:', + 'Modified:' => 'Změněno:', + 'Completed:' => 'Dokončeno:', + 'Started:' => 'Započato:', + 'Moved:' => 'Přesunuto', + 'Task #%d' => 'Úkol #%d', + 'Time format' => 'Časový formát', + 'Start date: ' => 'Počáteční datum', + 'End date: ' => 'Koncové datum: ', + 'New due date: ' => 'Nové datum splnění: ', + 'Start date changed: ' => 'Počáteční datum změněno: ', + 'Disable personal projects' => 'Zakázat osobní projekty', + 'Do you really want to remove this custom filter: "%s"?' => 'Opravdu chcete odstranit tento vlastní filtr: "%s"?', + 'Remove a custom filter' => 'Odebrat vlastní filtr', + 'User activated successfully.' => 'Uživatel byl úspěšně aktivován.', + 'Unable to enable this user.' => 'Tohoto uživatele nelze aktivovat.', + 'User disabled successfully.' => 'Uživatel byl úspěšně zakázán.', + 'Unable to disable this user.' => 'Tohoto uživatele nelze vypnout.', + 'All files have been uploaded successfully.' => 'Všechny soubory byly úspěšně nahrány.', + 'The maximum allowed file size is %sB.' => 'Maximální povolená velikost souboru je %sB.', + 'Drag and drop your files here' => 'Přetáhnout soubory sem', + 'choose files' => 'zvolit soubory', + 'View profile' => 'Prohlédnout profil', + 'Two Factor' => 'Dva faktory', + 'Disable user' => 'Zakázat uživatele', + 'Do you really want to disable this user: "%s"?' => 'Opravdu chcete tohoto uživatele zakázat: "%s"?', + 'Enable user' => 'Povolit uživatele', + 'Do you really want to enable this user: "%s"?' => 'Opravdu chcete povolit tohoto uživatele: "%s"?', + 'Download' => 'Stáhnout', + 'Uploaded: %s' => 'Nahráno: %s', + 'Size: %s' => 'Velikost: %s', + 'Uploaded by %s' => 'Nahráno uživatelem %s', + 'Filename' => 'Název souboru', + 'Size' => 'Velikost', + 'Column created successfully.' => 'Sloupec byl úspěšně vytvořen.', + 'Another column with the same name exists in the project' => 'V projektu existuje další sloupec se stejným názvem', + 'Default filters' => 'Výchozí filtry', + 'Your board doesn\'t have any columns!' => 'Vaše deska nemá žádné sloupce', + 'Change column position' => 'Změnit polohu sloupce', + 'Switch to the project overview' => 'Přepnout do přehledu projektu', + 'User filters' => 'Uživatelské filtry', + 'Category filters' => 'Filtry kategorie', + 'Upload a file' => 'Nahrát soubor', + 'View file' => 'Prohlédnout soubor', + 'Last activity' => 'Poslední aktivita', + 'Change subtask position' => 'Změnit pozici dílčí úlohy', + 'This value must be greater than %d' => 'Tato hodnota musí být větší než %d', + 'Another swimlane with the same name exists in the project' => 'V projektu existuje další dráha se stejným názvem', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Příklad: https://example.kanboard.org/ (slouží k generování absolutních adres URL)', + 'Actions duplicated successfully.' => 'Akce byly úspěšně duplikovány.', + 'Unable to duplicate actions.' => 'Nelze duplikovat akce.', + 'Add a new action' => 'Přidat novou akci', + 'Import from another project' => 'Import z jiného projektu', + 'There is no action at the moment.' => 'V tuto chvíli neexistuje žádná akce.', + 'Import actions from another project' => 'Import akcí z jiného projektu', + 'There is no available project.' => 'Projekt není k dispozici.', + 'Local File' => 'Místní soubor', + 'Configuration' => 'Konfigurace', + 'PHP version:' => 'Verze PHP:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'Verze operačního systému:', + 'Database version:' => 'Verze databáze:', + 'Browser:' => 'Prohlížeč:', + 'Task view' => 'Zobrazení úkolů', + 'Edit task' => 'Upravit úkol', + 'Edit description' => 'Upravit popis', + 'New internal link' => 'Nový interní odkaz', + 'Display list of keyboard shortcuts' => 'Zobrazí seznam klávesových zkratek', + 'Avatar' => 'Avatar', + 'Upload my avatar image' => 'Nahrát obrázek mého avatara', + 'Remove my image' => 'Odebrat můj obrázek', + 'The OAuth2 state parameter is invalid' => 'Parametr stavu OAuth2 je neplatný', + 'User not found.' => 'Uživatel nenalezen.', + 'Search in activity stream' => 'Vyhledávání v toku činností', + 'My activities' => 'Moje aktivity', + 'Activity until yesterday' => 'Aktivita do včerejška', + 'Activity until today' => 'Aktivita dodnes', + 'Search by creator: ' => 'Vyhledávání podle autora: ', + 'Search by creation date: ' => 'Vyhledávání podle data vytvoření: ', + 'Search by task status: ' => 'Vyhledávání podle stavu úkolu: ', + 'Search by task title: ' => 'Vyhledávání podle názvu úkolu: ', + 'Activity stream search' => 'Vyhledávání aktivity', + 'Projects where "%s" is manager' => 'Projekty, kde "%s" je manažer', + 'Projects where "%s" is member' => 'Projekty, kde "%s" je člen', + 'Open tasks assigned to "%s"' => 'Otevřít úkoly přiřazené k "%s"', + 'Closed tasks assigned to "%s"' => 'Uzavřené úkoly přiřazené k "%s"', + 'Assign automatically a color based on a priority' => 'Automaticky přiřadit barvu podle priority', + 'Overdue tasks for the project(s) "%s"' => 'Zpožděné úkoly pro projekt(y) "%s"', + 'Upload files' => 'Nahrát soubory', + 'Installed Plugins' => 'Instalované moduly', + 'Plugin Directory' => 'Plugin adresář', + 'Plugin installed successfully.' => 'Plugin byl úspěšně nainstalován.', + 'Plugin updated successfully.' => 'Plugin byl úspěšně aktualizován.', + 'Plugin removed successfully.' => 'Plugin byl úspěšně odstraněn.', + 'Subtask converted to task successfully.' => 'Dílčí úkol úspěšně převeden na úkol.', + 'Unable to convert the subtask.' => 'Nelze převést dílčí úkol.', + 'Unable to extract plugin archive.' => 'Nelze extrahovat archiv pluginů.', + 'Plugin not found.' => 'Plugin nebyl nalezen.', + 'You don\'t have the permission to remove this plugin.' => 'Nemáte povolení odstranit tento plugin.', + 'Unable to download plugin archive.' => 'Nelze stáhnout archiv pluginů.', + 'Unable to write temporary file for plugin.' => 'Nelze zapsat dočasný soubor pro plugin.', + 'Unable to open plugin archive.' => 'Nelze otevřít archiv pluginu.', + 'There is no file in the plugin archive.' => 'V archivu pluginů není žádný soubor.', + 'Create tasks in bulk' => 'Vytvořit úkoly hromadně', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Instance Kanboard není nakonfigurována pro instalaci pluginů z uživatelského rozhraní.', + 'There is no plugin available.' => 'Není k dispozici žádný plugin.', + 'Install' => 'Nainstalovat', + 'Update' => 'Aktualizace', + 'Up to date' => 'Aktuální', + 'Not available' => 'Není dostupný', + 'Remove plugin' => 'Odebrat plugin', + 'Do you really want to remove this plugin: "%s"?' => 'Opravdu chcete tento plugin odstranit: "%s"?', + 'Uninstall' => 'Odinstalovat', + 'Listing' => 'Výpis', + 'Metadata' => 'Metadata', + 'Manage projects' => 'Správa projektů', + 'Convert to task' => 'Převést na úkol', + 'Convert sub-task to task' => 'Převést dílčí úkol na úkol', + 'Do you really want to convert this sub-task to a task?' => 'Opravdu chcete převést tento dílčí úkol na úkol?', + 'My task title' => 'Můj název úkolu', + 'Enter one task by line.' => 'Zadejte jeden úkol na řádek.', + 'Number of failed login:' => 'Počet neúspěšných přihlášení:', + 'Account locked until:' => 'Účet uzamčen do:', + 'Email settings' => 'Nastavení e-mailu', + 'Email sender address' => 'Adresa odesílatele e-mailu', + 'Email transport' => 'E-mail transport', + 'Webhook token' => 'Token Webhook', + 'Project tags management' => 'Správa tagů projektu', + 'Tag created successfully.' => 'Značka byla úspěšně vytvořena.', + 'Unable to create this tag.' => 'Tuto značku nelze vytvořit.', + 'Tag updated successfully.' => 'Značka byla úspěšně aktualizována.', + 'Unable to update this tag.' => 'Tuto značku nelze aktualizovat.', + 'Tag removed successfully.' => 'Značka byla úspěšně odebrána.', + 'Unable to remove this tag.' => 'Tuto značku nelze odstranit.', + 'Global tags management' => 'Správa globálních značek', + 'Tags' => 'Štítky', + 'Tags management' => 'Správa štítků', + 'Add new tag' => 'Přidat nový štítek', + 'Edit a tag' => 'Upravit štítek', + 'Project tags' => 'Štítky projektu', + 'There is no specific tag for this project at the moment.' => 'Momentálně není pro tento projekt žádný specifický tag.', + 'Tag' => 'Štítek', + 'Remove a tag' => 'Odebrat značku', + 'Do you really want to remove this tag: "%s"?' => 'Opravdu chcete tuto značku odstranit: "%s"?', + 'Global tags' => 'Globální značky', + 'There is no global tag at the moment.' => 'Momentálně neexistuje globální značka.', + 'This field cannot be empty' => 'Toto pole nemůže být prázdné', + 'Close a task when there is no activity in a specific column' => 'Ukončit úlohu, pokud ve specifickém sloupci neexistuje žádná aktivita', + '%s removed a subtask for the task #%d' => '%s odstranil dílčí úkol pro úkol #%d', + '%s removed a comment on the task #%d' => '%s odstranil komentář k úkolu #%d', + 'Comment removed on task #%d' => 'Komentář byl odebrán na úkolu #%d', + 'Subtask removed on task #%d' => 'Dílčí úkol odstraněn na úkolu #%d', + 'Hide tasks in this column in the dashboard' => 'Skrýt úkoly v tomto sloupci v řídicím panelu', + '%s removed a comment on the task %s' => '%s odstranil komentář k úkolu %s', + '%s removed a subtask for the task %s' => '%s odstranil dílčí úkol pro úkol %s', + 'Comment removed' => 'Komentář byl odebrán', + 'Subtask removed' => 'Odstraněn dílčí úkol', + '%s set a new internal link for the task #%d' => '%s nastavit nový interní odkaz pro úkol #%d', + '%s removed an internal link for the task #%d' => '%s odstranil interní odkaz pro úkol #%d', + 'A new internal link for the task #%d has been defined' => 'Byl definován nový interní odkaz úkolu #%d', + 'Internal link removed for the task #%d' => 'Interní odkaz odstraněn pro úkol #%d', + '%s set a new internal link for the task %s' => '%s nastavit nový interní odkaz pro úkol %s', + '%s removed an internal link for the task %s' => '%s odstranil interní odkaz pro úkol %s', + 'Automatically set the due date on task creation' => 'Automaticky nastaví datum splatnosti úlohy', + 'Move the task to another column when closed' => 'Při zavření přesunout úkol do jiného sloupce', + 'Move the task to another column when not moved during a given period' => 'Přesunout úkol do jiného sloupce, pokud není přesunut během daného období', + 'Dashboard for %s' => 'Ovládací panel pro %s', + 'Tasks overview for %s' => 'Přehled úkolů pro %s', + 'Subtasks overview for %s' => 'Přehled dílčích úkolů pro%s', + 'Projects overview for %s' => 'Přehled projektů pro %s', + 'Activity stream for %s' => 'Proud aktivity pro %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Při přesunu úkolu na konkrétní dráhu přiřaďte barvu', + 'Assign a priority when the task is moved to a specific swimlane' => 'Při přesunu úkolu na konkrétní dráhu přiřaďte prioritu', + 'User unlocked successfully.' => 'Uživatel byl úspěšně odemčen.', + 'Unable to unlock the user.' => 'Nelze uživatele odemknout.', + 'Move a task to another swimlane' => 'Přemístěte úkol na jinou dráhu', + 'Creator Name' => 'Jméno tvůrce', + 'Time spent and estimated' => 'Čas strávený a odhadovaný', + 'Move position' => 'Přesuňte polohu', + 'Move task to another position on the board' => 'Přesuňte úkol na jiné místo na tabuli', + 'Insert before this task' => 'Vložit před tento úkol', + 'Insert after this task' => 'Vložit po tomto úkolu', + 'Unlock this user' => 'Odemknout tohoto uživatele', + 'Custom Project Roles' => 'Role vlastního projektu', + 'Add a new custom role' => 'Přidat novou vlastní roli', + 'Restrictions for the role "%s"' => 'Omezení role "%s"', + 'Add a new project restriction' => 'Přidat nové omezení projektu', + 'Add a new drag and drop restriction' => 'Přidat nové omezení přetažení', + 'Add a new column restriction' => 'Přidat nové omezení sloupce', + 'Edit this role' => 'Upravit tuto roli', + 'Remove this role' => 'Odebrat tuto roli', + 'There is no restriction for this role.' => 'Pro tuto roli neexistuje žádné omezení.', + 'Only moving task between those columns is permitted' => 'Povoleno je pouze přesunutí úkolu mezi těmito sloupci', + 'Close a task in a specific column when not moved during a given period' => 'Pokud není úkol během určitého období přesunut, zavřete úlohu ve specifickém sloupci', + 'Edit columns' => 'Upravit sloupce', + 'The column restriction has been created successfully.' => 'Omezení sloupce bylo úspěšně vytvořeno.', + 'Unable to create this column restriction.' => 'Toto omezení sloupce nelze vytvořit.', + 'Column restriction removed successfully.' => 'Omezení sloupců bylo úspěšně odebráno.', + 'Unable to remove this restriction.' => 'Toto omezení nelze odstranit.', + 'Your custom project role has been created successfully.' => 'Vaše vlastní role projektu byla úspěšně vytvořena.', + 'Unable to create custom project role.' => 'Nelze vytvořit vlastní roli projektu.', + 'Your custom project role has been updated successfully.' => 'Vaše vlastní role projektu byla úspěšně aktualizována.', + 'Unable to update custom project role.' => 'Nelze aktualizovat vlastní roli projektu.', + 'Custom project role removed successfully.' => 'Úkol vlastního projektu byl úspěšně odebrán.', + 'Unable to remove this project role.' => 'Tuto roli projektu nelze odstranit.', + 'The project restriction has been created successfully.' => 'Omezení projektu bylo úspěšně vytvořeno.', + 'Unable to create this project restriction.' => 'Toto omezení projektu nelze vytvořit.', + 'Project restriction removed successfully.' => 'Omezení projektu bylo úspěšně odstraněno.', + 'You cannot create tasks in this column.' => 'V tomto sloupci nelze vytvářet úkoly.', + 'Task creation is permitted for this column' => 'Pro tento sloupec je povoleno vytvoření úkolu', + 'Closing or opening a task is permitted for this column' => 'Pro tento sloupec je povoleno zavření nebo otevření úkolu ', + 'Task creation is blocked for this column' => 'Vytvoření úkolu je pro tento sloupec blokováno', + 'Closing or opening a task is blocked for this column' => 'Uzavření nebo otevření úkolu je pro tento sloupec blokováno', + 'Task creation is not permitted' => 'Vytváření úkolů není povoleno', + 'Closing or opening a task is not permitted' => 'Uzavření nebo otevření úkolu není povoleno', + 'New drag and drop restriction for the role "%s"' => 'Nové omezení přetažení pro roli "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Osoby patřící do této role budou moci přesouvat úkoly pouze mezi zdrojovým a cílovým sloupcem.', + 'Remove a column restriction' => 'Odebrat omezení sloupce', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Opravdu chcete odstranit toto omezení sloupce: "%s" na "%s"?', + 'New column restriction for the role "%s"' => 'Nové omezení sloupce pro roli "%s"', + 'Rule' => 'Pravidlo', + 'Do you really want to remove this column restriction?' => 'Opravdu chcete toto omezení sloupce odstranit?', + 'Custom roles' => 'Vlastní role', + 'New custom project role' => 'Nová role vlastního projektu', + 'Edit custom project role' => 'Upravit roli vlastního projektu', + 'Remove a custom role' => 'Odebrat vlastní roli', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Opravdu chcete tuto vlastní roli odebrat: "%s"? Všichni členové této role se stanou členem projektu.', + 'There is no custom role for this project.' => 'Pro tento projekt neexistuje žádná vlastní role.', + 'New project restriction for the role "%s"' => 'Nové omezení projektu pro roli "%s"', + 'Restriction' => 'Omezení', + 'Remove a project restriction' => 'Odebrat omezení projektu', + 'Do you really want to remove this project restriction: "%s"?' => 'Opravdu chcete toto omezení projektu odstranit: "%s"?', + 'Duplicate to multiple projects' => 'Duplikovat na více projektů', + 'This field is required' => 'Toto pole je povinné', + 'Moving a task is not permitted' => 'Přesunutí úkolu není dovoleno', + 'This value must be in the range %d to %d' => 'Tato hodnota musí být v rozsahu %d až %d', + 'You are not allowed to move this task.' => 'Tento úkol nesmíte přesouvat.', + 'API User Access' => 'API Uživatelský přístup', + 'Preview' => 'Náhled', + 'Write' => 'Napsat', + 'Write your text in Markdown' => 'Napsat svůj text v Markdown', + 'No personal API access token registered.' => 'Nebyl zaregistrován žádný token přístupu pro osobní rozhraní API.', + 'Your personal API access token is "%s"' => 'Osobní token přístupu API je "%s"', + 'Remove your token' => 'Odebrat token', + 'Generate a new token' => 'Vytvořit nový token', + 'Showing %d-%d of %d' => 'Zobrazeno %d-%d z %d', + 'Outgoing Emails' => 'Odchozí e-maily', + 'Add or change currency rate' => 'Přidat nebo změnit měnový kurz', + 'Reference currency: %s' => 'Referenční měna: %s', + 'Add custom filters' => 'Přidat vlastní filtry', + 'Export' => 'Export', + 'Add link label' => 'Přidat popisek na odkaz', + 'Incompatible Plugins' => 'Nekompatibilní zásuvné moduly', + 'Compatibility' => 'Kompatibilita', + 'Permissions and ownership' => 'Oprávnění a vlastnictví', + 'Priorities' => 'Priority', + 'Close this window' => 'Zavřít toto okno', + 'Unable to upload this file.' => 'Tento soubor nelze nahrát.', + 'Import tasks' => 'Import úkolů', + 'Choose a project' => 'Vybrat projekt', + 'Profile' => 'Profil', + 'Application role' => 'Role aplikace', + '%d invitations were sent.' => 'Bylo odesláno %d pozvánek.', + '%d invitation was sent.' => '%d pozvání bylo odesláno.', + 'Unable to create this user.' => 'Tohoto uživatele nelze vytvořit.', + 'Kanboard Invitation' => 'Pozvánka Kanboard', + 'Visible on dashboard' => 'Viditelné na přístrojové desce', + 'Created at:' => 'Vytvořeno:', + 'Updated at:' => 'Aktualizováno dne:', + 'There is no custom filter.' => 'Neexistuje žádný vlastní filtr.', + 'New User' => 'Nový uživatel', + 'Authentication' => 'Ověřování', + 'If checked, this user will use a third-party system for authentication.' => 'Pokud je tato volba zaškrtnuta, bude tento uživatel používat pro ověřování systém třetích stran.', + 'The password is necessary only for local users.' => 'Heslo je nutné pouze pro lokální uživatele.', + 'You have been invited to register on Kanboard.' => 'Byli jste pozváni k registraci na Kanboard.', + 'Click here to join your team' => 'Klikněte sem a připojte se ke svému týmu', + 'Invite people' => 'Pozvat lidi', + 'Emails' => 'E-maily', + 'Enter one email address by line.' => 'Zadejte jednu e-mailovou adresu na řádek.', + 'Add these people to this project' => 'Přidat tyto osoby do tohoto projektu', + 'Add this person to this project' => 'Přidat tuto osobu do tohoto projektu', + 'Sign-up' => 'Přihlásit se', + 'Credentials' => 'Pověření', + 'New user' => 'Nový uživatel', + 'This username is already taken' => 'Toto uživatelské jméno je již přijato', + 'Your profile must have a valid email address.' => 'Váš profil musí mít platnou e-mailovou adresu.', + 'TRL - Turkish Lira' => 'TRL - turecká lira', + 'The project email is optional and could be used by several plugins.' => 'E-mail projektu je nepovinný a může být použit několika pluginy.', + 'The project email must be unique across all projects' => 'E-mail projektu musí být jedinečný ve všech projektech', + 'The email configuration has been disabled by the administrator.' => 'Konfigurace e-mailu byla správcem zakázána.', + 'Close this project' => 'Uzavřít tento projekt', + 'Open this project' => 'Otevřít tento projekt', + 'Close a project' => 'Uzavřít projekt', + 'Do you really want to close this project: "%s"?' => 'Opravdu chcete zavřít tento projekt: "%s"?', + 'Reopen a project' => 'Znovu otevřít projekt', + 'Do you really want to reopen this project: "%s"?' => 'Opravdu chcete tento projekt znovu otevřít: "%s"?', + 'This project is open' => 'Tento projekt je otevřen', + 'This project is closed' => 'Tento projekt je ukončen', + 'Unable to upload files, check the permissions of your data folder.' => 'Nelze nahrát soubory, zkontrolujte oprávnění ke složce dat.', + 'Another category with the same name exists in this project' => 'V tomto projektu existuje další kategorie se stejným názvem', + 'Comment sent by email successfully.' => 'Komentář byl úspěšně odeslán e-mailem.', + 'Sent by email to "%s" (%s)' => 'Odesláno e-mailem na adresu "%s" (%s)', + 'Unable to read uploaded file.' => 'Nelze číst nahraný soubor.', + 'Database uploaded successfully.' => 'Databáze byla úspěšně nahrána.', + 'Task sent by email successfully.' => 'Úkol byl úspěšně odeslán e-mailem.', + 'There is no category in this project.' => 'V tomto projektu není žádná kategorie.', + 'Send by email' => 'Poslat e-mailem', + 'Create and send a comment by email' => 'Vytvořit a odeslat komentář e-mailem', + 'Subject' => 'Předmět', + 'Upload the database' => 'Nahrát databázi', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Můžete nahrát dříve staženou databázi Sqlite (formát Gzip).', + 'Database file' => 'Databázový soubor', + 'Upload' => 'Nahrát', + 'Your project must have at least one active swimlane.' => 'Váš projekt musí mít alespoň jednu aktivní dráhu.', + 'Project: %s' => 'Projekt: %s', + 'Automatic action not found: "%s"' => 'Automatická akce nebyla nalezena: "%s"', + '%d projects' => '%d projektů', + '%d project' => '%d projekt', + 'There is no project.' => 'Neexistuje žádný projekt.', + 'Sort' => 'Seřadit', + 'Project ID' => 'ID projektu', + 'Project name' => 'Název projektu', + 'Public' => 'Veřejnost', + 'Personal' => 'Osobní', + '%d tasks' => '%d úkoly', + '%d task' => '%d úkol', + 'Task ID' => 'ID úkolu', + 'Assign automatically a color when due date is expired' => 'Pokud datum splatnosti vyprší, přiřadit automaticky barvu', + 'Total score in this column across all swimlanes' => 'Celkové skóre v tomto sloupci napříč všemi drahami', + 'HRK - Kuna' => 'HRK - Kuna', + 'ARS - Argentine Peso' => 'ARS - Argentinské Peso', + 'COP - Colombian Peso' => 'COP - Kolumbijské Peso', + '%d groups' => '%d skupin', + '%d group' => '%d skupina', + 'Group ID' => 'ID skupiny', + 'External ID' => 'Externí ID', + '%d users' => '%d uživatelů', + '%d user' => '%d uživatel', + 'Hide subtasks' => 'Skrýt dílčí úkoly', + 'Show subtasks' => 'Zobrazit dílčí úkoly', + 'Authentication Parameters' => 'Parametry ověřování', + 'API Access' => 'API Přístup', + 'No users found.' => 'Nenalezeni žádní uživatelé.', + 'User ID' => 'Uživatelské ID', + 'Notifications are activated' => 'Oznámení jsou aktivována', + 'Notifications are disabled' => 'Oznámení jsou zakázána', + 'User disabled' => 'Uživatel je zakázán', + '%d notifications' => '%d oznámení', + '%d notification' => '%d oznámení', + 'There is no external integration installed.' => 'Není nainstalovaná žádná externí integrace.', + 'You are not allowed to update tasks assigned to someone else.' => 'Není dovoleno aktualizovat úkoly přiřazené někomu jinému.', + 'You are not allowed to change the assignee.' => 'Není dovoleno měnit postupníka.', + 'Task suppression is not permitted' => 'Potlačení úkolů není povoleno', + 'Changing assignee is not permitted' => 'Změna postupníka není povolena', + 'Update only assigned tasks is permitted' => 'Povolené jsou pouze úkoly přiřazené aktualizací', + 'Only for tasks assigned to the current user' => 'Pouze pro úkoly přiřazené aktuálnímu uživateli', + 'My projects' => 'Moje projekty', + 'You are not a member of any project.' => 'Nejste členem žádného projektu.', + 'My subtasks' => 'Moje dílčí úkoly', + '%d subtasks' => '%d dílčí úkoly', + '%d subtask' => '%d dílčí úkol', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Pro úkoly přiřazené aktuálnímu uživateli je povoleno pouze přesunutí úlohy mezi těmito sloupci', + '[DUPLICATE]' => '[KOPIE]', + 'DKK - Danish Krona' => 'DKK - dánská koruna', + 'Remove user from group' => 'Odebrat uživatele ze skupiny', + 'Assign the task to its creator' => 'Přiřadit úkol svému tvůrci', + 'This task was sent by email to "%s" with subject "%s".' => 'Tento úkol byl zaslán e-mailem na adresu "%s" s předmětem "%s".', + 'Predefined Email Subjects' => 'Předdefinované e-mailové objekty', + 'Write one subject by line.' => 'Napsat jeden předmět na řádek.', + 'Create another link' => 'Vytvořit další odkaz', + 'BRL - Brazilian Real' => 'BRL - Brazilský Real', + 'Add a new Kanboard task' => 'Přidat nový úkol Kanboard', + 'Subtask not started' => 'Dílčí úkol nebyl spuštěn', + 'Subtask currently in progress' => 'Probíhající dílčí úkol', + 'Subtask completed' => 'Dílčí úkol byl dokončen', + 'Subtask added successfully.' => 'Dílčí úkol byl úspěšně přidán.', + '%d subtasks added successfully.' => 'Úkoly %d byly úspěšně přidány.', + 'Enter one subtask by line.' => 'Zadejte jeden dílčí úkol na řádek.', + 'Predefined Contents' => 'Předdefinovaný obsah', + 'Predefined contents' => 'Předdefinovaný obsah', + 'Predefined Task Description' => 'Popis předdefinovaného úkolu', + 'Do you really want to remove this template? "%s"' => 'Opravdu chcete tuto šablonu odstranit? "%s"', + 'Add predefined task description' => 'Přidat předdefinovaný popis úkolu', + 'Predefined Task Descriptions' => 'Předdefinované popisy úkolů', + 'Template created successfully.' => 'Šablona byla úspěšně vytvořena.', + 'Unable to create this template.' => 'Tuto šablonu nelze vytvořit.', + 'Template updated successfully.' => 'Šablona byla úspěšně aktualizována.', + 'Unable to update this template.' => 'Tuto šablonu nelze aktualizovat.', + 'Template removed successfully.' => 'Šablona byla úspěšně odebrána.', + 'Unable to remove this template.' => 'Tuto šablonu nelze odebrat.', + 'Template for the task description' => 'Šablona pro popis úkolu', + 'The start date is greater than the end date' => 'Datum zahájení je větší než datum ukončení', + 'Tags must be separated by a comma' => 'Značky musí být odděleny čárkou', + 'Only the task title is required' => 'Je vyžadován pouze název úkolu', + 'Creator Username' => 'Uživatelské jméno tvůrce', + 'Color Name' => 'Název barvy', + 'Column Name' => 'Název sloupce', + 'Swimlane Name' => 'Název dráhy', + 'Time Estimated' => 'Odhadovaný čas', + 'Time Spent' => 'Strávený čas', + 'External Link' => 'Externí odkaz', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Tato funkce umožňuje iCal feed, RSS kanál a zobrazení veřejné rady.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Zastavení časovače všech dílčích úkolů při přesunutí úkolu do jiného sloupce', + 'Subtask Title' => 'Náázev dílčí úkolu', + 'Add a subtask and activate the timer when moving a task to another column' => 'Při přesunu úlohy do jiného sloupce přidat dílčí úkol a aktivovat časovač', + 'days' => 'dnů', + 'minutes' => 'minut', + 'seconds' => 'sekundy', + 'Assign automatically a color when preset start date is reached' => 'Při dosažení přednastaveného počátečního data automaticky přiřadit barvu', + 'Move the task to another column once a predefined start date is reached' => 'Po dosažení předdefinovaného data zahájení přesunout úkol do jiného sloupce', + 'This task is now linked to the task %s with the relation "%s"' => 'Tento úkol je nyní spojen s úkolem %s s relací "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'Odkaz se vztahem "%s" na úkol %s byl odstraněn', + 'Custom Filter:' => 'Vlastní filtr:', + 'Unable to find this group.' => 'Tuto skupinu nelze najít.', + '%s moved the task #%d to the column "%s"' => '%s přesunul úkol #%d do sloupce "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => '%s přesunul úkol #%d do pozice %d ve sloupci "%s"', + '%s moved the task #%d to the swimlane "%s"' => '%s přesunul úkol #%d do dráhy "%s"', + '%sh spent' => '%sh strávil', + '%sh estimated' => '%sh odhadnuto', + 'Select All' => 'Vybrat vše', + 'Unselect All' => 'Odznačit vše', + 'Apply action' => 'Použít akci', + 'Move selected tasks to another column or swimlane' => 'Přesuňte vybrané úkoly do jiného sloupce', + 'Edit tasks in bulk' => 'Úlohy lze upravovat hromadně', + 'Choose the properties that you would like to change for the selected tasks.' => 'Vyberte vlastnosti, které chcete změnit pro vybrané úkoly.', + 'Configure this project' => 'Nakonfigurovat tento projekt', + 'Start now' => 'Začněte hned', + '%s removed a file from the task #%d' => '%s odstranil soubor z úkolu #%d', + 'Attachment removed from task #%d: %s' => 'Příloha byla odstraněna z úlohy #%d: %s', + 'No color' => 'Bez barvy', + 'Attachment removed "%s"' => 'Příloha byla odebrána "%s"', + '%s removed a file from the task %s' => '%s odstranil soubor z úkolu %s', + 'Move the task to another swimlane when assigned to a user' => 'Když je úkol přiřazen uživateli, přesunout úkol na jinou dráhu', + 'Destination swimlane' => 'Cílová dráha', + 'Assign a category when the task is moved to a specific swimlane' => 'Přiřadit kategorii při přesunu úkolu na určitou dráhu', + 'Move the task to another swimlane when the category is changed' => 'Při změně kategorie přesunout úkol na jinou dráhu', + 'Reorder this column by priority (ASC)' => 'Změnit pořadí tohoto sloupce podle priority (vzestupně)', + 'Reorder this column by priority (DESC)' => 'Změnit pořadí tohoto sloupce podle priority (sestupně)', + 'Reorder this column by assignee and priority (ASC)' => 'Změnit pořadí tohoto sloupce podle zadavatele a priority (vzestupně)', + 'Reorder this column by assignee and priority (DESC)' => 'Změnit pořadí tohoto sloupce podle zadavatele a priority (sestupně)', + 'Reorder this column by assignee (A-Z)' => 'Změnit pořadí tohoto sloupce podle zadavatele (A-Z)', + 'Reorder this column by assignee (Z-A)' => 'Změnit pořadí tohoto sloupce podle zadavatele (Z-A)', + 'Reorder this column by due date (ASC)' => 'Změnit pořadí tohoto sloupce podle data vypršení platnosti (vzestupně)', + 'Reorder this column by due date (DESC)' => 'Změnit pořadí tohoto sloupce podle data vypršení platnosti (sestupně)', + 'Reorder this column by id (ASC)' => 'Přerovnat tento sloupec podle ID (vzestupně)', + 'Reorder this column by id (DESC)' => 'Přerovnat tento sloupec podle ID (sestupně)', + '%s moved the task #%d "%s" to the project "%s"' => '%s přesunul úkol #%d "%s" do projektu "%s"', + 'Task #%d "%s" has been moved to the project "%s"' => 'Úkol #%d "%s" byl přesunut do projektu "%s"', + 'Move the task to another column when the due date is less than a certain number of days' => 'Pokud je datum vypršení platnosti menší než určitý počet dní, přesuňte úlohu do jiného sloupce', + 'Automatically update the start date when the task is moved away from a specific column' => 'Automaticky aktualizuje datum zahájení, když je úkol přesunut z určitého sloupce', + 'HTTP Client:' => 'HTTP klient:', + 'Assigned' => 'Přiřazen', + 'Task limits apply to each swimlane individually' => 'Omezení úkolů platí pro každou dráhu samostatně', + 'Column task limits apply to each swimlane individually' => 'Omezení úkolů ve sloupcích platí pro každou dráhu samostatně', + 'Column task limits are applied to each swimlane individually' => 'Omezení úkolů ve sloupcích jsou aplikovány na každou dráhu samostatně', + 'Column task limits are applied across swimlanes' => 'Omezení úkolů ve sloupcích jsou aplikovány napříč drahami', + 'Task limit: ' => 'Limit úkolu:', + 'Change to global tag' => 'Změnit na globální značku', + 'Do you really want to make the tag "%s" global?' => 'Opravdu chcete, aby se značka "%s" stala globální?', + 'Enable global tags for this project' => 'Povolit pro tento projekt globální značky', + 'Group membership(s):' => 'Členství ve skupině:', + '%s is a member of the following group(s): %s' => '%s je členem následující skupiny (skupin): %s', + '%d/%d group(s) shown' => 'zobrazeno(a) %d/%d skupin(a)', + 'Subtask creation or modification' => 'Vytvoření nebo úprava dílčího úkolu', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Přiřadit úkol určitému uživateli při přesunu úkolu na určitou dráhu', + 'Comment' => 'Komentář', + 'Collapse vertically' => 'Rozbalit svisle', + 'Expand vertically' => 'Sbalit svisle', + 'MXN - Mexican Peso' => 'MXN - Mexické peso', + 'Estimated vs actual time per column' => 'Odhadovaný vs. skutečný čas na sloupec', + 'HUF - Hungarian Forint' => 'HUF - Maďarský forint', + 'XBT - Bitcoin' => 'XBT - Bitcoin', + 'You must select a file to upload as your avatar!' => 'Musíte vybrat soubor, který nahrajete jako svého avatara!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'Soubor, který jste nahráli, není platný obrázek! (Povoleny jsou pouze *.gif, *.jpg, *.jpeg a *.png!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Automaticky nastavit datum splatnosti, když je úkol přesunut z konkrétního sloupce', + 'No other projects found.' => 'Nebyly nalezeny žádné další projekty.', + 'Tasks copied successfully.' => 'Úkoly úspěšně zkopírovány.', + 'Unable to copy tasks.' => 'Nelze kopírovat úkoly.', + 'Theme' => 'Téma', + 'Theme:' => 'Téma:', + 'Light theme' => 'Světlé téma', + 'Dark theme' => 'Tmavé téma', + 'Automatic theme - Sync with system' => 'Automatické téma - Synchronizace se systémem', + 'Application managers or more' => 'Správci aplikace a vyšší', + 'Administrators' => 'Administrátoři', + 'Visibility:' => 'Viditelnost:', + 'Standard users' => 'Standardní uživatelé', + 'Visibility is required' => 'Viditelnost je povinná', + 'The visibility should be an app role' => 'Viditelnost by měla být rolí aplikace', + 'Reply' => 'Odpovědět', + '%s wrote: ' => '%s napsal:', + 'Number of visible tasks in this column and swimlane' => 'Počet viditelných úkolů v tomto sloupci a plavecké dráze', + 'Number of tasks in this swimlane' => 'Počet úkolů v této plavecké dráze', + 'Unable to find another subtask in progress, you can close this window.' => 'Nelze najít další probíhající dílčí úkol, můžete zavřít toto okno.', + 'This theme is invalid' => 'Toto téma je neplatné', + 'This role is invalid' => 'Tato role je neplatná', + 'This timezone is invalid' => 'Toto časové pásmo je neplatné', + 'This language is invalid' => 'Tento jazyk je neplatný', + 'This URL is invalid' => 'Tato URL adresa je neplatná', + 'Date format invalid' => 'Neplatný formát data', + 'Time format invalid' => 'Neplatný formát času', + 'Invalid Mail transport' => 'Neplatný přenos pošty', + 'Color invalid' => 'Neplatná barva', + 'This value must be greater or equal to %d' => 'Tato hodnota musí být větší nebo rovna %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Přidat BOM na začátek souboru (vyžadováno pro Microsoft Excel)', + 'Just add these tag(s)' => 'Stačí přidat tyto štítky', + 'Remove internal link(s)' => 'Odebrat interní odkaz(y)', + 'Import tasks from another project' => 'Importovat úkoly z jiného projektu', + 'Select the project to copy tasks from' => 'Vyberte projekt, ze kterého chcete úkoly kopírovat', + 'The total maximum allowed attachments size is %sB.' => 'Celková maximální povolená velikost příloh je %sB.', + 'Add attachments' => 'Přidat přílohy', + 'Task #%d "%s" is overdue' => 'Úkol #%d "%s" je po termínu', + 'Enable notifications by default for all new users' => 'Ve výchozím nastavení povolit oznámení pro všechny nové uživatele', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Přiřadit úkol jeho tvůrci pro vybrané sloupce, pokud není ručně nastaven řešitel', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Přiřadit úkol přihlášenému uživateli při přesunu do zadaného sloupce, pokud není přiřazen žádný uživatel', +]; diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php new file mode 100644 index 0000000..97c154b --- /dev/null +++ b/app/Locale/da_DK/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => '.', + 'None' => 'Ingen', + 'Edit' => 'Redigere', + 'Remove' => 'Fjerne', + 'Yes' => 'Ja', + 'No' => 'Nej', + 'cancel' => 'annullere', + 'or' => 'eller', + 'Yellow' => 'Gul', + 'Blue' => 'Blå', + 'Green' => 'Grøn', + 'Purple' => 'Lilla', + 'Red' => 'Rød', + 'Orange' => 'Orange', + 'Grey' => 'Grå', + 'Brown' => 'Brun', + 'Deep Orange' => 'Dyb orange', + 'Dark Grey' => 'Mørkegrå', + 'Pink' => 'Lyserød', + 'Teal' => 'Blågrøn', + 'Cyan' => 'Turkis', + 'Lime' => 'Citron', + 'Light Green' => 'Lysegrøn', + 'Amber' => 'Rav', + 'Save' => 'Gemme', + 'Login' => 'Logge ind', + 'Official website:' => 'Officielt netsted:', + 'Unassigned' => 'Ikke tildelt', + 'View this task' => 'Vis opgave', + 'Remove user' => 'Fjern bruger', + 'Do you really want to remove this user: "%s"?' => 'Fjern bruger: "%s"?', + 'All users' => 'Alle brugere', + 'Username' => 'Brugernavn', + 'Password' => 'Adgangskode', + 'Administrator' => 'Administrator', + 'Sign in' => 'Logge ind', + 'Users' => 'Brugere', + 'Forbidden' => 'Nægtet', + 'Access Forbidden' => 'Adgang nægtet', + 'Edit user' => 'Redigere bruger', + 'Logout' => 'Logge ud', + 'Bad username or password' => 'Forkert brugernavn eller adgangskode', + 'Edit project' => 'Redigere projekt', + 'Name' => 'Navn', + 'Projects' => 'Projekter', + 'No project' => 'Ingen projekt', + 'Project' => 'Projekt', + 'Status' => 'Status', + 'Tasks' => 'Opgaver', + 'Board' => 'Tavle', + 'Actions' => 'Handlinger', + 'Inactive' => 'Inaktiv', + 'Active' => 'Aktiv', + 'Unable to update this board.' => 'Kan ikke opdatere tavlen.', + 'Disable' => 'Deaktivere', + 'Enable' => 'Aktivere', + 'New project' => 'Nyt projekt', + 'Do you really want to remove this project: "%s"?' => 'Fjern projekt: "%s"?', + 'Remove project' => 'Fjern projekt', + 'Edit the board for "%s"' => 'Redigere tavlen for "%s"', + 'Add a new column' => 'Tilføj ny kolonne', + 'Title' => 'Titel', + 'Assigned to %s' => 'Tildelt %s', + 'Remove a column' => 'Fjern kolonne', + 'Unable to remove this column.' => 'Kan ikke fjerne kolonne.', + 'Do you really want to remove this column: "%s"?' => 'Fjern kolonne: "%s"?', + 'Settings' => 'Opsætning', + 'Application settings' => 'Program opsætning', + 'Language' => 'Sprog', + 'Webhook token:' => 'Net-hægte symbol:', + 'API token:' => 'API symbol:', + 'Database size:' => 'Database størrelse:', + 'Download the database' => 'Hente databasen', + 'Optimize the database' => 'Optimere databasen', + '(VACUUM command)' => '(VACUUM kommando)', + '(Gzip compressed Sqlite file)' => '(Gzip komprimeret SQLite fil)', + 'Close a task' => 'Lukke opgave', + 'Column' => 'Kolonne', + 'Color' => 'Farve', + 'Assignee' => 'Ansvarlig', + 'Create another task' => 'Opret anden opgave', + 'New task' => 'Ny opgave', + 'Open a task' => 'Åbne opgave', + 'Do you really want to open this task: "%s"?' => 'Åbne opgave: "%s"?', + 'Back to the board' => 'Tilbage til tavlen', + 'There is nobody assigned' => 'Ingen tildelt', + 'Column on the board:' => 'Kolonne på tavlen:', + 'Close this task' => 'Lukke opgave', + 'Open this task' => 'Åbne opgave', + 'There is no description.' => 'Ingen beskrivelse.', + 'Add a new task' => 'Tilføj ny opgave', + 'The username is required' => 'Brugernavn påkrævet', + 'The maximum length is %d characters' => 'Maksimale længde er %d tegn', + 'The minimum length is %d characters' => 'Mindste længde er %d tegn', + 'The password is required' => 'Adgangskode påkrævet', + 'This value must be an integer' => 'Værdien skal være et heltal', + 'The username must be unique' => 'Brugernavn skal være unikt', + 'The user id is required' => 'Bruger ID påkrævet', + 'Passwords don\'t match' => 'Adgangskoder stemmer ikke overens', + 'The confirmation is required' => 'Verifikation påkrævet', + 'The project is required' => 'Projekt påkrævet', + 'The id is required' => 'ID påkrævet', + 'The project id is required' => 'Projekt ID påkrævet', + 'The project name is required' => 'Projekt navn påkrævet', + 'The title is required' => 'Titel påkrævet', + 'Settings saved successfully.' => 'Opsætning gemt.', + 'Unable to save your settings.' => 'Kan ikke gemme opsætning.', + 'Database optimization done.' => 'Database optimering færdig.', + 'Your project has been created successfully.' => 'Projekt oprettet.', + 'Unable to create your project.' => 'Kan ikke oprette projekt.', + 'Project updated successfully.' => 'Projekt opdateret.', + 'Unable to update this project.' => 'Kan ikke opdatere projekt.', + 'Unable to remove this project.' => 'Kan ikke fjerne projekt.', + 'Project removed successfully.' => 'Projekt fjernet.', + 'Project activated successfully.' => 'Projekt aktiveret.', + 'Unable to activate this project.' => 'Kan ikke aktivere projekt.', + 'Project disabled successfully.' => 'Projekt deaktiveret.', + 'Unable to disable this project.' => 'Kan ikke deaktivere projekt.', + 'Unable to open this task.' => 'Kan ikke åbne opgave.', + 'Task opened successfully.' => 'Opgave åbnet.', + 'Unable to close this task.' => 'Kan ikke lukke opgave.', + 'Task closed successfully.' => 'Opgave lukket.', + 'Unable to update your task.' => 'Kan ikke opdatere opgave.', + 'Task updated successfully.' => 'Opgave opdateret.', + 'Unable to create your task.' => 'Kan ikke oprette opgave.', + 'Task created successfully.' => 'Opgave oprettet.', + 'User created successfully.' => 'Bruger oprettet.', + 'Unable to create your user.' => 'Kan ikke oprette bruger.', + 'User updated successfully.' => 'Bruger opdateret.', + 'User removed successfully.' => 'Bruger fjernet.', + 'Unable to remove this user.' => 'Kan ikke fjerne bruger.', + 'Board updated successfully.' => 'Tavle opdateret.', + 'Ready' => 'Klar', + 'Backlog' => 'Efterslæb', + 'Work in progress' => 'Igangværende', + 'Done' => 'Færdig', + 'Application version:' => 'Program version:', + 'Id' => 'ID', + 'Public link' => 'Offentlig henvisning', + 'Timezone' => 'Tidszone', + 'Sorry, I didn\'t find this information in my database!' => 'Kan ikke finde information i database!', + 'Page not found' => 'Side ikke fundet', + 'Complexity' => 'Kompleksitet', + 'Task limit' => 'Opgave begrænsning', + 'Task count' => 'Opgave tæller', + 'User' => 'Bruger', + 'Comments' => 'Kommentarer', + 'Comment is required' => 'Kommentar påkrævet', + 'Comment added successfully.' => 'Kommentar tilføjet.', + 'Unable to create your comment.' => 'Kan ikke oprette kommentar.', + 'Due Date' => 'Forfaldsdato', + 'Invalid date' => 'Ugyldig dato', + 'Automatic actions' => 'Automatiske handlinger', + 'Your automatic action has been created successfully.' => 'Automatiske handling oprettet.', + 'Unable to create your automatic action.' => 'Kan ikke oprette automatisk handling.', + 'Remove an action' => 'Fjern handling', + 'Unable to remove this action.' => 'Kan ikke fjerne handling.', + 'Action removed successfully.' => 'Handling fjernet.', + 'Automatic actions for the project "%s"' => 'Automatiske handlinger for projekt "%s"', + 'Add an action' => 'Tilføj handling', + 'Event name' => 'Begivenheds-navn', + 'Action' => 'Handling', + 'Event' => 'Begivenhed', + 'When the selected event occurs execute the corresponding action.' => 'Når valgt begivenhed opstår udføres tilsvarende handling.', + 'Next step' => 'Næste trin', + 'Define action parameters' => 'Bestemme handlings-parametre', + 'Do you really want to remove this action: "%s"?' => 'Fjern handling: "%s"?', + 'Remove an automatic action' => 'Fjern automatisk handling', + 'Assign the task to a specific user' => 'Tildel opgave til bestemt bruger', + 'Assign the task to the person who does the action' => 'Tildel opgave til bruger der udfører handling', + 'Duplicate the task to another project' => 'Kopiere opgave til andet projekt', + 'Move a task to another column' => 'Flyt opgave til anden kolonne', + 'Task modification' => 'Opgave ændring', + 'Task creation' => 'Opgave oprettelse', + 'Closing a task' => 'Lukke opgave', + 'Assign a color to a specific user' => 'Tildel farve til bestemt bruger', + 'Position' => 'Placering', + 'Duplicate to project' => 'Kopiere til projekt', + 'Duplicate' => 'Kopiere', + 'Link' => 'Henvis', + 'Comment updated successfully.' => 'Kommentar opdateret.', + 'Unable to update your comment.' => 'Kan ikke opdatere kommentar.', + 'Remove a comment' => 'Fjern kommentar', + 'Comment removed successfully.' => 'Kommentar fjernet.', + 'Unable to remove this comment.' => 'Kan ikke fjerne kommentar.', + 'Do you really want to remove this comment?' => 'Fjern kommentar?', + 'Current password for the user "%s"' => 'Nuværende adgangskode for bruger "%s"', + 'The current password is required' => 'Nuværende adgangskode påkrævet', + 'Wrong password' => 'Forkert adgangskode', + 'Unknown' => 'Ukendt', + 'Last logins' => 'Seneste log ind', + 'Login date' => 'Log ind dato', + 'Authentication method' => 'Godkendelses-metode', + 'IP address' => 'IP adresse', + 'User agent' => 'Bruger agent', + 'Persistent connections' => 'Vedvarende forbindelser', + 'No session.' => 'Ingen blivende forbindelse.', + 'Expiration date' => 'Udløbsdato', + 'Remember Me' => 'Husk mig', + 'Creation date' => 'Oprettelsesdato', + 'Everybody' => 'Alle', + 'Open' => 'Åben', + 'Closed' => 'Lukket', + 'Search' => 'Søg', + 'Nothing found.' => 'Intet fundet.', + 'Due date' => 'Forfaldsdato', + 'Description' => 'Beskrivelse', + '%d comments' => '%d kommentarer', + '%d comment' => '%d kommentar', + 'Email address invalid' => 'Ugyldig e-post adresse', + 'Your external account is not linked anymore to your profile.' => 'Ekstern konto ikke forbundet til profil.', + 'Unable to unlink your external account.' => 'Kan ikke fjerne henvisning til ekstern konto.', + 'External authentication failed' => 'Ekstern godkendelse mislykkedes', + 'Your external account is linked to your profile successfully.' => 'Ekstern konto forbundet til profil.', + 'Email' => 'E-post', + 'Task removed successfully.' => 'Opgave fjernet.', + 'Unable to remove this task.' => 'Kan ikke fjerne opgave.', + 'Remove a task' => 'Fjern opgave', + 'Do you really want to remove this task: "%s"?' => 'Fjern opgave: "%s"?', + 'Assign automatically a color based on a category' => 'Tildele automatisk farve baseret på kategori', + 'Assign automatically a category based on a color' => 'Tildele automatisk kategori baseret på farve', + 'Task creation or modification' => 'Opgave oprettelse eller ændring', + 'Category' => 'Kategori', + 'Category:' => 'Kategori:', + 'Categories' => 'Kategorier', + 'Your category has been created successfully.' => 'Kategori oprettet.', + 'This category has been updated successfully.' => 'Kategori opdateret.', + 'Unable to update this category.' => 'Kan ikke opdatere kategori.', + 'Remove a category' => 'Fjern kategori', + 'Category removed successfully.' => 'Kategori fjernet.', + 'Unable to remove this category.' => 'Kan ikke fjerne kategori.', + 'Category modification for the project "%s"' => 'Kategori ændring for projekt "%s"', + 'Category Name' => 'Kategori navn', + 'Add a new category' => 'Tilføj ny kategori', + 'Do you really want to remove this category: "%s"?' => 'Fjern kategori: "%s"?', + 'All categories' => 'Alle kategorier', + 'No category' => 'Ingen kategori', + 'The name is required' => 'Navn er påkrævet', + 'Remove a file' => 'Fjern fil', + 'Unable to remove this file.' => 'Kan ikke fjerne fil.', + 'File removed successfully.' => 'Fil fjernet.', + 'Attach a document' => 'Vedhæfte dokument', + 'Do you really want to remove this file: "%s"?' => 'Fjern fil: "%s"?', + 'Attachments' => 'Vedhæftninger', + 'Edit the task' => 'Redigere opgave', + 'Add a comment' => 'Tilføj kommentar', + 'Edit a comment' => 'Redigere kommentar', + 'Summary' => 'Oversigt', + 'Time tracking' => 'Tidsregistrering', + 'Estimate:' => 'Anslå:', + 'Spent:' => 'Brugt:', + 'Do you really want to remove this sub-task?' => 'Fjern delopgave?', + 'Remaining:' => 'Tilbageværende:', + 'hours' => 'timer', + 'estimated' => 'anslået', + 'Sub-Tasks' => 'Delopgaver', + 'Add a sub-task' => 'Tilføj delopgave', + 'Original estimate' => 'Original anslået', + 'Create another sub-task' => 'Opret anden delopgave', + 'Time spent' => 'Tidsforbrug', + 'Edit a sub-task' => 'Redigere delopgave', + 'Remove a sub-task' => 'Fjern delopgave', + 'The time must be a numeric value' => 'Tiden skal være numerisk værdi', + 'Todo' => 'At gøre', + 'In progress' => 'I gang', + 'Sub-task removed successfully.' => 'Delopgave fjernet.', + 'Unable to remove this sub-task.' => 'Kan ikke fjerne delopgave.', + 'Sub-task updated successfully.' => 'Delopgave opdateret.', + 'Unable to update your sub-task.' => 'Kan ikke opdatere delopgave.', + 'Unable to create your sub-task.' => 'Kan ikke oprette delopgave.', + 'Maximum size: ' => 'Maksimum størrelse: ', + 'Display another project' => 'Vis andet projekt', + 'Created by %s' => 'Oprettet af %s', + 'Tasks Export' => 'Opgave eksport', + 'Start Date' => 'Start dato', + 'Execute' => 'Udføre', + 'Task Id' => 'Opgave ID', + 'Creator' => 'Opretter', + 'Modification date' => 'Ændrings-dato', + 'Completion date' => 'Afslutningsdato', + 'Clone' => 'Klone', + 'Project cloned successfully.' => 'Projekt klonet..', + 'Unable to clone this project.' => 'Kan ikke klone projekt.', + 'Enable email notifications' => 'Aktivere e-post påmindelser', + 'Task position:' => 'Opgave placering:', + 'The task #%d has been opened.' => 'Opgave #%d åbnet.', + 'The task #%d has been closed.' => 'Opgave #%d lukket.', + 'Sub-task updated' => 'Delopgave opdateret', + 'Title:' => 'Titel:', + 'Status:' => 'Status:', + 'Assignee:' => 'Ansvarlig:', + 'Time tracking:' => 'Tidsregistrering:', + 'New sub-task' => 'Ny delopgave', + 'New attachment added "%s"' => 'Ny vedhæftning tilføjet "%s"', + 'New comment posted by %s' => 'Ny kommentar af %s', + 'New comment' => 'Ny kommentar', + 'Comment updated' => 'Kommentar opdateret', + 'New subtask' => 'Ny delopgave', + 'I only want to receive notifications for these projects:' => 'Modtage påmindelser for disse projekter:', + 'view the task on Kanboard' => 'vis opgave på Kanboard', + 'Public access' => 'Offentlig adgang', + 'Disable public access' => 'Deaktivere offentlig adgang', + 'Enable public access' => 'Aktivere offentlig adgang', + 'Public access disabled' => 'Offentlig adgang deaktiveret', + 'Move the task to another project' => 'Flyt opgave til andet projekt', + 'Move to project' => 'Flyt til projekt', + 'Do you really want to duplicate this task?' => 'Kopiere opgave?', + 'Duplicate a task' => 'Kopiere opgave', + 'External accounts' => 'Eksterne konti', + 'Account type' => 'Konto type', + 'Local' => 'Lokal', + 'Remote' => 'Fjern', + 'Enabled' => 'Aktiveret', + 'Disabled' => 'Deaktiveret', + 'Login:' => 'Log ind:', + 'Full Name:' => 'Fuldt navn:', + 'Email:' => 'E-post:', + 'Notifications:' => 'Påmindelser:', + 'Notifications' => 'Påmindelser', + 'Account type:' => 'Konto type:', + 'Edit profile' => 'Redigere profil', + 'Change password' => 'Ændre adgangskode', + 'Password modification' => 'Adgangskode ændring', + 'External authentications' => 'Ekstern godkendelse', + 'Never connected.' => 'Aldrig forbundet.', + 'No external authentication enabled.' => 'Ingen ekstern godkendelse aktiveret.', + 'Password modified successfully.' => 'Adgangskode ændret.', + 'Unable to change the password.' => 'Kan ikke ændre adgangskode.', + 'Change category' => 'Ændre kategori', + '%s updated the task %s' => '%s opdatere opgave %s', + '%s opened the task %s' => '%s åbne opgave %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s flytte opgave %s til placering #%d i kolonne "%s"', + '%s moved the task %s to the column "%s"' => '%s flyttet opgave %s til kolonne "%s"', + '%s created the task %s' => '%s oprettet opgave %s', + '%s closed the task %s' => '%s lukket opgave %s', + '%s created a subtask for the task %s' => '%s oprettet delopgave for opgave %s', + '%s updated a subtask for the task %s' => '%s opdateret delopgave for opgave %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Tildelt %s med anslåning på %s/%st', + 'Not assigned, estimate of %sh' => 'Ikke tildelt, anslået til %st', + '%s updated a comment on the task %s' => '%s opdateret kommentar på opgave %s', + '%s commented the task %s' => '%s har kommenteret opgave %s', + '%s\'s activity' => '%s\'s aktivitet', + 'RSS feed' => 'RSS nyheds-kilde', + '%s updated a comment on the task #%d' => '%s opdaterede kommentar på opgave #%d', + '%s commented on the task #%d' => '%s kommenterede på opgave #%d', + '%s updated a subtask for the task #%d' => '%s opdaterede delopgave for opgave #%d', + '%s created a subtask for the task #%d' => '%s oprettede delopgave for opgave #%d', + '%s updated the task #%d' => '%s opdaterede opgave #%d', + '%s created the task #%d' => '%s oprettede opgave #%d', + '%s closed the task #%d' => '%s lukkede opgave #%d', + '%s opened the task #%d' => '%s åbnede opgave #%d', + 'Activity' => 'Aktivitet', + 'Default values are "%s"' => 'Standard værdier er "%s"', + 'Default columns for new projects (Comma-separated)' => 'Standard kolonne for nye projekter (kommasepareret)', + 'Task assignee change' => 'Ændre opgave ansvarlig', + '%s changed the assignee of the task #%d to %s' => '%s ændre ansvarlig for opgave #%d til %s', + '%s changed the assignee of the task %s to %s' => '%s ændre ansvarlig for opgave %s til %s', + 'New password for the user "%s"' => 'Ny adgangskode til bruger "%s"', + 'Choose an event' => 'Vælge hændelse', + 'Create a task from an external provider' => 'Opret opgave fra ekstern udbyder', + 'Change the assignee based on an external username' => 'Ændre tildelt baseret på eksternt brugernavn', + 'Change the category based on an external label' => 'Ændre kategori baseret på ekstern etiket', + 'Reference' => 'Henvisning', + 'Label' => 'Etiket', + 'Database' => 'Database', + 'About' => 'Om', + 'Database driver:' => 'Database drivprogram:', + 'Board settings' => 'Tavle indstillinger', + 'Webhook settings' => 'Net-hægte indstillinger', + 'Reset token' => 'Nulstille symbol', + 'API endpoint:' => 'API slutpunkt:', + 'Refresh interval for personal board' => 'Opdaterings-frekvens for privat tavle', + 'Refresh interval for public board' => 'Opdaterings-frekvens for offentlig tavle', + 'Task highlight period' => 'Fremhæve opgave periode', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Periode (sekunder) for at betragte opgave nylig ændret (0 deaktiverer, standard 2 dage)', + 'Frequency in second (60 seconds by default)' => 'Frekvens i sekunder (standard 60 sekunder)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frekvens i sekunder (0 deaktiverer funktion, standard 10 sekunder)', + 'Application URL' => 'Program URL', + 'Token regenerated.' => 'Symbol gendannet.', + 'Date format' => 'Dato format', + 'ISO format is always accepted, example: "%s" and "%s"' => 'ISO format accepteres altid, eksempelvis: "%s" og "%s"', + 'New personal project' => 'Nyt privat projekt', + 'This project is personal' => 'Privat projekt', + 'Add' => 'Tilføje', + 'Start date' => 'Start dato', + 'Time estimated' => 'Tid anslået', + 'There is nothing assigned to you.' => 'Intet tildelt.', + 'My tasks' => 'Tildelte opgaver', + 'Activity stream' => 'Aktivitet strøm', + 'Dashboard' => 'Instrumentbræt', + 'Confirmation' => 'Bekræftelse', + 'Webhooks' => 'Net-hægter', + 'API' => 'API', + 'Create a comment from an external provider' => 'Opret kommentar fra ekstern udbyder', + 'Project management' => 'Projekt styring', + 'Columns' => 'Kolonner', + 'Task' => 'Opgave', + 'Percentage' => 'Procent', + 'Number of tasks' => 'Antal opgaver', + 'Task distribution' => 'Opgave fordeling', + 'Analytics' => 'Analyse', + 'Subtask' => 'Delopgave', + 'User repartition' => 'Bruger fordeling', + 'Clone this project' => 'Kopiere projekt', + 'Column removed successfully.' => 'Kolonne fjernet.', + 'Not enough data to show the graph.' => 'Ikke nok data for at vise graf.', + 'Previous' => 'Forrige', + 'The id must be an integer' => 'ID\'et skal være heltal', + 'The project id must be an integer' => 'Projekt ID skal være heltal', + 'The status must be an integer' => 'Status skal være heltal', + 'The subtask id is required' => 'Delopgave ID påkrævet', + 'The subtask id must be an integer' => 'Delopgave ID skal være heltal', + 'The task id is required' => 'Opgave ID påkrævet', + 'The task id must be an integer' => 'Opgave ID skal være heltal', + 'The user id must be an integer' => 'Bruger ID skal være heltal', + 'This value is required' => 'Værdi påkrævet', + 'This value must be numeric' => 'Værdi skal være numerisk', + 'Unable to create this task.' => 'Kan ikke oprette opgave.', + 'Cumulative flow diagram' => 'Akkumuleret rutediagram', + 'Daily project summary' => 'Daglig projektoversigt', + 'Daily project summary export' => 'Daglig projektoversigt eksport', + 'Exports' => 'Eksportere', + 'This export contains the number of tasks per column grouped per day.' => 'Eksport indeholder antal opgaver pr. kolonne grupperet pr. dag.', + 'Active swimlanes' => 'Aktive spor', + 'Add a new swimlane' => 'Tilføj nyt spor', + 'Default swimlane' => 'Standard spor', + 'Do you really want to remove this swimlane: "%s"?' => 'Fjern spor: "%s"?', + 'Inactive swimlanes' => 'Inaktive spor', + 'Remove a swimlane' => 'Fjern spor', + 'Swimlane modification for the project "%s"' => 'Ændre spor for projekt "%s"', + 'Swimlane removed successfully.' => 'Spor fjernet.', + 'Swimlanes' => 'Spor', + 'Swimlane updated successfully.' => 'Spor opdateret.', + 'Unable to remove this swimlane.' => 'Kan ikke fjerne spor.', + 'Unable to update this swimlane.' => 'Kan ikke opdatere spor.', + 'Your swimlane has been created successfully.' => 'Spor oprettet.', + 'Example: "Bug, Feature Request, Improvement"' => 'Eksempel: "fejl, egenskab forespørgsel, forbedring"', + 'Default categories for new projects (Comma-separated)' => 'Standard kategorier for nye projekter (komma separeret)', + 'Integrations' => 'Integrationer', + 'Integration with third-party services' => 'Integration med 3. part tjenester', + 'Subtask Id' => 'Delopgave ID', + 'Subtasks' => 'Delopgaver', + 'Subtasks Export' => 'Delopgave eksport', + 'Task Title' => 'Opgave titel', + 'Untitled' => 'Intet navn', + 'Application default' => 'Program standard', + 'Language:' => 'Sprog:', + 'Timezone:' => 'Tidszone:', + 'All columns' => 'Alle kolonner', + 'Next' => 'Næste', + '#%d' => '#%d', + 'All swimlanes' => 'Alle spor', + 'All colors' => 'Alle farver', + 'Moved to column %s' => 'Flyttet til kolonne %s', + 'User dashboard' => 'Bruger instrumentbræt', + 'Allow only one subtask in progress at the same time for a user' => 'Tillad én aktiv delopgave i gang på samme tid for bruger', + 'Edit column "%s"' => 'Redigere kolonne "%s"', + 'Select the new status of the subtask: "%s"' => 'Vælge ny status for delopgave: "%s"', + 'Subtask timesheet' => 'Delopgave tidsregistrering', + 'There is nothing to show.' => 'Ikke noget at vise.', + 'Time Tracking' => 'Tidsporing', + 'You already have one subtask in progress' => 'En delopgave er i gang', + 'Which parts of the project do you want to duplicate?' => 'Hvilke dele af projekt skal kopieres?', + 'Disallow login form' => 'Afvis log ind formular', + 'Start' => 'Start', + 'End' => 'Slut', + 'Task age in days' => 'Opgave tid i dage', + 'Days in this column' => 'Dage i kolonne', + '%dd' => '%dd', + 'Add a new link' => 'Tilføj ny henvisning', + 'Do you really want to remove this link: "%s"?' => 'Fjern henvisning: "%s"?', + 'Do you really want to remove this link with task #%d?' => 'Fjern henvisning med opgave #%d?', + 'Field required' => 'Felt påkrævet', + 'Link added successfully.' => 'Henvisning tilføjet.', + 'Link updated successfully.' => 'Henvisning opdateret.', + 'Link removed successfully.' => 'Henvisning fjernet.', + 'Link labels' => 'Henvisning etiketter', + 'Link modification' => 'Henvisning ændring', + 'Opposite label' => 'Modsatte etiket', + 'Remove a link' => 'Fjern henvisning', + 'The labels must be different' => 'Etiketter skal være forskellige', + 'There is no link.' => 'Ingen henvisning.', + 'This label must be unique' => 'Etiket skal være unik', + 'Unable to create your link.' => 'Kan ikke oprette henvisning.', + 'Unable to update your link.' => 'Kan ikke opdatere henvisning.', + 'Unable to remove this link.' => 'Kan ikke fjerne henvisning.', + 'relates to' => 'vedrører', + 'blocks' => 'blokerer', + 'is blocked by' => 'er blokeret af', + 'duplicates' => 'dubletter', + 'is duplicated by' => 'er kopieret af', + 'is a child of' => 'er barn af', + 'is a parent of' => 'er forælder til', + 'targets milestone' => 'mål milepæl', + 'is a milestone of' => 'er milepæl af', + 'fixes' => 'rettelser', + 'is fixed by' => 'er rettet af', + 'This task' => 'Opgave', + '<1h' => '<1t', + '%dh' => '%dt', + 'Expand tasks' => 'Udfolde opgaver', + 'Collapse tasks' => 'Sammenfolde opgaver', + 'Expand/collapse tasks' => 'Udfolde/sammenfolde opgaver', + 'Close dialog box' => 'Lukke dialogboks', + 'Submit a form' => 'Indsende formular', + 'Board view' => 'Tavle visning', + 'Keyboard shortcuts' => 'Tastaturgenveje', + 'Open board switcher' => 'Åbne tavle skifter', + 'Application' => 'Program', + 'Compact view' => 'Kompakt visning', + 'Horizontal scrolling' => 'Vandret rulning', + 'Compact/wide view' => 'Kompakt/bred visning', + 'Currency' => 'Valuta', + 'Personal project' => 'Privat projekt', + 'AUD - Australian Dollar' => 'AUD - australsk dollar', + 'CAD - Canadian Dollar' => 'CAD - canadisk dollar', + 'CHF - Swiss Francs' => 'CHF - schweizisk franc', + 'Custom Stylesheet' => 'Brugertilpasset stilark', + 'EUR - Euro' => 'EUR - euro', + 'GBP - British Pound' => 'GBP - britisk pund', + 'INR - Indian Rupee' => 'INR - indisk rupee', + 'JPY - Japanese Yen' => 'JPY - japansk yen', + 'NZD - New Zealand Dollar' => 'NZD - new zealandsk dollar', + 'PEN - Peruvian Sol' => 'PEN – peruviansk sol', + 'RSD - Serbian dinar' => 'RSD - serbisk dinar', + 'CNY - Chinese Yuan' => 'CNY - kinesisk yuan', + 'USD - US Dollar' => 'USD - amerikansk dollar', + 'VES - Venezuelan Bolívar' => 'VES – venezuelansk bolívar', + 'Destination column' => 'Mål kolonne', + 'Move the task to another column when assigned to a user' => 'Flyt opgave til anden kolonne når den er tildelt bruger', + 'Move the task to another column when assignee is cleared' => 'Flyt opgave til anden kolonne når ansvarlig er ryddet', + 'Source column' => 'Kilde kolonne', + 'Transitions' => 'Overgange', + 'Executer' => 'Udfører', + 'Time spent in the column' => 'Tid brugt i kolonne', + 'Task transitions' => 'Opgave overgange', + 'Task transitions export' => 'Opgave overgange eksport', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Rapport indeholder alle kolonne bevægelser for hver opgave med dato, bruger og tid der bruges til hver overgang.', + 'Currency rates' => 'Valutakurser', + 'Rate' => 'Kurs', + 'Change reference currency' => 'Ændre referencevaluta', + 'Reference currency' => 'Referencevaluta', + 'The currency rate has been added successfully.' => 'Valutakursen tilføjet.', + 'Unable to add this currency rate.' => 'Kan ikke tilføje valutakurs.', + 'Webhook URL' => 'Net-hægte URL', + '%s removed the assignee of the task %s' => '%s fjernede ansvarlig for opgave %s', + 'Information' => 'Information', + 'Check two factor authentication code' => 'Kontrollere to-faktor godkendelses-kode', + 'The two factor authentication code is not valid.' => 'To-faktor godkendelses-kode ugyldig.', + 'The two factor authentication code is valid.' => 'To-faktor godkendelses-koden gyldig.', + 'Code' => 'Kode', + 'Two factor authentication' => 'To-faktor godkendelse', + 'This QR code contains the key URI: ' => 'QR kode indeholder nøgle URI: ', + 'Check my code' => 'Kontrollere kode', + 'Secret key: ' => 'Hemmelig nøgle: ', + 'Test your device' => 'Test enhed', + 'Assign a color when the task is moved to a specific column' => 'Tildele farve når opgave flyttes til bestemt kolonne', + '%s via Kanboard' => '%s via Kanboard', + 'Burndown chart' => 'Burn-down diagram', + 'This chart show the task complexity over the time (Work Remaining).' => 'Diagram viser opgavekompleksitet over tid (resterende arbejde).', + 'Screenshot taken %s' => 'Skærmbillede taget %s', + 'Add a screenshot' => 'Tilføj skærmbillede', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Tage skærmbillede og tryk på CTRL + V eller ⌘ + V for at indsætte her.', + 'Screenshot uploaded successfully.' => 'Skærmbillede overført.', + 'SEK - Swedish Krona' => 'SEK - svensk krone', + 'Identifier' => 'ID', + 'Disable two factor authentication' => 'Deaktivere to-faktor godkendelse', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Deaktivere to-faktor godkendelse for bruger: "%s"?', + 'Edit link' => 'Redigere henvisning', + 'Start to type task title...' => 'Start med at skrive opgave titel...', + 'A task cannot be linked to itself' => 'En opgave kan ikke henvise til sig selv', + 'The exact same link already exists' => 'Henvisning eksisterer allerede', + 'Recurrent task is scheduled to be generated' => 'Gentagende opgave planlagt at blive oprettet', + 'Score' => 'Bedømmelse', + 'The identifier must be unique' => 'ID skal være unik', + 'This linked task id doesn\'t exists' => 'Opgave ID henvisning eksisterer ikke', + 'This value must be alphanumeric' => 'Værdi skal være alfanumerisk', + 'Edit recurrence' => 'Redigere gentagelse', + 'Generate recurrent task' => 'Generere gentagende opgave', + 'Trigger to generate recurrent task' => 'Udløser for at generere gentagende opgave', + 'Factor to calculate new due date' => 'Faktor til beregning af ny forfaldsdato', + 'Timeframe to calculate new due date' => 'Tidsramme til beregning af ny forfaldsdato', + 'Base date to calculate new due date' => 'Grund dato til beregning af ny forfaldsdato', + 'Action date' => 'Handlings-dato', + 'Base date to calculate new due date: ' => 'Grund dato til beregning af ny forfaldsdato: ', + 'This task has created this child task: ' => 'Opgave har oprettet barne opgave: ', + 'Day(s)' => 'Dag(e)', + 'Existing due date' => 'Eksisterende forfaldsdato', + 'Factor to calculate new due date: ' => 'Faktor til beregning af ny forfaldsdato: ', + 'Month(s)' => 'Måned(er)', + 'This task has been created by: ' => 'Opgave oprettet af: ', + 'Recurrent task has been generated:' => 'Gentagende opgave oprettet:', + 'Timeframe to calculate new due date: ' => 'Tidsramme til beregning af ny forfaldsdato: ', + 'Trigger to generate recurrent task: ' => 'Udløser for at generere gentagende opgave: ', + 'When task is closed' => 'Når opgave er lukket', + 'When task is moved from first column' => 'Når opgave flyttes fra første kolonne', + 'When task is moved to last column' => 'Når opgave flyttes til sidste kolonne', + 'Year(s)' => 'År', + 'Project settings' => 'Projekt indstillinger', + 'Automatically update the start date' => 'Opdatere automatisk startdato', + 'iCal feed' => 'iCal nyheds-kilde', + 'Preferences' => 'Indstillinger', + 'Security' => 'Sikkerhed', + 'Two factor authentication disabled' => 'To-faktor godkendelse deaktiveret', + 'Two factor authentication enabled' => 'To-faktor godkendelse aktiveret', + 'Unable to update this user.' => 'Kan ikke opdatere bruger.', + 'There is no user management for personal projects.' => 'Ingen bruger styring til private projekter.', + 'User that will receive the email' => 'Bruger der modtager e-post', + 'Email subject' => 'E-post emne', + 'Date' => 'Dato', + 'Add a comment log when moving the task between columns' => 'Tilføj kommentar log når opgave flyttes mellem kolonner', + 'Move the task to another column when the category is changed' => 'Flyt opgave til anden kolonne når kategori ændres', + 'Send a task by email to someone' => 'Sende opgave som e-post til nogen', + 'Reopen a task' => 'Genåbne opgave', + 'Notification' => 'Påmindelse', + '%s moved the task #%d to the first swimlane' => '%s flyttet opgave #%d til første spor', + 'Swimlane' => 'Spor', + '%s moved the task %s to the first swimlane' => '%s flyttet opgave %s til første spor', + '%s moved the task %s to the swimlane "%s"' => '%s flyttet opgave %s til spor "%s"', + 'This report contains all subtasks information for the given date range.' => 'Rapport indeholder alle delopgave oplysninger for dato periode.', + 'This report contains all tasks information for the given date range.' => 'Rapport indeholder alle opgave oplysninger for dato periode.', + 'Project activities for %s' => 'Projekt aktiviteter for %s', + 'view the board on Kanboard' => 'vis tavle på Kanboard', + 'The task has been moved to the first swimlane' => 'Opgave flyttet til første spor', + 'The task has been moved to another swimlane:' => 'Opgave flyttet til andet spor:', + 'New title: %s' => 'Ny titel: %s', + 'The task is not assigned anymore' => 'Opgave ikke tildelt længere', + 'New assignee: %s' => 'Ny ansvarlig: %s', + 'There is no category now' => 'Ingen kategori nu', + 'New category: %s' => 'Ny kategori: %s', + 'New color: %s' => 'Ny farve: %s', + 'New complexity: %d' => 'Ny kompleksitet: %d', + 'The due date has been removed' => 'Forfaldsdato fjernet', + 'There is no description anymore' => 'Ingen beskrivelse mere', + 'Recurrence settings has been modified' => 'Gentagelse indstillinger ændret', + 'Time spent changed: %sh' => 'Tid forbrugt ændret: %st', + 'Time estimated changed: %sh' => 'Tid anslået ændret: %st', + 'The field "%s" has been updated' => 'Felt "%s" opdateret', + 'The description has been modified:' => 'Beskrivelse ændret:', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Lukke opgave "%s" og alle delopgaver?', + 'I want to receive notifications for:' => 'Bruger modtager underretninger for:', + 'All tasks' => 'Alle opgaver', + 'Only for tasks assigned to me' => 'Kun tildelte opgaver', + 'Only for tasks created by me' => 'Kun opgaver oprettet af bruger', + 'Only for tasks created by me and tasks assigned to me' => 'Kun opgaver oprettet og tildelt bruger', + '%%Y-%%m-%%d' => '%%Y-%%m-%%d', + 'Total for all columns' => 'Total for alle kolonner', + 'You need at least 2 days of data to show the chart.' => 'Der skal mindst 2 dages data for at vise diagram.', + '<15m' => '<15m', + '<30m' => '<30m', + 'Stop timer' => 'Stop stopur', + 'Start timer' => 'Start stopur', + 'My activity stream' => 'Aktivitet strøm', + 'Search tasks' => 'Søg opgaver', + 'Reset filters' => 'Nulstille filtre', + 'My tasks due tomorrow' => 'Tildelte opgaver i morgen', + 'Tasks due today' => 'Opgaver i dag', + 'Tasks due tomorrow' => 'Opgaver i morgen', + 'Tasks due yesterday' => 'Opgaver i går', + 'Closed tasks' => 'Lukket opgaver', + 'Open tasks' => 'Åbne opgaver', + 'Not assigned' => 'Ikke tildelt', + 'View advanced search syntax' => 'Vis avanceret søge syntaks', + 'Overview' => 'Oversigt', + 'Board/Calendar/List view' => 'Tavle / kalender / liste visning', + 'Switch to the board view' => 'Skift til tavle visning', + 'Switch to the list view' => 'Skift til liste visning', + 'Go to the search/filter box' => 'Gå til søg- / filter-boks', + 'There is no activity yet.' => 'Endnu ingen aktivitet.', + 'No tasks found.' => 'Ingen opgave fundet.', + 'Keyboard shortcut: "%s"' => 'Tastaturgenvej: ”%s”', + 'List' => 'Liste', + 'Filter' => 'Filter', + 'Advanced search' => 'Avanceret søgning', + 'Example of query: ' => 'Forespørgsel eksempel: ', + 'Search by project: ' => 'Søg efter projekt: ', + 'Search by column: ' => 'Søg efter kolonne: ', + 'Search by assignee: ' => 'Søg ved ansvarlig: ', + 'Search by color: ' => 'Søg efter farve: ', + 'Search by category: ' => 'Søg efter kategori: ', + 'Search by description: ' => 'Søg efter beskrivelse: ', + 'Search by due date: ' => 'Søg efter forfaldsdato: ', + 'Average time spent in each column' => 'Gennemsnitlig tid forbrugt i hver kolonne', + 'Average time spent' => 'Gennemsnitlig tid forbrugt', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Diagram viser gennemsnitlig tid i hver kolonne for de seneste %d opgaver.', + 'Average Lead and Cycle time' => 'Gennemsnitlig hoved- og gennemløbstid', + 'Average lead time: ' => 'Gennemsnitlig hoved tid: ', + 'Average cycle time: ' => 'Gennemsnitlig gennemløbstid: ', + 'Cycle Time' => 'Gennemløbstid', + 'Lead Time' => 'Hoved tid', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Diagram viser gennemsnitlig hoved- og gennemløbstid for de seneste %d opgaver over tid.', + 'Average time into each column' => 'Gennemsnitlig tid i hver kolonne', + 'Lead and cycle time' => 'Hoved- og gennemløbstid', + 'Lead time: ' => 'Hoved tid: ', + 'Cycle time: ' => 'Gennemløbstid: ', + 'Time spent in each column' => 'Tid forbrugt i hver kolonne', + 'The lead time is the duration between the task creation and the completion.' => 'Hoved tid er varighed mellem opgaveoprettelse og færdiggørelse.', + 'The cycle time is the duration between the start date and the completion.' => 'Gennemløbstid er varighed mellem startdato og færdiggørelse.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Hvis opgave ikke er lukket bruges nuværende tid i stedet for færdiggørelses-dato.', + 'Set the start date automatically' => 'Angive automatisk startdato', + 'Edit Authentication' => 'Redigere godkendelse', + 'Remote user' => 'Fjern bruger', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Fjern brugere gemmer ikke adgangskode i Kanboard databasen; eksempler: LDAP, Google og Github konti.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Ved at markere afkrydsnings-felt "Tillad ikke log ind formular" ignoreres legitimations-oplysninger, der er indtastet i log ind formular.', + 'Default task color' => 'Standard opgave farve', + 'This feature does not work with all browsers.' => 'Funktion virker ikke med alle netlæsere.', + 'There is no destination project available.' => 'Ingen mål projekt tilgængelig.', + 'Trigger automatically subtask time tracking' => 'Udløser automatisk delopgave tidsporing', + 'Include closed tasks in the cumulative flow diagram' => 'Inkludere lukkede opgaver i kumulativ rutediagram', + 'Current swimlane: %s' => 'Nuværende spor: %s', + 'Current column: %s' => 'Nuværende kolonne: %s', + 'Current category: %s' => 'Nuværende kategori: %s', + 'no category' => 'ingen kategori', + 'Current assignee: %s' => 'Nuværende ansvarlig: %s', + 'not assigned' => 'ikke tildelt', + 'Author:' => 'Forfatter:', + 'contributors' => 'bidragsydere', + 'License:' => 'Licens:', + 'License' => 'Licens', + 'Enter the text below' => 'Indtast tekst nedenfor', + 'Start date:' => 'Start dato:', + 'Due date:' => 'Forfaldsdato:', + 'People who are project managers' => 'Personer der er projektledere', + 'People who are project members' => 'Personer der er projektmedlemmer', + 'NOK - Norwegian Krone' => 'NOK - norsk krone', + 'Show this column' => 'Vis kolonne', + 'Hide this column' => 'Skjul kolonne', + 'End date' => 'Slut dato', + 'Users overview' => 'Bruger oversigt', + 'Members' => 'Medlemmer', + 'Shared project' => 'Delt projekt', + 'Project managers' => 'Projektledere', + 'Projects list' => 'Projekt liste', + 'End date:' => 'Slut dato:', + 'Change task color when using a specific task link' => 'Ændre opgave farve når der bruges bestemt opgave henvisning', + 'Task link creation or modification' => 'Opgave henvisning oprettelse eller ændring', + 'Milestone' => 'Milepæl', + 'Reset the search/filter box' => 'Nulstil søg- / filter-boks', + 'Documentation' => 'Dokumentation', + 'Author' => 'Forfatter', + 'Version' => 'Version', + 'Plugins' => 'Udvidelses-moduler', + 'There is no plugin loaded.' => 'Ingen udvidelses-modul indlæst.', + 'My notifications' => 'Tildelte påmindelser', + 'Custom filters' => 'Brugertilpasset filtre', + 'Your custom filter has been created successfully.' => 'Brugertilpasset filter oprettet.', + 'Unable to create your custom filter.' => 'Kan ikke oprette brugertilpasset filter.', + 'Custom filter removed successfully.' => 'Brugertilpasset filter fjernet.', + 'Unable to remove this custom filter.' => 'Kan ikke fjerne brugertilpasset filter.', + 'Edit custom filter' => 'Redigere brugertilpasset filter', + 'Your custom filter has been updated successfully.' => 'Brugertilpasset filter opdateret.', + 'Unable to update custom filter.' => 'Kan ikke opdatere brugertilpasset filter.', + 'Web' => 'Net', + 'New attachment on task #%d: %s' => 'Ny vedhæftet fil til opgave #%d: %s', + 'New comment on task #%d' => 'Ny kommentar til opgave #%d', + 'Comment updated on task #%d' => 'Kommentar opdateret til opgave #%d', + 'New subtask on task #%d' => 'Ny delopgave til opgave #%d', + 'Subtask updated on task #%d' => 'Delopgave opdateret til opgave #%d', + 'New task #%d: %s' => 'Ny opgave #%d: %s', + 'Task updated #%d' => 'Opgave opdateret #%d', + 'Task #%d closed' => 'Opgave #%d lukket', + 'Task #%d opened' => 'Opgave #%d åbnet', + 'Column changed for task #%d' => 'Kolonne ændret for opgave #%d', + 'New position for task #%d' => 'Ny placering for opgave #%d', + 'Swimlane changed for task #%d' => 'Spor ændret for opgave #%d', + 'Assignee changed on task #%d' => 'Tildelte ændret til opgave #%d', + '%d overdue tasks' => '%d forsinkede opgaver', + 'No notification.' => 'Ingen påmindelse.', + 'Mark all as read' => 'Markere alle som læst', + 'Mark as read' => 'Markere som læst', + 'Total number of tasks in this column across all swimlanes' => 'Samlet antal opgaver i kolonne på tværs af alle spor', + 'Collapse swimlane' => 'Sammenfolde spor', + 'Expand swimlane' => 'Udfolde spor', + 'Add a new filter' => 'Tilføj nyt filter', + 'Share with all project members' => 'Del med alle projektmedlemmer', + 'Shared' => 'Delt', + 'Owner' => 'Ejer', + 'Unread notifications' => 'Ulæste påmindelser', + 'Notification methods:' => 'Påmindelse metoder:', + 'Unable to read your file' => 'Kan ikke læse fil', + '%d task(s) have been imported successfully.' => '%d opgave(er) blev importeret.', + 'Nothing has been imported!' => 'Intet blev importeret!', + 'Import users from CSV file' => 'Importere brugere fra CSV fil', + '%d user(s) have been imported successfully.' => '%d bruger(e) blev importeret.', + 'Comma' => 'Komma', + 'Semi-colon' => 'Semikolon', + 'Tab' => 'Fane', + 'Vertical bar' => 'Lodret bjælke', + 'Double Quote' => 'Dobbelt citat', + 'Single Quote' => 'Enkelt citat', + '%s attached a file to the task #%d' => '%s vedhæftet fil til opgave #%d', + 'There is no column or swimlane activated in your project!' => 'Ingen kolonne eller spor aktiveret i projekt!', + 'Append filter (instead of replacement)' => 'Tilføj filter (i stedet for udskiftning)', + 'Append/Replace' => 'Tilføj / erstat', + 'Append' => 'Tilføj', + 'Replace' => 'Erstat', + 'Import' => 'Importer', + 'Change sorting' => 'Ændre sortering', + 'Tasks Importation' => 'Opgaver Import', + 'Delimiter' => 'Afgrænse', + 'Enclosure' => 'Indeluk', + 'CSV File' => 'CSV fil', + 'Instructions' => 'Instruktioner', + 'Your file must use the predefined CSV format' => 'Filen skal bruge forudbestemt CSV format', + 'Your file must be encoded in UTF-8' => 'Fil skal være indkodet i UTF-8', + 'The first row must be the header' => 'Første række skal være overskrift', + 'Duplicates are not verified for you' => 'Dubletter er ikke efterprøvet', + 'The due date must use the ISO format: YYYY-MM-DD' => 'Forfaldsdato skal bruge ISO-format: ÅÅÅÅ-MM-DD', + 'Download CSV template' => 'Hente CSV skabelon', + 'No external integration registered.' => 'Ingen ekstern integration registreret.', + 'Duplicates are not imported' => 'Dubletter importeres ikke', + 'Usernames must be lowercase and unique' => 'Brugernavn skal være små bogstaver og unik', + 'Passwords will be encrypted if present' => 'Adgangskoder krypteres hvis tilstede', + '%s attached a new file to the task %s' => '%s knytte ny fil til opgave %s', + 'Link type' => 'Henvisning type', + 'Assign automatically a category based on a link' => 'Tildele automatisk kategori baseret på henvisning', + 'BAM - Konvertible Mark' => 'BAM - mærke kan konverteres', + 'Assignee Username' => 'Ansvarligs brugernavn', + 'Assignee Name' => 'Ansvarligs navn', + 'Groups' => 'Grupper', + 'Members of %s' => 'Medlemmer af %s', + 'New group' => 'Ny gruppe', + 'Group created successfully.' => 'Gruppe oprettet.', + 'Unable to create your group.' => 'Kan ikke oprette gruppe.', + 'Edit group' => 'Redigere gruppe', + 'Group updated successfully.' => 'Gruppe opdateret.', + 'Unable to update your group.' => 'Kan ikke opdatere gruppe.', + 'Add group member to "%s"' => 'Tilføj gruppemedlem til "%s"', + 'Group member added successfully.' => 'Gruppemedlem tilføjet.', + 'Unable to add group member.' => 'Kan ikke tilføje gruppemedlem.', + 'Remove user from group "%s"' => 'Fjern bruger fra gruppe "%s"', + 'User removed successfully from this group.' => 'Fjernet bruger fra gruppe.', + 'Unable to remove this user from the group.' => 'Kan ikke fjerne bruger fra gruppe.', + 'Remove group' => 'Fjern gruppe', + 'Group removed successfully.' => 'Gruppe fjernet.', + 'Unable to remove this group.' => 'Kan ikke fjerne gruppe.', + 'Project Permissions' => 'Projekt tilladelser', + 'Manager' => 'Leder', + 'Project Manager' => 'Projektleder', + 'Project Member' => 'Projektmedlem', + 'Project Viewer' => 'Projekt betragter', + 'Your account is locked for %d minutes' => 'Konto låst i %d minutter', + 'Invalid captcha' => 'Ugyldig CAPTCHA', + 'The name must be unique' => 'Navn skal være unik', + 'View all groups' => 'Vis alle grupper', + 'There is no user available.' => 'Ingen bruger tilgængelig.', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Fjern bruger "%s" fra gruppe "%s"?', + 'There is no group.' => 'Ingen gruppe.', + 'Add group member' => 'Tilføj gruppe medlem', + 'Do you really want to remove this group: "%s"?' => 'Fjern gruppe: "%s"?', + 'There is no user in this group.' => 'Ingen bruger i gruppe.', + 'Permissions' => 'Tilladelser', + 'Allowed Users' => 'Tillad brugere', + 'No specific user has been allowed.' => 'Ingen bruger specifik tilladt.', + 'Role' => 'Rolle', + 'Enter user name...' => 'Indtast brugernavn...', + 'Allowed Groups' => 'Tillad grupper', + 'No group has been allowed.' => 'Ingen gruppe specifik tilladt.', + 'Group' => 'Gruppe', + 'Group Name' => 'Gruppenavn', + 'Enter group name...' => 'Indtast gruppenavn...', + 'Role:' => 'Rolle:', + 'Project members' => 'Projektmedlemmer', + '%s mentioned you in the task #%d' => '%s nævnte bruger i opgave #%d', + '%s mentioned you in a comment on the task #%d' => '%s nævnte bruger i kommentar til opgave #%d', + 'You were mentioned in the task #%d' => 'Bruger nævnt i opgave #%d', + 'You were mentioned in a comment on the task #%d' => 'Bruger nævnt i kommentar til opgave #%d', + 'Estimated hours: ' => 'Anslåede timer: ', + 'Actual hours: ' => 'Faktiske timer: ', + 'Hours Spent' => 'Forbrugte timer:', + 'Hours Estimated' => 'Anslåede timer', + 'Estimated Time' => 'Anslået tid', + 'Actual Time' => 'Faktiske tid', + 'Estimated vs actual time' => 'Anslået mod faktisk tid', + 'RUB - Russian Ruble' => 'RUB - russisk rubel', + 'Assign the task to the person who does the action when the column is changed' => 'Tildele opgave til person som begår handlingen når kolonnen ændres', + 'Close a task in a specific column' => 'Lukke opgave i bestemt kolonne', + 'Time-based One-time Password Algorithm' => 'Tidsbaseret engangs-adgangskode algoritme', + 'Two-Factor Provider: ' => 'To-faktor udbyder: ', + 'Disable two-factor authentication' => 'Deaktivere to-faktor godkendelse', + 'Enable two-factor authentication' => 'Aktivere to-faktor godkendelse', + 'There is no integration registered at the moment.' => 'Ingen integration registreret i øjeblikket.', + 'Password Reset for Kanboard' => 'Nulstille adgangskode for Kanboard', + 'Forgot password?' => 'Glemt adgangskode?', + 'Enable "Forget Password"' => 'Aktivere "Glemme adgangskode"', + 'Password Reset' => 'Nulstille adgangskode', + 'New password' => 'Ny adgangskode', + 'Change Password' => 'Ændre adgangskode', + 'To reset your password click on this link:' => 'For at nulstille adgangskode klik på henvisning:', + 'Last Password Reset' => 'Adgangskode senest nulstillet', + 'The password has never been reinitialized.' => 'Adgangskode aldrig nulstillet.', + 'Creation' => 'Oprettelse', + 'Expiration' => 'Udløb', + 'Password reset history' => 'Adgangskode nulstille historik', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Alle opgaver i kolonne "%s" og spor "%s" blev lukket.', + 'Do you really want to close all tasks of this column?' => 'Lukke alle opgaver i kolonnen?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d opgave(r) i kolonne "%s" og spor "%s" lukkes.', + 'Close all tasks in this column and this swimlane' => 'Lukke alle opgaver i kolonne', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Ingen udvidelses-modul har registreret projekt påmindelses-metode Brugeren kan stadig konfigurere individuelle påmindelser i brugerprofil.', + 'My dashboard' => 'Instrumentbræt', + 'My profile' => 'Profil', + 'Project owner: ' => 'Projekt ejer: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'Projekt ID valgfri og skal være alfanumerisk; eksempel: MitProjekt.', + 'Project owner' => 'Projekt ejer', + 'Personal projects do not have users and groups management.' => 'Private projekter har ikke bruger og gruppe styring.', + 'There is no project member.' => 'Ingen projekt medlem.', + 'Priority' => 'Prioritet', + 'Task priority' => 'Opgave prioritet', + 'General' => 'Generelt', + 'Dates' => 'Datoer', + 'Default priority' => 'Standard prioritet', + 'Lowest priority' => 'Laveste prioritet', + 'Highest priority' => 'Højeste prioritet', + 'Close a task when there is no activity' => 'Lukke opgave når der ikke er aktivitet', + 'Duration in days' => 'Varighed i dage', + 'Send email when there is no activity on a task' => 'Sende e-post når der ikke er aktivitet på opgave', + 'Unable to fetch link information.' => 'Kan ikke hente henvisning informationer.', + 'Daily background job for tasks' => 'Daglige baggrundsjob til opgaver', + 'Auto' => 'Auto', + 'Related' => 'Relateret', + 'Attachment' => 'Vedhæftning', + 'Web Link' => 'Net henvisning', + 'External links' => 'Eksterne henvisninger', + 'Add external link' => 'Tilføj ekstern henvisning', + 'Type' => 'Type', + 'Dependency' => 'Afhængighed', + 'Add internal link' => 'Tilføj intern henvisning', + 'Add a new external link' => 'Tilføj ny ekstern henvisning', + 'Edit external link' => 'Rette ekstern henvisning', + 'External link' => 'Ekstern henvisning', + 'Copy and paste your link here...' => 'Kopiere og indsætte henvisning her...', + 'URL' => 'URL', + 'Internal links' => 'Intern henvisning', + 'Assign to me' => 'Tildele bruger', + 'Me' => 'Bruger', + 'Do not duplicate anything' => 'Kopiere ikke noget', + 'Projects management' => 'Projekt styring', + 'Users management' => 'Bruger styring', + 'Groups management' => 'Gruppe styring', + 'Create from another project' => 'Opret fra andet projekt', + 'open' => 'åben', + 'closed' => 'lukket', + 'Priority:' => 'Prioritet:', + 'Reference:' => 'Reference:', + 'Complexity:' => 'Kompleksitet:', + 'Swimlane:' => 'Spor:', + 'Column:' => 'Kolonne:', + 'Position:' => 'Placering:', + 'Creator:' => 'Opretter:', + 'Time estimated:' => 'Tid anslået:', + '%s hours' => '%s timer', + 'Time spent:' => 'Tid brugt:', + 'Created:' => 'Oprettet:', + 'Modified:' => 'Ændret:', + 'Completed:' => 'Afsluttet:', + 'Started:' => 'Startet:', + 'Moved:' => 'Flyttet:', + 'Task #%d' => 'Opgave #%d', + 'Time format' => 'Tidsformat', + 'Start date: ' => 'Start dato: ', + 'End date: ' => 'Slut dato: ', + 'New due date: ' => 'Ny forfalds-dato: ', + 'Start date changed: ' => 'Start dato ændret: ', + 'Disable personal projects' => 'Deaktivere private projekter', + 'Do you really want to remove this custom filter: "%s"?' => 'Fjern brugertilpasset filter: "%s"?', + 'Remove a custom filter' => 'Fjern brugertilpasset filter', + 'User activated successfully.' => 'Bruger aktiveret.', + 'Unable to enable this user.' => 'Kan ikke aktivere bruger.', + 'User disabled successfully.' => 'Bruger deaktiveret.', + 'Unable to disable this user.' => 'Kan ikke deaktivere bruger.', + 'All files have been uploaded successfully.' => 'Alle filer overført.', + 'The maximum allowed file size is %sB.' => 'Den maksimale filstørrelse er %sB.', + 'Drag and drop your files here' => 'Træk og slip filer her', + 'choose files' => 'vælg filer', + 'View profile' => 'Vis profil', + 'Two Factor' => 'To-faktor', + 'Disable user' => 'Deaktivere bruger', + 'Do you really want to disable this user: "%s"?' => 'Deaktivere bruger: "%s"?', + 'Enable user' => 'Aktivere bruger', + 'Do you really want to enable this user: "%s"?' => 'Aktivere bruger: "%s"?', + 'Download' => 'Hente', + 'Uploaded: %s' => 'Overført: %s', + 'Size: %s' => 'Størrelse: %s', + 'Uploaded by %s' => 'Overført af %s', + 'Filename' => 'Filnavn', + 'Size' => 'Størrelse', + 'Column created successfully.' => 'Kolonne oprettet.', + 'Another column with the same name exists in the project' => 'Der findes anden kolonne med samme navn i projekt', + 'Default filters' => 'Standard filtre', + 'Your board doesn\'t have any columns!' => 'Tavle har ingen kolonner!', + 'Change column position' => 'Ændre kolonne placering', + 'Switch to the project overview' => 'Skifte til projekt oversigt', + 'User filters' => 'Bruger filtre', + 'Category filters' => 'Kategori filtre', + 'Upload a file' => 'Overføre fil', + 'View file' => 'Vis fil', + 'Last activity' => 'Seneste aktivitet', + 'Change subtask position' => 'Ændre delopgave placering', + 'This value must be greater than %d' => 'Værdi skal være større end %d', + 'Another swimlane with the same name exists in the project' => 'Der findes andet spor med samme navn i projekt', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Eksempel: https://example.kanboard.org/ (bruges til at generere absolutte URLer)', + 'Actions duplicated successfully.' => 'Handlinger kopieret.', + 'Unable to duplicate actions.' => 'Kan ikke kopiere handlinger.', + 'Add a new action' => 'Tilføj ny handling', + 'Import from another project' => 'Importere fra andet projekt', + 'There is no action at the moment.' => 'Ingen handling i øjeblikket.', + 'Import actions from another project' => 'Importere handlinger fra andet projekt', + 'There is no available project.' => 'Ingen projekt tilgængelig.', + 'Local File' => 'Lokal fil', + 'Configuration' => 'Indstillinger', + 'PHP version:' => 'PHP version:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'OS version:', + 'Database version:' => 'Database version:', + 'Browser:' => 'Netlæser:', + 'Task view' => 'Opgave oversigt', + 'Edit task' => 'Redigere opgave', + 'Edit description' => 'Redigere beskrivelse', + 'New internal link' => 'Ny intern henvisning', + 'Display list of keyboard shortcuts' => 'Vis liste over tastaturgenveje', + 'Avatar' => 'Ikon', + 'Upload my avatar image' => 'Overføre ikon billede', + 'Remove my image' => 'Fjern billede', + 'The OAuth2 state parameter is invalid' => 'Ugyldig OAuth2 tilstands-parameter', + 'User not found.' => 'Bruger ikke fundet.', + 'Search in activity stream' => 'Søg i aktivitet strøm', + 'My activities' => 'Tildelte aktiviteter', + 'Activity until yesterday' => 'Aktivitet indtil i går', + 'Activity until today' => 'Aktivitet indtil i dag', + 'Search by creator: ' => 'Søg efter opretter: ', + 'Search by creation date: ' => 'Søg efter oprettelsesdato: ', + 'Search by task status: ' => 'Søg efter opgave status: ', + 'Search by task title: ' => 'Søg efter opgavetitel: ', + 'Activity stream search' => 'Aktivitet strøm søgning', + 'Projects where "%s" is manager' => 'Projekter hvor "%s" er leder', + 'Projects where "%s" is member' => 'Projekter, hvor "%s" er medlem', + 'Open tasks assigned to "%s"' => 'Åbne opgaver tildelt "%s"', + 'Closed tasks assigned to "%s"' => 'Lukkede opgaver tildelt "%s"', + 'Assign automatically a color based on a priority' => 'Tildele automatisk farve baseret på prioritet', + 'Overdue tasks for the project(s) "%s"' => 'Forfaldne opgaver for projekt(er) "%s"', + 'Upload files' => 'Overføre filer', + 'Installed Plugins' => 'Installerede udvidelses-moduler', + 'Plugin Directory' => 'Udvidelses-modul katalog', + 'Plugin installed successfully.' => 'Udvidelses-modul installeret.', + 'Plugin updated successfully.' => 'Udvidelses-modul opdateret.', + 'Plugin removed successfully.' => 'Udvidelses-modul fjernet.', + 'Subtask converted to task successfully.' => 'Delopgave konverteret til opgave.', + 'Unable to convert the subtask.' => 'Kan ikke konvertere delopgave.', + 'Unable to extract plugin archive.' => 'Kan ikke udpakke udvidelses-modul arkiv.', + 'Plugin not found.' => 'Udvidelses-modul ikke fundet.', + 'You don\'t have the permission to remove this plugin.' => 'Ikke tilladelse til at fjerne udvidelses-modul.', + 'Unable to download plugin archive.' => 'Kan ikke hente udvidelses-modul arkiv.', + 'Unable to write temporary file for plugin.' => 'Kan ikke skrive midlertidig fil til udvidelses-modul.', + 'Unable to open plugin archive.' => 'Kan ikke åbne udvidelses-modul arkiv.', + 'There is no file in the plugin archive.' => 'Ingen fil i udvidelses-modul arkivet.', + 'Create tasks in bulk' => 'Opret flere opgaver', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Kanboard forekomst er ikke indstillettil at installere udvidelses-moduler fra brugergrænsefladen.', + 'There is no plugin available.' => 'Ingen udvidelses-modul til rådighed.', + 'Install' => 'Installere', + 'Update' => 'Opdatere', + 'Up to date' => 'Opdateret', + 'Not available' => 'Ikke tilgængelig', + 'Remove plugin' => 'Fjern udvidelses-modul', + 'Do you really want to remove this plugin: "%s"?' => 'Fjern udvidelses-modul: "%s"?', + 'Uninstall' => 'Afinstallere', + 'Listing' => 'Kategorisere', + 'Metadata' => 'Metadata', + 'Manage projects' => 'Projekt styring', + 'Convert to task' => 'Konvertere til opgave', + 'Convert sub-task to task' => 'Konvertere delopgave til opgave', + 'Do you really want to convert this sub-task to a task?' => 'Konvertere delopgave til opgave?', + 'My task title' => 'Tildelt opgave titel', + 'Enter one task by line.' => 'Indtast én opgave pr. linje.', + 'Number of failed login:' => 'Antal mislykkede log ind:', + 'Account locked until:' => 'Konto låst indtil:', + 'Email settings' => 'E-post indstillinger', + 'Email sender address' => 'E-post afsender adresse', + 'Email transport' => 'E-post transport', + 'Webhook token' => 'Net-hægte symbol', + 'Project tags management' => 'Projekt mærke styring', + 'Tag created successfully.' => 'Mærke oprettet.', + 'Unable to create this tag.' => 'Kunne ikke oprette mærke.', + 'Tag updated successfully.' => 'Mærke opdateret.', + 'Unable to update this tag.' => 'Kan ikke opdatere mærke.', + 'Tag removed successfully.' => 'Mærke fjernet.', + 'Unable to remove this tag.' => 'Kan ikke fjerne mærke.', + 'Global tags management' => 'Global mærke styring', + 'Tags' => 'Mærker', + 'Tags management' => 'Mærke styring', + 'Add new tag' => 'Tilføj nyt mærke', + 'Edit a tag' => 'Redigere mærke', + 'Project tags' => 'Projekt mærker', + 'There is no specific tag for this project at the moment.' => 'Ingen specifik projekt mærke i øjeblikket.', + 'Tag' => 'Mærke', + 'Remove a tag' => 'Fjern et mærke', + 'Do you really want to remove this tag: "%s"?' => 'Fjern mærke: "%s"?', + 'Global tags' => 'Globale mærker', + 'There is no global tag at the moment.' => 'Ingen global mærke i øjeblikket.', + 'This field cannot be empty' => 'Feltet kan ikke være tomt', + 'Close a task when there is no activity in a specific column' => 'Lukke opgave når der ikke er nogen aktivitet i bestemt kolonne', + '%s removed a subtask for the task #%d' => '%s fjernet delopgave til opgave #%d', + '%s removed a comment on the task #%d' => '%s fjernet kommentar til opgave #%d', + 'Comment removed on task #%d' => 'Kommentar fjernet til opgave #%d', + 'Subtask removed on task #%d' => 'Delopgave fjernet til opgave #%d', + 'Hide tasks in this column in the dashboard' => 'Skjul opgaver i kolonne i instrumentbrættet', + '%s removed a comment on the task %s' => '%s fjernet kommentar til opgave %s', + '%s removed a subtask for the task %s' => '%s fjernet delopgave til opgave %s', + 'Comment removed' => 'Kommentar fjernet', + 'Subtask removed' => 'Delopgave fjernet', + '%s set a new internal link for the task #%d' => '%s angive ny intern henvisning til opgave #%d', + '%s removed an internal link for the task #%d' => '%s fjernet intern henvisning til opgave #%d', + 'A new internal link for the task #%d has been defined' => 'Ny intern henvisning til opgave #%d er bestemt', + 'Internal link removed for the task #%d' => 'Intern henvisning fjernet til opgave #%d', + '%s set a new internal link for the task %s' => '%s angive ny intern henvisning til opgave %s', + '%s removed an internal link for the task %s' => '%s fjernet intern henvisning til opgave %s', + 'Automatically set the due date on task creation' => 'Angive forfaldsdato automatisk ved oprettelse af opgave', + 'Move the task to another column when closed' => 'Flyt opgave til anden kolonne når den er lukket', + 'Move the task to another column when not moved during a given period' => 'Flyt opgave til anden kolonne når den ikke er flyttet i given periode', + 'Dashboard for %s' => 'Instrumentbræt for %s', + 'Tasks overview for %s' => 'Opgave oversigt for %s', + 'Subtasks overview for %s' => 'Delopgave oversigt for %s', + 'Projects overview for %s' => 'Projektoversigt for %s', + 'Activity stream for %s' => 'Aktivitet strøm for %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Tildele farve når opgave flyttes til bestemt spor', + 'Assign a priority when the task is moved to a specific swimlane' => 'Tildele prioritet når opgave flyttes til bestemt spor', + 'User unlocked successfully.' => 'Bruger frigjort.', + 'Unable to unlock the user.' => 'Kan ikke frigøre bruger.', + 'Move a task to another swimlane' => 'Flyt opgave til andet spor', + 'Creator Name' => 'Opretter navn', + 'Time spent and estimated' => 'Tid brugt og anslået', + 'Move position' => 'Flyt placering', + 'Move task to another position on the board' => 'Flyt opgave til anden placering på tavle', + 'Insert before this task' => 'Indsæt før opgave', + 'Insert after this task' => 'Indsæt efter opgave', + 'Unlock this user' => 'Frigøre bruger', + 'Custom Project Roles' => 'Brugertilpasset projektroller', + 'Add a new custom role' => 'Tilføj ny brugertilpasset rolle', + 'Restrictions for the role "%s"' => 'Begrænsninger for rolle "%s"', + 'Add a new project restriction' => 'Tilføj ny projektbegrænsning', + 'Add a new drag and drop restriction' => 'Tilføj ny træk og slip begrænsning', + 'Add a new column restriction' => 'Tilføj ny kolonne begrænsning', + 'Edit this role' => 'Redigere rolle', + 'Remove this role' => 'Fjern rolle', + 'There is no restriction for this role.' => 'Ingen begrænsninger for rolle.', + 'Only moving task between those columns is permitted' => 'Det er kun tilladt at flytte opgave mellem disse kolonner', + 'Close a task in a specific column when not moved during a given period' => 'Lukke opgave i bestemt kolonne når den ikke er flyttet i given periode', + 'Edit columns' => 'Redigere kolonner', + 'The column restriction has been created successfully.' => 'Kolonne begrænsning oprettet.', + 'Unable to create this column restriction.' => 'Kan ikke oprette kolonne begrænsning.', + 'Column restriction removed successfully.' => 'Kolonne begrænsning fjernet.', + 'Unable to remove this restriction.' => 'Kan ikke fjerne begrænsning.', + 'Your custom project role has been created successfully.' => 'Brugertilpasset projektrolle oprettet.', + 'Unable to create custom project role.' => 'Kan ikke oprette brugertilpasset projekt rolle.', + 'Your custom project role has been updated successfully.' => 'Brugertilpasset projektrolle opdateret.', + 'Unable to update custom project role.' => 'Kan ikke opdatere brugertilpasset projekt rolle.', + 'Custom project role removed successfully.' => 'Brugertilpasset projektrolle fjernet.', + 'Unable to remove this project role.' => 'Kan ikke fjerne projekt rolle.', + 'The project restriction has been created successfully.' => 'Projektbegrænsningen oprettet.', + 'Unable to create this project restriction.' => 'Kan ikke oprette projekt begrænsning.', + 'Project restriction removed successfully.' => 'Projektbegrænsningen fjernet.', + 'You cannot create tasks in this column.' => 'Kan ikke oprette opgaver i kolonne.', + 'Task creation is permitted for this column' => 'Opgaveoprettelse tilladt for kolonne', + 'Closing or opening a task is permitted for this column' => 'Lukning eller åbning af opgave tilladt for kolonne', + 'Task creation is blocked for this column' => 'Opgaveoprettelse blokeret for kolonne', + 'Closing or opening a task is blocked for this column' => 'Lukning eller åbning af opgave blokeret for kolonne', + 'Task creation is not permitted' => 'Opgaveoprettelse ikke tilladt', + 'Closing or opening a task is not permitted' => 'Lukning eller åbning af opgave ikke tilladt', + 'New drag and drop restriction for the role "%s"' => 'Ny træk og slip begrænsning for rolle "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Personer med denne rolle kan flytte opgaver mellem kilde og destinations kolonne.', + 'Remove a column restriction' => 'Fjern kolonne begrænsning', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Fjern kolonne begrænsning: "%s" til "%s"?', + 'New column restriction for the role "%s"' => 'Ny kolonne begrænsning for rolle "%s"', + 'Rule' => 'Regel', + 'Do you really want to remove this column restriction?' => 'Fjern kolonne begrænsning?', + 'Custom roles' => 'Brugertilpassede roller', + 'New custom project role' => 'Ny brugertilpasset projektrolle', + 'Edit custom project role' => 'Redigere brugertilpasset projektrolle', + 'Remove a custom role' => 'Fjern brugertilpasset rolle', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Fjern brugertilpasset rolle: "%s"? Alle personer tildelt rolle bliver projektmedlem.', + 'There is no custom role for this project.' => 'Ingen brugertilpasset rolle for projekt.', + 'New project restriction for the role "%s"' => 'Ny projektbegrænsning for rolle "%s"', + 'Restriction' => 'Begrænsning', + 'Remove a project restriction' => 'Fjern projektbegrænsning', + 'Do you really want to remove this project restriction: "%s"?' => 'Fjern projektbegrænsning: "%s"?', + 'Duplicate to multiple projects' => 'Kopiere til flere projekter', + 'This field is required' => 'Felt er påkrævet', + 'Moving a task is not permitted' => 'Ikke tilladt at flytte opgave', + 'This value must be in the range %d to %d' => 'Værdi skal ligge i område %d til %d', + 'You are not allowed to move this task.' => 'Ikke tilladelse til at flytte opgave.', + 'API User Access' => 'API bruger adgang', + 'Preview' => 'Forhånds-visning', + 'Write' => 'Skriv', + 'Write your text in Markdown' => 'Skriv tekst i mark-down format', + 'No personal API access token registered.' => 'Ingen personlig API adgangsymbol registreret.', + 'Your personal API access token is "%s"' => 'Personlig API adgangsymbol er "%s"', + 'Remove your token' => 'Fjern symbol', + 'Generate a new token' => 'Generere nyt symbol', + 'Showing %d-%d of %d' => 'Viser %d-%d af %d', + 'Outgoing Emails' => 'Udgående e-post', + 'Add or change currency rate' => 'Tilføj eller ændre valutakurs', + 'Reference currency: %s' => 'Referencevaluta: %s', + 'Add custom filters' => 'Tilføj brugertilpasset filtre', + 'Export' => 'Eksport', + 'Add link label' => 'Tilføj henvisnings etiket', + 'Incompatible Plugins' => 'Inkompatibel udvidelses-moduler', + 'Compatibility' => 'Kompatibilitet', + 'Permissions and ownership' => 'Tilladelser og ejerskab', + 'Priorities' => 'Prioriteter', + 'Close this window' => 'Lukke vindue', + 'Unable to upload this file.' => 'Kan ikke overføre fil.', + 'Import tasks' => 'Importere opgaver', + 'Choose a project' => 'Vælge projekt', + 'Profile' => 'Profil', + 'Application role' => 'Program rolle', + '%d invitations were sent.' => '%d invitationer blev sendt.', + '%d invitation was sent.' => '%d invitation blev sendt.', + 'Unable to create this user.' => 'Kan ikke oprette bruger.', + 'Kanboard Invitation' => 'Kanboard invitation', + 'Visible on dashboard' => 'Synlig på instrumentbræt', + 'Created at:' => 'Oprettet på:', + 'Updated at:' => 'Opdateret på:', + 'There is no custom filter.' => 'Ingen brugertilpasset filter.', + 'New User' => 'Ny Bruger', + 'Authentication' => 'Godkendelse', + 'If checked, this user will use a third-party system for authentication.' => 'Hvis markeret vil bruger bruge et tredjeparts system til godkendelse.', + 'The password is necessary only for local users.' => 'Adgangskode er kun nødvendig for lokale brugere.', + 'You have been invited to register on Kanboard.' => 'Invitation til at registrere på Kanboard.', + 'Click here to join your team' => 'Klik her for at deltage på holdet', + 'Invite people' => 'Invitere personer', + 'Emails' => 'E-post', + 'Enter one email address by line.' => 'Indtaste e-post adresse pr. linje.', + 'Add these people to this project' => 'Tilføj disse personer til projekt', + 'Add this person to this project' => 'Tilføj person til projekt', + 'Sign-up' => 'Tilmelde', + 'Credentials' => 'Legitimations-oplysninger', + 'New user' => 'Ny bruger', + 'This username is already taken' => 'Brugernavn optaget', + 'Your profile must have a valid email address.' => 'Profil skal have gyldig e-post adresse.', + 'TRL - Turkish Lira' => 'TRL - tyrkisk lire', + 'The project email is optional and could be used by several plugins.' => 'Projekt e-post adresse valgfri og kan bruges af flere udvidelses-moduler.', + 'The project email must be unique across all projects' => 'Projekt e-post adresse skal være unik på tværs af projekter', + 'The email configuration has been disabled by the administrator.' => 'E-post indstilling deaktiveret af administrator.', + 'Close this project' => 'Lukke projekt', + 'Open this project' => 'Åbne projekt', + 'Close a project' => 'Lukke projekt', + 'Do you really want to close this project: "%s"?' => 'Lukke projekt: "%s"?', + 'Reopen a project' => 'Genåbne projekt', + 'Do you really want to reopen this project: "%s"?' => 'Genåbne projekt: "%s"?', + 'This project is open' => 'Projekt åben', + 'This project is closed' => 'Projekt lukket', + 'Unable to upload files, check the permissions of your data folder.' => 'Kan ikke overføre filer, kontrollere mappe tilladelser.', + 'Another category with the same name exists in this project' => 'Anden kategori med samme navn i projekt', + 'Comment sent by email successfully.' => 'Kommentar sendt via e-post.', + 'Sent by email to "%s" (%s)' => 'Sendt via e-post til "%s" (%s)', + 'Unable to read uploaded file.' => 'Kan ikke læse overført fil.', + 'Database uploaded successfully.' => 'Database overført.', + 'Task sent by email successfully.' => 'Opgave sendt via e-post.', + 'There is no category in this project.' => 'Ingen kategori i projekt.', + 'Send by email' => 'Send via e-post', + 'Create and send a comment by email' => 'Opret og send kommentar via e-post', + 'Subject' => 'Emne', + 'Upload the database' => 'Overføre database', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Overføre tidligere hentede SQL-database (Gzip format).', + 'Database file' => 'Databasefil', + 'Upload' => 'Overføre', + 'Your project must have at least one active swimlane.' => 'Projekt skal have mindst et aktivt spor.', + 'Project: %s' => 'Projekt: %s', + 'Automatic action not found: "%s"' => 'Automatisk handling ikke fundet: "%s"', + '%d projects' => '%d projekter', + '%d project' => '%d projekt', + 'There is no project.' => 'Ikke noget projekt.', + 'Sort' => 'Sortere', + 'Project ID' => 'Projekt ID', + 'Project name' => 'Projekt navn', + 'Public' => 'Offentlig', + 'Personal' => 'Privat', + '%d tasks' => '%d opgaver', + '%d task' => '%d opgave', + 'Task ID' => 'Opgave ID', + 'Assign automatically a color when due date is expired' => 'Tildele automatisk farve når forfaldsdato er udløbet', + 'Total score in this column across all swimlanes' => 'Samlet bedømmelse i kolonne på tværs af alle spor', + 'HRK - Kuna' => 'HRK - kroatisk kuna', + 'ARS - Argentine Peso' => 'ARS - argentinsk peso', + 'COP - Colombian Peso' => 'COP - colombiansk peso', + '%d groups' => '%d grupper', + '%d group' => '%d gruppe', + 'Group ID' => 'Gruppe ID', + 'External ID' => 'Ekstern ID', + '%d users' => '%d brugere', + '%d user' => '%d bruger', + 'Hide subtasks' => 'Skjul delopgaver', + 'Show subtasks' => 'Vis delopgaver', + 'Authentication Parameters' => 'Godkendelses-parametre', + 'API Access' => 'API adgang', + 'No users found.' => 'Ingen brugere fundet.', + 'User ID' => 'Bruger ID', + 'Notifications are activated' => 'Påmindelser aktiveret', + 'Notifications are disabled' => 'Påmindelser deaktiveret', + 'User disabled' => 'Bruger deaktiveret', + '%d notifications' => '%d påmindelser', + '%d notification' => '%d påmindelse', + 'There is no external integration installed.' => 'Ingen ekstern integration installeret.', + 'You are not allowed to update tasks assigned to someone else.' => 'Kan ikke opdatere opgaver tildelt anden bruger.', + 'You are not allowed to change the assignee.' => 'Kan ikke ændre ejer.', + 'Task suppression is not permitted' => 'Opgave undertrykkelse er ikke tilladt', + 'Changing assignee is not permitted' => 'Ændring af ansvarlig er ikke tilladt', + 'Update only assigned tasks is permitted' => 'Opdatere kun tilladt tildelte opgaver', + 'Only for tasks assigned to the current user' => 'Kun opgaver tildelt nuværende bruger', + 'My projects' => 'Tildelte projekter', + 'You are not a member of any project.' => 'Ikke medlem af noget projekt.', + 'My subtasks' => 'Tildelte delopgaver', + '%d subtasks' => '%d delopgaver', + '%d subtask' => '%d delopgave', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Kun opgave flyt mellem disse kolonner er tilladt for opgaver tildelt nuværende bruger', + '[DUPLICATE]' => '[KOPIERE]', + 'DKK - Danish Krona' => 'DKK - dansk krone', + 'Remove user from group' => 'Fjern bruger fra gruppe', + 'Assign the task to its creator' => 'Tildele opgave til opretter', + 'This task was sent by email to "%s" with subject "%s".' => 'Opgave sendt via e-post til "%s" med emne "%s".', + 'Predefined Email Subjects' => 'Forudbestemte e-post emner', + 'Write one subject by line.' => 'Skriv ét emne pr. linje.', + 'Create another link' => 'Opret anden henvisning', + 'BRL - Brazilian Real' => 'BRL - brasiliansk real', + 'Add a new Kanboard task' => 'Tilføj ny Kanboard opgave', + 'Subtask not started' => 'Delopgave ikke startet', + 'Subtask currently in progress' => 'Delopgave i gang', + 'Subtask completed' => 'Delopgave afsluttet', + 'Subtask added successfully.' => 'Delopgave tilføjet.', + '%d subtasks added successfully.' => '%d delopgaver tilføjet.', + 'Enter one subtask by line.' => 'Indtaste én delopgave pr. linje.', + 'Predefined Contents' => 'Forudbestemt indhold', + 'Predefined contents' => 'Forudbestemt indhold', + 'Predefined Task Description' => 'Forudbestemt opgavebeskrivelse', + 'Do you really want to remove this template? "%s"' => 'Fjern skabelon? "%s"', + 'Add predefined task description' => 'Tilføj forudbestemt opgavebeskrivelse', + 'Predefined Task Descriptions' => 'Forudbestemte opgavebeskrivelser', + 'Template created successfully.' => 'Skabelonen oprettet.', + 'Unable to create this template.' => 'Kan ikke oprette skabelon.', + 'Template updated successfully.' => 'Skabelon opdateret.', + 'Unable to update this template.' => 'Kan ikke opdatere skabelon.', + 'Template removed successfully.' => 'Skabelon fjernet.', + 'Unable to remove this template.' => 'Kan ikke fjerne skabelon.', + 'Template for the task description' => 'Skabelon til opgavebeskrivelse', + 'The start date is greater than the end date' => 'Startdato er større end slutdato', + 'Tags must be separated by a comma' => 'Mærker skal adskilles med komma', + 'Only the task title is required' => 'Kun opgave titel påkrævet', + 'Creator Username' => 'Opretters brugernavn', + 'Color Name' => 'Farve navn', + 'Column Name' => 'Kolonne navn', + 'Swimlane Name' => 'Spor navn', + 'Time Estimated' => 'Tid anslået', + 'Time Spent' => 'Tid brugt', + 'External Link' => 'Ekstern henvisning', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Funktionen aktiverer iCal og RSS nyheds-kilde samt offentlig tavle visning.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Stop tidsuret for alle delopgaver når opgave flyttes til anden kolonne', + 'Subtask Title' => 'Delopgave titel', + 'Add a subtask and activate the timer when moving a task to another column' => 'Tilføj delopgave og aktivere tidsuret når opgave flyttes til anden kolonne', + 'days' => 'dage', + 'minutes' => 'minutter', + 'seconds' => 'sekunder', + 'Assign automatically a color when preset start date is reached' => 'Tildel automatisk farve når forudindstillet startdato er nået', + 'Move the task to another column once a predefined start date is reached' => 'Flyt opgave til anden kolonne når forudbestemt startdato er nået', + 'This task is now linked to the task %s with the relation "%s"' => 'Opgave henviser til opgave %s med relation "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'Henvisning med relation "%s" til opgave %s er fjernet', + 'Custom Filter:' => 'Brugertilpasset filter:', + 'Unable to find this group.' => 'Kan ikke finde gruppe.', + '%s moved the task #%d to the column "%s"' => '%s flyttet opgave #%d til kolonne "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => '%s flyttet opgave #%d til placering %d i kolonne "%s"', + '%s moved the task #%d to the swimlane "%s"' => '%s flyttet opgave #%d til spor "%s"', + '%sh spent' => '%sh brugt', + '%sh estimated' => '%sh anslået', + 'Select All' => 'Vælge alle', + 'Unselect All' => 'Fravælge alle', + 'Apply action' => 'Anvende handling', + 'Move selected tasks to another column or swimlane' => 'Flyt valgte opgaver til anden kolonne', + 'Edit tasks in bulk' => 'Masse redigere opgaver', + 'Choose the properties that you would like to change for the selected tasks.' => 'Vælg egenskaber som skal ændres for de valgte opgaver.', + 'Configure this project' => 'Indstille projekt', + 'Start now' => 'Start nu', + '%s removed a file from the task #%d' => '%s fjernede en fil fra opgave #%d', + 'Attachment removed from task #%d: %s' => 'Vedhæftet fjernet fra opgave #%d: %s', + 'No color' => 'Ingen farve', + 'Attachment removed "%s"' => 'Vedhæftet fjernet "%s"', + '%s removed a file from the task %s' => '%s fjernede en fil fra opgave %s', + 'Move the task to another swimlane when assigned to a user' => 'Flyt opgave til andet spor når tildelt anden bruger', + 'Destination swimlane' => 'Mål spor', + 'Assign a category when the task is moved to a specific swimlane' => 'Tildele kategori når opgave flyttes til anført spor', + 'Move the task to another swimlane when the category is changed' => 'Flyt opgave til andet spor når kategori ændres', + 'Reorder this column by priority (ASC)' => 'Sortere kolonne efter prioritet (ASC)', + 'Reorder this column by priority (DESC)' => 'Sortere kolonne efter prioritet (DESC)', + 'Reorder this column by assignee and priority (ASC)' => 'Sortere kolonne efter ansvarlig og prioritet (ASC)', + 'Reorder this column by assignee and priority (DESC)' => 'Sortere kolonne efter ansvarlig og prioritet (DESC)', + 'Reorder this column by assignee (A-Z)' => 'Sorter kolonne efter ansvarlig (A-Z)', + 'Reorder this column by assignee (Z-A)' => 'Sorter kolonne efter ansvarlig (Z-A)', + 'Reorder this column by due date (ASC)' => 'Sorter kolonne efter udløbstid (ASC)', + 'Reorder this column by due date (DESC)' => 'Sorter kolonne efter udløbstid (DESC)', + 'Reorder this column by id (ASC)' => 'Sortér denne kolonne efter id (stigende)', + 'Reorder this column by id (DESC)' => 'Sortér denne kolonne efter id (faldende)', + '%s moved the task #%d "%s" to the project "%s"' => '%s flyttet opgave #%d "%s" til projekt "%s"', + 'Task #%d "%s" has been moved to the project "%s"' => 'Opgave #%d "%s" er flyttet til projekt "%s"', + 'Move the task to another column when the due date is less than a certain number of days' => 'Flyt opgave til anden kolonne når afleveringsdato er mindre end et bestemt antal dage', + 'Automatically update the start date when the task is moved away from a specific column' => 'Opdatere automatisk startdato når opgaven flyttes væk fra bestemt kolonne', + 'HTTP Client:' => 'HTTP klient:', + 'Assigned' => 'Tildelt', + 'Task limits apply to each swimlane individually' => 'Opgavegrænser gælder individuelt for hvert sport', + 'Column task limits apply to each swimlane individually' => 'Kolonne opgave grænser gælder individuelt for hvert spor', + 'Column task limits are applied to each swimlane individually' => 'Kolonne opgave grænser anvendes individuelt på hvert spor', + 'Column task limits are applied across swimlanes' => 'Kolonne opgave grænser anvendes på tværs af spor', + 'Task limit: ' => 'Opgave grænser:', + 'Change to global tag' => 'Ændre til globalt mærke', + 'Do you really want to make the tag "%s" global?' => 'Skal mærket "%s" gøres globalt?', + 'Enable global tags for this project' => 'Aktivere globale mærker for dette projekt', + 'Group membership(s):' => 'Gruppe medlemskab', + '%s is a member of the following group(s): %s' => '%s er medlem af følgende gruppe(r): %s', + '%d/%d group(s) shown' => '%d/%d gruppe(r) vist', + 'Subtask creation or modification' => 'Underopgave oprettelse eller ændring', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Tildele opgave til bestemt bruger når opgave flyttes til anført spor', + 'Comment' => 'Kommentar', + 'Collapse vertically' => 'Sammenfolde lodret', + 'Expand vertically' => 'Udvide lodret', + 'MXN - Mexican Peso' => 'Mexicansk Peso', + 'Estimated vs actual time per column' => 'Anslået mod faktisk tid pr. kolonne', + 'HUF - Hungarian Forint' => 'HUF - Ungarsk forint', + 'XBT - Bitcoin' => 'XBT - Bitcoin', + 'You must select a file to upload as your avatar!' => 'Vælg fil som overføres som profil billede!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'Overført fil er ikke et gyldigt billede! (*.gif, *.jpg, *.jpeg eller *.png)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Sæt automatisk afleveringsdato når opgave er flyttet væk fra en bestemt kolonne', + 'No other projects found.' => 'Ingen andre projekter fundet.', + 'Tasks copied successfully.' => 'Opgaver blev kopieret med succes.', + 'Unable to copy tasks.' => 'Kunne ikke kopiere opgaver.', + 'Theme' => 'Tema', + 'Theme:' => 'Tema:', + 'Light theme' => 'Lyst tema', + 'Dark theme' => 'Mørkt tema', + 'Automatic theme - Sync with system' => 'Automatisk tema – synkroniser med systemet', + 'Application managers or more' => 'Applikationsansvarlige eller højere', + 'Administrators' => 'Administratorer', + 'Visibility:' => 'Synlighed:', + 'Standard users' => 'Standardbrugere', + 'Visibility is required' => 'Synlighed er påkrævet', + 'The visibility should be an app role' => 'Synligheden skal være en app-rolle', + 'Reply' => 'Svar', + '%s wrote: ' => '%s skrev: ', + 'Number of visible tasks in this column and swimlane' => 'Antal synlige opgaver i denne kolonne og bane', + 'Number of tasks in this swimlane' => 'Antal opgaver i denne bane', + 'Unable to find another subtask in progress, you can close this window.' => 'Kunne ikke finde en anden underopgave i gang, du kan lukke dette vindue.', + 'This theme is invalid' => 'Dette tema er ugyldigt', + 'This role is invalid' => 'Denne rolle er ugyldig', + 'This timezone is invalid' => 'Denne tidszone er ugyldig', + 'This language is invalid' => 'Dette sprog er ugyldigt', + 'This URL is invalid' => 'Denne URL er ugyldig', + 'Date format invalid' => 'Ugyldigt datoformat', + 'Time format invalid' => 'Ugyldigt tidsformat', + 'Invalid Mail transport' => 'Ugyldig mailtransport', + 'Color invalid' => 'Ugyldig farve', + 'This value must be greater or equal to %d' => 'Værdien skal være større end eller lig med %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Tilføj en BOM i begyndelsen af filen (krævet af Microsoft Excel)', + 'Just add these tag(s)' => 'Tilføj blot disse mærker', + 'Remove internal link(s)' => 'Fjern interne links', + 'Import tasks from another project' => 'Importer opgaver fra et andet projekt', + 'Select the project to copy tasks from' => 'Vælg projektet, du vil kopiere opgaver fra', + 'The total maximum allowed attachments size is %sB.' => 'Den samlede maksimalt tilladte størrelse for vedhæftede filer er %sB.', + 'Add attachments' => 'Tilføj vedhæftede filer', + 'Task #%d "%s" is overdue' => 'Opgave #%d "%s" er forfalden', + 'Enable notifications by default for all new users' => 'Aktiver påmindelser som standard for alle nye brugere', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Tildel opgaven til dens opretter for bestemte kolonner, hvis ingen ansvarlig er sat manuelt', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Tildel en opgave til den loggede bruger ved kolonneændring til den angivne kolonne, hvis ingen bruger er tildelt', +]; diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php new file mode 100644 index 0000000..be33474 --- /dev/null +++ b/app/Locale/de_DE/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => '.', + 'None' => 'Keines', + 'Edit' => 'Bearbeiten', + 'Remove' => 'Entfernen', + 'Yes' => 'Ja', + 'No' => 'Nein', + 'cancel' => 'Abbrechen', + 'or' => 'oder', + 'Yellow' => 'Gelb', + 'Blue' => 'Blau', + 'Green' => 'Grün', + 'Purple' => 'Violett', + 'Red' => 'Rot', + 'Orange' => 'Orange', + 'Grey' => 'Grau', + 'Brown' => 'Braun', + 'Deep Orange' => 'Dunkelorange', + 'Dark Grey' => 'Dunkelgrau', + 'Pink' => 'Pink', + 'Teal' => 'Türkis', + 'Cyan' => 'Cyan', + 'Lime' => 'Limette', + 'Light Green' => 'Hellgrün', + 'Amber' => 'Bernstein', + 'Save' => 'Speichern', + 'Login' => 'Anmelden', + 'Official website:' => 'Offizielle Webseite:', + 'Unassigned' => 'Nicht zugeordnet', + 'View this task' => 'Aufgabe ansehen', + 'Remove user' => 'Benutzer löschen', + 'Do you really want to remove this user: "%s"?' => 'Soll dieser Benutzer wirklich gelöscht werden: "%s"?', + 'All users' => 'Alle Benutzer', + 'Username' => 'Benutzername', + 'Password' => 'Passwort', + 'Administrator' => 'Administrator', + 'Sign in' => 'Anmelden', + 'Users' => 'Benutzer', + 'Forbidden' => 'Verboten', + 'Access Forbidden' => 'Zugriff verboten', + 'Edit user' => 'Benutzer bearbeiten', + 'Logout' => 'Abmelden', + 'Bad username or password' => 'Falscher Benutzername oder Passwort', + 'Edit project' => 'Projekt bearbeiten', + 'Name' => 'Name', + 'Projects' => 'Projekte', + 'No project' => 'Keine Projekte', + 'Project' => 'Projekt', + 'Status' => 'Status', + 'Tasks' => 'Aufgaben', + 'Board' => 'Pinnwand', + 'Actions' => 'Aktionen', + 'Inactive' => 'Inaktiv', + 'Active' => 'Aktiv', + 'Unable to update this board.' => 'Ändern dieser Pinnwand nicht möglich.', + 'Disable' => 'Deaktivieren', + 'Enable' => 'Aktivieren', + 'New project' => 'Neues Projekt', + 'Do you really want to remove this project: "%s"?' => 'Soll dieses Projekt wirklich gelöscht werden: "%s"?', + 'Remove project' => 'Projekt löschen', + 'Edit the board for "%s"' => 'Pinnwand für "%s" bearbeiten', + 'Add a new column' => 'Neue Spalte hinzufügen', + 'Title' => 'Titel', + 'Assigned to %s' => 'Zuständig: %s', + 'Remove a column' => 'Spalte löschen', + 'Unable to remove this column.' => 'Löschen dieser Spalte nicht möglich.', + 'Do you really want to remove this column: "%s"?' => 'Soll diese Spalte wirklich gelöscht werden: "%s"?', + 'Settings' => 'Einstellungen', + 'Application settings' => 'Anwendungskonfiguration', + 'Language' => 'Sprache', + 'Webhook token:' => 'Webhook Token:', + 'API token:' => 'API Token:', + 'Database size:' => 'Datenbankgröße:', + 'Download the database' => 'Datenbank herunterladen', + 'Optimize the database' => 'Datenbank optimieren', + '(VACUUM command)' => '(VACUUM Befehl)', + '(Gzip compressed Sqlite file)' => '(Gzip-komprimierte SQLite-Datei)', + 'Close a task' => 'Aufgabe abschließen', + 'Column' => 'Spalte', + 'Color' => 'Farbe', + 'Assignee' => 'Zuständiger', + 'Create another task' => 'Weitere Aufgabe erstellen', + 'New task' => 'Neue Aufgabe', + 'Open a task' => 'Öffne eine Aufgabe', + 'Do you really want to open this task: "%s"?' => 'Soll diese Aufgabe wirklich wieder geöffnet werden: "%s"?', + 'Back to the board' => 'Zurück zur Pinnwand', + 'There is nobody assigned' => 'Die Aufgabe wurde niemandem zugewiesen', + 'Column on the board:' => 'Spalte:', + 'Close this task' => 'Aufgabe schließen', + 'Open this task' => 'Aufgabe wieder öffnen', + 'There is no description.' => 'Keine Beschreibung vorhanden.', + 'Add a new task' => 'Neue Aufgabe hinzufügen', + 'The username is required' => 'Der Benutzername wird benötigt', + 'The maximum length is %d characters' => 'Die maximale Länge beträgt %d Zeichen', + 'The minimum length is %d characters' => 'Die minimale Länge beträgt %d Zeichen', + 'The password is required' => 'Das Passwort wird benötigt', + 'This value must be an integer' => 'Dieser Wert muss eine ganze Zahl sein', + 'The username must be unique' => 'Der Benutzername muss eindeutig sein', + 'The user id is required' => 'Die Benutzer-ID ist anzugeben', + 'Passwords don\'t match' => 'Passwörter nicht gleich', + 'The confirmation is required' => 'Die Bestätigung ist erforderlich', + 'The project is required' => 'Das Projekt ist anzugeben', + 'The id is required' => 'Die ID ist anzugeben', + 'The project id is required' => 'Die Projekt-ID ist anzugeben', + 'The project name is required' => 'Der Projektname ist anzugeben', + 'The title is required' => 'Der Titel ist anzugeben', + 'Settings saved successfully.' => 'Einstellungen erfolgreich gespeichert.', + 'Unable to save your settings.' => 'Speichern der Einstellungen nicht möglich.', + 'Database optimization done.' => 'Optimieren der Datenbank abgeschlossen.', + 'Your project has been created successfully.' => 'Das Projekt wurde erfolgreich erstellt.', + 'Unable to create your project.' => 'Erstellen des Projekts nicht möglich.', + 'Project updated successfully.' => 'Projekt erfolgreich geändert.', + 'Unable to update this project.' => 'Änderung des Projekts nicht möglich.', + 'Unable to remove this project.' => 'Löschen des Projekts nicht möglich.', + 'Project removed successfully.' => 'Projekt erfolgreich gelöscht.', + 'Project activated successfully.' => 'Projekt erfolgreich aktiviert.', + 'Unable to activate this project.' => 'Aktivieren des Projekts nicht möglich.', + 'Project disabled successfully.' => 'Projekt erfolgreich deaktiviert.', + 'Unable to disable this project.' => 'Deaktivieren des Projekts nicht möglich.', + 'Unable to open this task.' => 'Wiedereröffnung der Aufgabe nicht möglich.', + 'Task opened successfully.' => 'Aufgabe erfolgreich wieder geöffnet.', + 'Unable to close this task.' => 'Abschließen der Aufgabe nicht möglich.', + 'Task closed successfully.' => 'Aufgabe erfolgreich geschlossen.', + 'Unable to update your task.' => 'Aktualisieren der Aufgabe nicht möglich.', + 'Task updated successfully.' => 'Aufgabe erfolgreich aktualisiert.', + 'Unable to create your task.' => 'Erstellen der Aufgabe nicht möglich.', + 'Task created successfully.' => 'Aufgabe erfolgreich erstellt.', + 'User created successfully.' => 'Benutzer erfolgreich erstellt.', + 'Unable to create your user.' => 'Erstellen des Benutzers nicht möglich.', + 'User updated successfully.' => 'Benutzer erfolgreich geändert.', + 'User removed successfully.' => 'Benutzer erfolgreich gelöscht.', + 'Unable to remove this user.' => 'Löschen des Benutzers nicht möglich.', + 'Board updated successfully.' => 'Pinnwand erfolgreich geändert.', + 'Ready' => 'Bereit', + 'Backlog' => 'Ideen', + 'Work in progress' => 'In Arbeit', + 'Done' => 'Erledigt', + 'Application version:' => 'Version:', + 'Id' => 'ID', + 'Public link' => 'Öffentlicher Link', + 'Timezone' => 'Zeitzone', + 'Sorry, I didn\'t find this information in my database!' => 'Diese Information wurde in der Datenbank nicht gefunden!', + 'Page not found' => 'Seite nicht gefunden', + 'Complexity' => 'Komplexität', + 'Task limit' => 'Maximale Anzahl von Aufgaben', + 'Task count' => 'Aufgabenanzahl', + 'User' => 'Benutzer', + 'Comments' => 'Kommentare', + 'Comment is required' => 'Ein Kommentar wird benötigt', + 'Comment added successfully.' => 'Kommentar erfolgreich hinzugefügt.', + 'Unable to create your comment.' => 'Hinzufügen eines Kommentars nicht möglich.', + 'Due Date' => 'Fällig am', + 'Invalid date' => 'Ungültiges Datum', + 'Automatic actions' => 'Automatische Aktionen', + 'Your automatic action has been created successfully.' => 'Die automatische Aktion wurde erfolgreich erstellt.', + 'Unable to create your automatic action.' => 'Erstellen der automatischen Aktion nicht möglich.', + 'Remove an action' => 'Aktion löschen', + 'Unable to remove this action.' => 'Löschen der Aktion nicht möglich.', + 'Action removed successfully.' => 'Aktion erfolgreich gelöscht.', + 'Automatic actions for the project "%s"' => 'Automatische Aktionen für das Projekt "%s"', + 'Add an action' => 'Aktion hinzufügen', + 'Event name' => 'Ereignisname', + 'Action' => 'Aktion', + 'Event' => 'Ereignis', + 'When the selected event occurs execute the corresponding action.' => 'Wenn das gewählte Ereignis eintritt, führe die zugehörige Aktion aus.', + 'Next step' => 'Weiter', + 'Define action parameters' => 'Aktionsparameter definieren', + 'Do you really want to remove this action: "%s"?' => 'Soll diese Aktion wirklich gelöscht werden: "%s"?', + 'Remove an automatic action' => 'Löschen einer automatischen Aktion', + 'Assign the task to a specific user' => 'Aufgabe einem Benutzer zuordnen', + 'Assign the task to the person who does the action' => 'Aufgabe dem Benutzer zuordnen, der die Aktion ausgeführt hat', + 'Duplicate the task to another project' => 'Aufgabe in ein anderes Projekt kopieren', + 'Move a task to another column' => 'Aufgabe in andere Spalte verschieben', + 'Task modification' => 'Aufgabe ändern', + 'Task creation' => 'Aufgabe erstellen', + 'Closing a task' => 'Aufgabe abschließen', + 'Assign a color to a specific user' => 'Einem Benutzer eine Farbe zuordnen', + 'Position' => 'Position', + 'Duplicate to project' => 'In ein anderes Projekt duplizieren', + 'Duplicate' => 'Duplizieren', + 'Link' => 'Link', + 'Comment updated successfully.' => 'Kommentar erfolgreich aktualisiert.', + 'Unable to update your comment.' => 'Aktualisierung des Kommentars nicht möglich.', + 'Remove a comment' => 'Kommentar löschen', + 'Comment removed successfully.' => 'Kommentar erfolgreich gelöscht.', + 'Unable to remove this comment.' => 'Löschen des Kommentars nicht möglich.', + 'Do you really want to remove this comment?' => 'Soll dieser Kommentar wirklich gelöscht werden?', + 'Current password for the user "%s"' => 'Aktuelles Passwort des Benutzers "%s"', + 'The current password is required' => 'Das aktuelle Passwort wird benötigt', + 'Wrong password' => 'Falsches Passwort', + 'Unknown' => 'Unbekannt', + 'Last logins' => 'Letzte Anmeldungen', + 'Login date' => 'Anmeldedatum', + 'Authentication method' => 'Authentisierungsmethode', + 'IP address' => 'IP-Adresse', + 'User agent' => 'User-Agent', + 'Persistent connections' => 'Bestehende Verbindungen', + 'No session.' => 'Keine Sitzung.', + 'Expiration date' => 'Ablaufdatum', + 'Remember Me' => 'Angemeldet bleiben', + 'Creation date' => 'Erstellungsdatum', + 'Everybody' => 'Alle', + 'Open' => 'Offen', + 'Closed' => 'Abgeschlossen', + 'Search' => 'Suchen', + 'Nothing found.' => 'Nichts gefunden.', + 'Due date' => 'Fälligkeitsdatum', + 'Description' => 'Beschreibung', + '%d comments' => '%d Kommentare', + '%d comment' => '%d Kommentar', + 'Email address invalid' => 'Ungültige E-Mail-Adresse', + 'Your external account is not linked anymore to your profile.' => 'Ihr externer Account ist nicht mehr mit Ihrem Profil verbunden.', + 'Unable to unlink your external account.' => 'Externer Account konnte nicht getrennt werden.', + 'External authentication failed' => 'Externe Authentifizierung fehlgeschlagen', + 'Your external account is linked to your profile successfully.' => 'Ihr externer Account wurde erfolgreich mit Ihrem Profil verbunden', + 'Email' => 'E-Mail', + 'Task removed successfully.' => 'Aufgabe erfolgreich gelöscht.', + 'Unable to remove this task.' => 'Löschen der Aufgabe nicht möglich.', + 'Remove a task' => 'Aufgabe löschen', + 'Do you really want to remove this task: "%s"?' => 'Soll diese Aufgabe wirklich gelöscht werden: "%s"?', + 'Assign automatically a color based on a category' => 'Automatisch eine Farbe anhand der Kategorie zuweisen', + 'Assign automatically a category based on a color' => 'Automatisch eine Kategorie anhand der Farbe zuweisen', + 'Task creation or modification' => 'Aufgabe erstellen oder ändern', + 'Category' => 'Kategorie', + 'Category:' => 'Kategorie:', + 'Categories' => 'Kategorien', + 'Your category has been created successfully.' => 'Kategorie erfolgreich erstellt.', + 'This category has been updated successfully.' => 'Kategorie erfolgreich aktualisiert.', + 'Unable to update this category.' => 'Änderung der Kategorie nicht möglich.', + 'Remove a category' => 'Kategorie löschen', + 'Category removed successfully.' => 'Kategorie erfolgreich gelöscht.', + 'Unable to remove this category.' => 'Löschen der Kategorie nicht möglich.', + 'Category modification for the project "%s"' => 'Kategorie für das Projekt "%s" bearbeiten', + 'Category Name' => 'Kategoriename', + 'Add a new category' => 'Neue Kategorie', + 'Do you really want to remove this category: "%s"?' => 'Soll diese Kategorie wirklich gelöscht werden: "%s"?', + 'All categories' => 'Alle Kategorien', + 'No category' => 'Keine Kategorie', + 'The name is required' => 'Der Name ist erforderlich', + 'Remove a file' => 'Datei löschen', + 'Unable to remove this file.' => 'Löschen der Datei nicht möglich.', + 'File removed successfully.' => 'Datei erfolgreich gelöscht.', + 'Attach a document' => 'Dokument anhängen', + 'Do you really want to remove this file: "%s"?' => 'Soll diese Datei wirklich gelöscht werden: "%s"?', + 'Attachments' => 'Anhänge', + 'Edit the task' => 'Aufgabe bearbeiten', + 'Add a comment' => 'Kommentar hinzufügen', + 'Edit a comment' => 'Kommentar bearbeiten', + 'Summary' => 'Zusammenfassung', + 'Time tracking' => 'Zeiterfassung', + 'Estimate:' => 'Geschätzt:', + 'Spent:' => 'Aufgewendet:', + 'Do you really want to remove this sub-task?' => 'Soll diese Teilaufgabe wirklich gelöscht werden?', + 'Remaining:' => 'Verbleibend:', + 'hours' => 'Stunden', + 'estimated' => 'geschätzt', + 'Sub-Tasks' => 'Teilaufgaben', + 'Add a sub-task' => 'Teilaufgabe anlegen', + 'Original estimate' => 'Geschätzter Aufwand', + 'Create another sub-task' => 'Weitere Teilaufgabe anlegen', + 'Time spent' => 'Aufgewendete Zeit', + 'Edit a sub-task' => 'Teilaufgabe bearbeiten', + 'Remove a sub-task' => 'Teilaufgabe löschen', + 'The time must be a numeric value' => 'Zeit nur als nummerische Angabe', + 'Todo' => 'Nicht gestartet', + 'In progress' => 'In Bearbeitung', + 'Sub-task removed successfully.' => 'Teilaufgabe erfolgreich gelöscht.', + 'Unable to remove this sub-task.' => 'Löschen der Teilaufgabe nicht möglich.', + 'Sub-task updated successfully.' => 'Teilaufgabe erfolgreich aktualisiert.', + 'Unable to update your sub-task.' => 'Aktualisieren der Teilaufgabe nicht möglich.', + 'Unable to create your sub-task.' => 'Erstellen der Teilaufgabe nicht möglich.', + 'Maximum size: ' => 'Maximalgröße: ', + 'Display another project' => 'Zu Projekt wechseln', + 'Created by %s' => 'Erstellt durch %s', + 'Tasks Export' => 'Aufgaben exportieren', + 'Start Date' => 'Anfangsdatum', + 'Execute' => 'Ausführen', + 'Task Id' => 'Aufgaben-ID', + 'Creator' => 'Erstellt von', + 'Modification date' => 'Änderungsdatum', + 'Completion date' => 'Abschlussdatum', + 'Clone' => 'duplizieren', + 'Project cloned successfully.' => 'Projekt wurde dupliziert.', + 'Unable to clone this project.' => 'Duplizieren dieses Projekts schlug fehl.', + 'Enable email notifications' => 'E-Mail-Benachrichtigungen einschalten', + 'Task position:' => 'Position der Aufgabe:', + 'The task #%d has been opened.' => 'Die Aufgabe #%d wurde geöffnet.', + 'The task #%d has been closed.' => 'Die Aufgabe #%d wurde geschlossen.', + 'Sub-task updated' => 'Teilaufgabe aktualisiert', + 'Title:' => 'Titel:', + 'Status:' => 'Status:', + 'Assignee:' => 'Zuständigkeit:', + 'Time tracking:' => 'Zeiterfassung:', + 'New sub-task' => 'Neue Teilaufgabe', + 'New attachment added "%s"' => 'Neuer Anhang "%s" wurde hinzugefügt.', + 'New comment posted by %s' => 'Neuer Kommentar verfasst durch %s', + 'New comment' => 'Neuer Kommentar', + 'Comment updated' => 'Kommentar wurde aktualisiert', + 'New subtask' => 'Neue Teilaufgabe', + 'I only want to receive notifications for these projects:' => 'Ich möchte nur für diese Projekte Benachrichtigungen erhalten:', + 'view the task on Kanboard' => 'diese Aufgabe auf dem Kanboard zeigen', + 'Public access' => 'Öffentlicher Zugriff', + 'Disable public access' => 'Öffentlichen Zugriff deaktivieren', + 'Enable public access' => 'Öffentlichen Zugriff aktivieren', + 'Public access disabled' => 'Öffentlicher Zugriff deaktiviert', + 'Move the task to another project' => 'Aufgabe in ein anderes Projekt verschieben', + 'Move to project' => 'In anderes Projekt verschieben', + 'Do you really want to duplicate this task?' => 'Möchten Sie diese Aufgabe wirklich duplizieren?', + 'Duplicate a task' => 'Aufgabe duplizieren', + 'External accounts' => 'Externe Accounts', + 'Account type' => 'Accounttyp', + 'Local' => 'Lokal', + 'Remote' => 'Remote', + 'Enabled' => 'angeschaltet', + 'Disabled' => 'abgeschaltet', + 'Login:' => 'Benutzername:', + 'Full Name:' => 'Vollständiger Name:', + 'Email:' => 'E-Mail:', + 'Notifications:' => 'Benachrichtigungen:', + 'Notifications' => 'Benachrichtigungen', + 'Account type:' => 'Accounttyp:', + 'Edit profile' => 'Profil bearbeiten', + 'Change password' => 'Passwort ändern', + 'Password modification' => 'Passwortänderung', + 'External authentications' => 'Externe Authentisierungsmethoden', + 'Never connected.' => 'Noch nie verbunden.', + 'No external authentication enabled.' => 'Es sind keine externen Authentisierungsmethoden aktiv.', + 'Password modified successfully.' => 'Passwort wurde erfolgreich geändert.', + 'Unable to change the password.' => 'Passwort konnte nicht geändert werden.', + 'Change category' => 'Kategorie ändern', + '%s updated the task %s' => '%s hat die Aufgabe %s aktualisiert', + '%s opened the task %s' => '%s hat die Aufgabe %s geöffnet', + '%s moved the task %s to the position #%d in the column "%s"' => '%s hat die Aufgabe %s auf die Position #%d in der Spalte "%s" verschoben', + '%s moved the task %s to the column "%s"' => '%s hat die Aufgabe %s in die Spalte "%s" verschoben', + '%s created the task %s' => '%s hat die Aufgabe %s angelegt', + '%s closed the task %s' => '%s hat die Aufgabe %s geschlossen', + '%s created a subtask for the task %s' => '%s hat eine Teilaufgabe für die Aufgabe %s angelegt', + '%s updated a subtask for the task %s' => '%s hat eine Teilaufgabe der Aufgabe %s verändert', + 'Assigned to %s with an estimate of %s/%sh' => 'An %s zugewiesen mit einer Schätzung von %s/%s Stunden', + 'Not assigned, estimate of %sh' => 'Nicht zugewiesen, Schätzung von %s Stunden', + '%s updated a comment on the task %s' => '%s hat einen Kommentar der Aufgabe %s aktualisiert', + '%s commented the task %s' => '%s hat die Aufgabe %s kommentiert', + '%s\'s activity' => '%s\'s Aktivität', + 'RSS feed' => 'RSS Feed', + '%s updated a comment on the task #%d' => '%s hat einen Kommentar der Aufgabe #%d aktualisiert', + '%s commented on the task #%d' => '%s hat die Aufgabe #%d kommentiert', + '%s updated a subtask for the task #%d' => '%s hat eine Teilaufgabe der Aufgabe #%d aktualisiert', + '%s created a subtask for the task #%d' => '%s hat eine Teilaufgabe der Aufgabe #%d angelegt', + '%s updated the task #%d' => '%s hat die Aufgabe #%d aktualisiert', + '%s created the task #%d' => '%s hat die Aufgabe #%d angelegt', + '%s closed the task #%d' => '%s hat die Aufgabe #%d geschlossen', + '%s opened the task #%d' => '%s hat die Aufgabe #%d geöffnet', + 'Activity' => 'Aktivität', + 'Default values are "%s"' => 'Die Standardwerte sind "%s"', + 'Default columns for new projects (Comma-separated)' => 'Standardspalten für neue Projekte (Komma getrennt)', + 'Task assignee change' => 'Zuständigkeit geändert', + '%s changed the assignee of the task #%d to %s' => '%s hat die Zuständigkeit der Aufgabe #%d geändert zu %s', + '%s changed the assignee of the task %s to %s' => '%s hat die Zuständigkeit der Aufgabe %s geändert zu %s', + 'New password for the user "%s"' => 'Neues Passwort des Benutzers "%s"', + 'Choose an event' => 'Aktion wählen', + 'Create a task from an external provider' => 'Eine Aufgabe durch einen externen Provider hinzufügen', + 'Change the assignee based on an external username' => 'Zuordnung ändern basierend auf externem Benutzernamen', + 'Change the category based on an external label' => 'Kategorie basierend auf einer externen Kennzeichnung ändern', + 'Reference' => 'Referenz', + 'Label' => 'Kennzeichnung', + 'Database' => 'Datenbank', + 'About' => 'Über', + 'Database driver:' => 'Datenbanktreiber:', + 'Board settings' => 'Pinnwandeinstellungen', + 'Webhook settings' => 'Webhook-Einstellungen', + 'Reset token' => 'Token zurücksetzen', + 'API endpoint:' => 'API-Endpunkt:', + 'Refresh interval for personal board' => 'Aktualisierungsintervall für persönliche Pinnwände', + 'Refresh interval for public board' => 'Aktualisierungsintervall für öffentliche Pinnwände', + 'Task highlight period' => 'Aufgaben-Hervorhebungsdauer', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Dauer (in Sekunden), wie lange eine Aufgabe als kürzlich verändert gilt (0 um diese Funktion zu deaktivieren, standardmäßig 2 Tage)', + 'Frequency in second (60 seconds by default)' => 'Frequenz in Sekunden (standardmäßig 60 Sekunden)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frequenz in Sekunden (0 um diese Funktion zu deaktivieren, standardmäßig 10 Sekunden)', + 'Application URL' => 'Applikations-URL', + 'Token regenerated.' => 'Token wurde neu generiert.', + 'Date format' => 'Datumsformat', + 'ISO format is always accepted, example: "%s" and "%s"' => 'ISO Format wird immer akzeptiert, z.B.: "%s" und "%s"', + 'New personal project' => 'Neues persönliches Projekt', + 'This project is personal' => 'Dies ist ein persönliches Projekt', + 'Add' => 'Hinzufügen', + 'Start date' => 'Startdatum', + 'Time estimated' => 'Geschätzte Zeit', + 'There is nothing assigned to you.' => 'Ihnen ist nichts zugewiesen.', + 'My tasks' => 'Meine Aufgaben', + 'Activity stream' => 'Letzte Aktivitäten', + 'Dashboard' => 'Dashboard', + 'Confirmation' => 'Wiederholung', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Create a comment from an external provider' => 'Kommentar eines externen Providers hinzufügen', + 'Project management' => 'Projektmanagement', + 'Columns' => 'Spalten', + 'Task' => 'Aufgabe', + 'Percentage' => 'Prozentsatz', + 'Number of tasks' => 'Anzahl an Aufgaben', + 'Task distribution' => 'Aufgabenverteilung', + 'Analytics' => 'Analyse', + 'Subtask' => 'Teilaufgabe', + 'User repartition' => 'Benutzerverteilung', + 'Clone this project' => 'Projekt kopieren', + 'Column removed successfully.' => 'Spalte erfolgreich entfernt.', + 'Not enough data to show the graph.' => 'Nicht genügend Daten, um die Grafik zu zeigen.', + 'Previous' => 'Vorherige', + 'The id must be an integer' => 'Die ID muss eine ganze Zahl sein', + 'The project id must be an integer' => 'Der Projekt-ID muss eine ganze Zahl sein', + 'The status must be an integer' => 'Der Status muss eine ganze Zahl sein', + 'The subtask id is required' => 'Die Teilaufgaben-ID ist benötigt', + 'The subtask id must be an integer' => 'Die Teilaufgaben-ID muss eine ganze Zahl sein', + 'The task id is required' => 'Die Aufgaben-ID ist benötigt', + 'The task id must be an integer' => 'Die Aufgaben-ID muss eine ganze Zahl sein', + 'The user id must be an integer' => 'Die Benutzer-ID muss eine ganze Zahl sein', + 'This value is required' => 'Dieser Wert ist erforderlich', + 'This value must be numeric' => 'Dieser Wert muss nummerisch sein', + 'Unable to create this task.' => 'Diese Aufgabe kann nicht erstellt werden', + 'Cumulative flow diagram' => 'Kumulatives Flussdiagramm', + 'Daily project summary' => 'Tägliche Projektzusammenfassung', + 'Daily project summary export' => 'Export der täglichen Projektzusammenfassung', + 'Exports' => 'Exporte', + 'This export contains the number of tasks per column grouped per day.' => 'Dieser Export enthält die Anzahl der Aufgaben pro Spalte nach Tagen gruppiert.', + 'Active swimlanes' => 'Aktive Swimlane', + 'Add a new swimlane' => 'Eine neue Swimlane hinzufügen', + 'Default swimlane' => 'Standard-Swimlane', + 'Do you really want to remove this swimlane: "%s"?' => 'Diese Swimlane wirklich entfernen: "%s"?', + 'Inactive swimlanes' => 'Inaktive Swimlane', + 'Remove a swimlane' => 'Swimlane entfernen', + 'Swimlane modification for the project "%s"' => 'Swimlane-Änderung für das Projekt "%s"', + 'Swimlane removed successfully.' => 'Swimlane erfolgreich entfernt.', + 'Swimlanes' => 'Swimlanes', + 'Swimlane updated successfully.' => 'Swimlane erfolgreich geändert.', + 'Unable to remove this swimlane.' => 'Es ist nicht möglich, die Swimlane zu entfernen.', + 'Unable to update this swimlane.' => 'Es ist nicht möglich, die Swimlane zu ändern.', + 'Your swimlane has been created successfully.' => 'Die Swimlane wurde erfolgreich angelegt.', + '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)', + 'Integrations' => 'Integration', + 'Integration with third-party services' => 'Integration von externen Diensten', + 'Subtask Id' => 'Teilaufgaben-ID', + 'Subtasks' => 'Teilaufgaben', + 'Subtasks Export' => 'Export von Teilaufgaben', + 'Task Title' => 'Aufgaben-Titel', + 'Untitled' => 'unbetitelt', + 'Application default' => 'Anwendungsstandard', + 'Language:' => 'Sprache:', + 'Timezone:' => 'Zeitzone:', + 'All columns' => 'Alle Spalten', + 'Next' => 'Nächste', + '#%d' => 'Nr %d', + 'All swimlanes' => 'Alle Swimlanes', + 'All colors' => 'Alle Farben', + 'Moved to column %s' => 'In Spalte %s verschoben', + 'User dashboard' => 'Benutzer-Dashboard', + 'Allow only one subtask in progress at the same time for a user' => 'Erlaube nur eine Teilaufgabe pro Benutzer zu bearbeiten', + 'Edit column "%s"' => 'Spalte "%s" bearbeiten', + 'Select the new status of the subtask: "%s"' => 'Wähle einen neuen Status für Teilaufgabe: "%s"', + 'Subtask timesheet' => 'Teilaufgaben Zeiterfassung', + 'There is nothing to show.' => 'Es ist nichts zum Anzeigen vorhanden.', + 'Time Tracking' => 'Zeiterfassung', + 'You already have one subtask in progress' => 'Bereits eine Teilaufgabe in Bearbeitung', + 'Which parts of the project do you want to duplicate?' => 'Welcher Teil des Projekts soll kopiert werden?', + 'Disallow login form' => 'Verbiete Login-Formular', + 'Start' => 'Start', + 'End' => 'Ende', + 'Task age in days' => 'Aufgabenalter in Tagen', + 'Days in this column' => 'Tage in dieser Spalte', + '%dd' => '%dT', + 'Add a new link' => 'Neue Verbindung hinzufügen', + 'Do you really want to remove this link: "%s"?' => 'Die Verbindung "%s" wirklich löschen?', + 'Do you really want to remove this link with task #%d?' => 'Die Verbindung mit der Aufgabe #%d wirklich löschen?', + 'Field required' => 'Feld erforderlich', + 'Link added successfully.' => 'Verbindung erfolgreich hinzugefügt.', + 'Link updated successfully.' => 'Verbindung erfolgreich aktualisiert.', + 'Link removed successfully.' => 'Verbindung erfolgreich gelöscht.', + 'Link labels' => 'Verbindungsbeschriftung', + 'Link modification' => 'Verbindung ändern', + 'Opposite label' => 'Gegenteil', + 'Remove a link' => 'Verbindung entfernen', + 'The labels must be different' => 'Die Beschriftung muss unterschiedlich sein', + 'There is no link.' => 'Es gibt keine Verbindung', + 'This label must be unique' => 'Die Beschriftung muss einzigartig sein', + 'Unable to create your link.' => 'Verbindung kann nicht erstellt werden.', + 'Unable to update your link.' => 'Verbindung kann nicht aktualisiert werden.', + 'Unable to remove this link.' => 'Verbindung kann nicht entfernt werden', + 'relates to' => 'gehört zu', + 'blocks' => 'blockiert', + 'is blocked by' => 'ist blockiert von', + 'duplicates' => 'doppelt', + 'is duplicated by' => 'ist gedoppelt von', + 'is a child of' => 'ist ein untergeordnetes Element von', + 'is a parent of' => 'ist ein übergeordnetes Element von', + 'targets milestone' => 'betrifft Meilenstein', + 'is a milestone of' => 'ist ein Meilenstein von', + 'fixes' => 'behebt', + 'is fixed by' => 'wird behoben von', + 'This task' => 'Diese Aufgabe', + '<1h' => '<1Std', + '%dh' => '%dStd', + 'Expand tasks' => 'Aufgaben aufklappen', + 'Collapse tasks' => 'Aufgaben zusammenklappen', + 'Expand/collapse tasks' => 'Aufgaben auf/zuklappen', + 'Close dialog box' => 'Dialog schließen', + 'Submit a form' => 'Formular abschicken', + 'Board view' => 'Pinnwand Ansicht', + 'Keyboard shortcuts' => 'Tastaturkürzel', + 'Open board switcher' => 'Pinnwandauswahl öffnen', + 'Application' => 'Anwendung', + 'Compact view' => 'Kompaktansicht', + 'Horizontal scrolling' => 'Horizontales Scrollen', + 'Compact/wide view' => 'Kompakt/Breite-Ansicht', + 'Currency' => 'Währung', + 'Personal project' => 'privates Projekt', + 'AUD - Australian Dollar' => 'AUD - Australische Dollar', + 'CAD - Canadian Dollar' => 'CAD - Kanadische Dollar', + 'CHF - Swiss Francs' => 'CHF - Schweizer Franken', + 'Custom Stylesheet' => 'benutzerdefiniertes Stylesheet', + 'EUR - Euro' => 'EUR - Euro', + 'GBP - British Pound' => 'GBP - Britische Pfund', + 'INR - Indian Rupee' => 'INR - Indische Rupien', + 'JPY - Japanese Yen' => 'JPY - Japanische Yen', + 'NZD - New Zealand Dollar' => 'NZD - Neuseeland-Dollar', + 'PEN - Peruvian Sol' => 'PEN - Peruanischer Sol', + 'RSD - Serbian dinar' => 'RSD – Serbischer Dinar', + 'CNY - Chinese Yuan' => 'CNY - Chinesischer Yuan', + 'USD - US Dollar' => 'USD – US-Dollar', + 'VES - Venezuelan Bolívar' => 'VES - Venezolanischer Bolívar', + 'Destination column' => 'Zielspalte', + 'Move the task to another column when assigned to a user' => 'Aufgabe in eine andere Spalte verschieben, wenn ein Benutzer 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', + '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', + 'Reference currency' => 'Referenzwährung', + 'The currency rate has been added successfully.' => 'Der Währungskurs wurde erfolgreich hinzugefügt.', + 'Unable to add this currency rate.' => 'Währungskurs konnte nicht hinzugefügt werden', + 'Webhook URL' => 'Webhook-URL', + '%s removed the assignee of the task %s' => '%s Zuordnung für die Aufgabe %s entfernen', + 'Information' => 'Information', + 'Check two factor authentication code' => 'Prüfe Zwei-Faktor-Authentifizierungscode', + 'The two factor authentication code is not valid.' => 'Der Zwei-Faktor-Authentifizierungscode ist ungültig.', + 'The two factor authentication code is valid.' => 'Der Zwei-Faktor-Authentifizierungscode ist gültig.', + 'Code' => 'Code', + 'Two factor authentication' => 'Zwei-Faktor-Authentifizierung', + 'This QR code contains the key URI: ' => 'Dieser QR-Code beinhaltet die Schlüssel-URI: ', + 'Check my code' => 'Überprüfe meinen Code', + 'Secret key: ' => 'Geheimer Schlüssel: ', + 'Test your device' => 'Testen Sie Ihr Gerät', + 'Assign a color when the task is moved to a specific column' => 'Weise eine Farbe zu, wenn die Aufgabe zu einer bestimmten Spalte bewegt wird', + '%s via Kanboard' => '%s via Kanboard', + 'Burndown chart' => 'Burndown-Diagramm', + 'This chart show the task complexity over the time (Work Remaining).' => 'Dieses Diagramm zeigt die Aufgabenkomplexität über den Faktor Zeit (Verbleibende Arbeit).', + 'Screenshot taken %s' => 'Screenshot aufgenommen %s', + 'Add a screenshot' => 'Screenshot hinzufügen', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Nehmen Sie einen Screenshot auf und drücken STRG+V oder ⌘+V um ihn hier einzufügen.', + 'Screenshot uploaded successfully.' => 'Screenshot erfolgreich hochgeladen.', + 'SEK - Swedish Krona' => 'SEK - Schwedische Kronen', + 'Identifier' => 'Identifikator', + 'Disable two factor authentication' => 'Deaktiviere Zwei-Faktor-Authentifizierung', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Wollen Sie wirklich für folgenden Benutzer die Zwei-Faktor-Authentifizierung deaktivieren: "%s"?', + 'Edit link' => 'Verbindung bearbeiten', + 'Start to type task title...' => 'Beginne mit der Titeleingabe...', + 'A task cannot be linked to itself' => 'Eine Aufgabe kann nicht mit sich selber verbunden werden', + 'The exact same link already exists' => 'Diese Verbindung existiert bereits', + 'Recurrent task is scheduled to be generated' => 'Wiederkehrende Aufgabe ist zur Generierung eingeplant', + 'Score' => 'Wertung', + 'The identifier must be unique' => 'Der Schlüssel muss einzigartig sein', + 'This linked task id doesn\'t exists' => 'Die verbundene Aufgabe existiert nicht', + 'This value must be alphanumeric' => 'Der Wert muss alphanumerisch sein', + 'Edit recurrence' => 'Wiederholung bearbeiten', + 'Generate recurrent task' => 'Wiederkehrende Aufgabe generieren', + 'Trigger to generate recurrent task' => 'Auslöser für wiederkehrende Aufgabe', + 'Factor to calculate new due date' => 'Faktor zur Berechnung für neues Ablaufdatum', + 'Timeframe to calculate new due date' => 'Zeitfenster zur Berechnung für neues Ablaufdatum', + 'Base date to calculate new due date' => 'Basisdatum zur Berechnung für neues Ablaufdatum', + 'Action date' => 'Aktionsdatum', + 'Base date to calculate new due date: ' => 'Basisdatum zur Berechnung für neues Ablaufdatum: ', + 'This task has created this child task: ' => 'Diese Aufgabe hat diese Teilaufgabe erstellt: ', + 'Day(s)' => 'Tag(e)', + 'Existing due date' => 'Existierendes Ablaufdatum', + 'Factor to calculate new due date: ' => 'Faktor zur Berechnung für neues Ablaufdatum: ', + 'Month(s)' => 'Monat(e)', + 'This task has been created by: ' => 'Diese Aufgabe wurde erstellt von: ', + 'Recurrent task has been generated:' => 'Wiederkehrende Aufgabe wurde erstellt:', + 'Timeframe to calculate new due date: ' => 'Zeitfenster zur Berechnung für neues Ablaufdatum: ', + 'Trigger to generate recurrent task: ' => 'Auslöser für wiederkehrende Aufgabe: ', + 'When task is closed' => 'Wenn Aufgabe geschlossen wird', + 'When task is moved from first column' => 'Wenn Aufgabe von erster Spalte verschoben wird', + 'When task is moved to last column' => 'Wenn Aufgabe in letzte Spalte verschoben wird', + 'Year(s)' => 'Jahr(e)', + 'Project settings' => 'Projekteinstellungen', + 'Automatically update the start date' => 'Beginndatum automatisch aktualisieren', + 'iCal feed' => 'iCal Feed', + 'Preferences' => 'Einstellungen', + 'Security' => 'Sicherheit', + 'Two factor authentication disabled' => 'Zwei-Faktor-Authentifizierung deaktiviert', + 'Two factor authentication enabled' => 'Zwei-Faktor-Authentifizierung aktiviert', + 'Unable to update this user.' => 'Benutzer kann nicht bearbeitet werden', + 'There is no user management for personal projects.' => 'Es gibt keine Benutzerverwaltung für persönliche Projekte', + 'User that will receive the email' => 'Empfänger der E-Mail', + 'Email subject' => 'E-Mail-Betreff', + 'Date' => 'Datum', + 'Add a comment log when moving the task between columns' => 'Kommentar hinzufügen, wenn Aufgabe in andere Spalte verschoben wird', + 'Move the task to another column when the category is changed' => 'Aufgabe in andere Spalte verschieben, wenn Kategorie geändert wird', + 'Send a task by email to someone' => 'Aufgabe per E-Mail versenden', + 'Reopen a task' => 'Aufgabe wieder öffnen', + 'Notification' => 'Benachrichtigungen', + '%s moved the task #%d to the first swimlane' => '%s hat die Aufgabe #%d in die erste Swimlane verschoben', + 'Swimlane' => 'Swimlane', + '%s moved the task %s to the first swimlane' => '%s hat die Aufgabe %s in die erste Swimlane verschoben', + '%s moved the task %s to the swimlane "%s"' => '%s hat die Aufgaben %s in die Swimlane "%s" verschoben', + 'This report contains all subtasks information for the given date range.' => 'Der Bericht beinhaltet alle Teilaufgaben im gewählten Zeitraum', + 'This report contains all tasks information for the given date range.' => 'Der Bericht beinhaltet alle Aufgaben im gewählten Zeitraum', + 'Project activities for %s' => 'Projektaktivitäten für %s', + 'view the board on Kanboard' => 'Pinnwand in Kanboard anzeigen', + 'The task has been moved to the first swimlane' => 'Die Aufgabe wurde in die erste Swimlane verschoben', + 'The task has been moved to another swimlane:' => 'Die Aufgaben wurde in eine andere Swimlane verschoben:', + 'New title: %s' => 'Neuer Titel: %s', + 'The task is not assigned anymore' => 'Die Aufgabe ist nicht mehr zugewiesen', + 'New assignee: %s' => 'Neue Zuordnung: %s', + 'There is no category now' => 'Es gibt keine Kategorie im Moment', + 'New category: %s' => 'Neue Kategorie: %s', + 'New color: %s' => 'Neue Farbe: %s', + 'New complexity: %d' => 'Neue Komplexität: %d', + 'The due date has been removed' => 'Das Ablaufdatum wurde entfernt', + 'There is no description anymore' => 'Es gibt keine Beschreibung mehr', + 'Recurrence settings has been modified' => 'Die Einstellungen für Wiederholung wurden geändert', + 'Time spent changed: %sh' => 'Verbrauchte Zeit geändert: %sh', + 'Time estimated changed: %sh' => 'Geschätzte Zeit geändert: %sh', + 'The field "%s" has been updated' => 'Das Feld "%s" wurde verändert', + 'The description has been modified:' => 'Die Beschreibung wurde geändert:', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Soll die Aufgabe "%s" wirklich geschlossen werden? (einschließlich Teilaufgaben)', + 'I want to receive notifications for:' => 'Ich möchte Benachrichtigungen erhalten für:', + 'All tasks' => 'Alle Aufgaben', + 'Only for tasks assigned to me' => 'nur mir zugeordnete Aufgaben', + 'Only for tasks created by me' => 'nur von mir erstellte Aufgaben', + 'Only for tasks created by me and tasks assigned to me' => 'nur mir zugeordnete und von mir erstellte Aufgaben', + '%%Y-%%m-%%d' => '%%d.%%m.%%Y', + 'Total for all columns' => 'Gesamt für alle Spalten', + 'You need at least 2 days of data to show the chart.' => 'Es werden mindestens 2 Tage zur Darstellung benötigt', + '<15m' => '<15min', + '<30m' => '<30min', + 'Stop timer' => 'Stoppe Timer', + 'Start timer' => 'Starte Timer', + 'My activity stream' => 'Aktivitätsstream', + 'Search tasks' => 'Suche nach Aufgaben', + 'Reset filters' => 'Filter zurücksetzen', + 'My tasks due tomorrow' => 'Meine morgen fälligen Aufgaben', + 'Tasks due today' => 'Heute fällige Aufgaben', + 'Tasks due tomorrow' => 'Morgen fällige Aufgaben', + 'Tasks due yesterday' => 'Gestern fällige Aufgaben', + 'Closed tasks' => 'Abgeschlossene Aufgaben', + 'Open tasks' => 'Offene Aufgaben', + 'Not assigned' => 'Nicht zugewiesen', + 'View advanced search syntax' => 'Zur erweiterten Suchsyntax', + 'Overview' => 'Überblick', + 'Board/Calendar/List view' => 'Board-/Kalender-/Listen-Ansicht', + 'Switch to the board view' => 'Zur Board-Ansicht', + 'Switch to the list view' => 'Zur Listen-Ansicht', + 'Go to the search/filter box' => 'Zum Such- und Filterfeld', + 'There is no activity yet.' => 'Es gibt bislang keine Aktivitäten.', + 'No tasks found.' => 'Keine Aufgaben gefunden.', + 'Keyboard shortcut: "%s"' => 'Tastaturkürzel: "%s"', + 'List' => 'Liste', + 'Filter' => 'Filter', + 'Advanced search' => 'Fortgeschrittene Suche', + 'Example of query: ' => 'Beispiel einer Abfrage: ', + 'Search by project: ' => 'Suche nach Projekt: ', + 'Search by column: ' => 'Suche nach Spalte: ', + 'Search by assignee: ' => 'Suche nach zugeordnetem Benutzer: ', + 'Search by color: ' => 'Suche nach Farbe: ', + 'Search by category: ' => 'Suche nach Kategorie: ', + 'Search by description: ' => 'Suche nach Beschreibung: ', + 'Search by due date: ' => 'Suche nach Fälligkeitsdatum: ', + 'Average time spent in each column' => 'Durchschnittszeit in jeder Spalte', + 'Average time spent' => 'Durchschnittlicher Zeitverbrauch', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Dieses Diagramm zeigt die durchschnittliche Zeit in jeder Spalte der letzten %d Aufgaben.', + 'Average Lead and Cycle time' => 'Durchschnittliche Zyklus- und Durchlaufzeit', + 'Average lead time: ' => 'Durchschnittliche Durchlaufzeit: ', + 'Average cycle time: ' => 'Durchschnittliche Zykluszeit: ', + 'Cycle Time' => 'Zykluszeit', + 'Lead Time' => 'Durchlaufzeit', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Das Diagramm zeigt die durchschnittliche Durchlauf- und Zykluszeit der letzten %d Aufgaben über die Zeit an.', + 'Average time into each column' => 'Durchschnittszeit in jeder Spalte', + 'Lead and cycle time' => 'Durchlauf- und Zykluszeit', + 'Lead time: ' => 'Durchlaufzeit: ', + 'Cycle time: ' => 'Zykluszeit: ', + 'Time spent in each column' => 'zeit verbracht in jeder Spalte', + 'The lead time is the duration between the task creation and the completion.' => 'Die Durchlaufzeit ist die Dauer zwischen Erstellung und Fertigstellung.', + 'The cycle time is the duration between the start date and the completion.' => 'Die Zykluszeit ist die Dauer zwischen Start und Fertigstellung.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Wenn die Aufgabe nicht geschlossen ist, wird die aktuelle Zeit statt der Fertigstellung verwendet.', + 'Set the start date automatically' => 'Setze Startdatum automatisch', + 'Edit Authentication' => 'Authentifizierung bearbeiten', + 'Remote user' => 'Remote-Benutzer', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Remote-Benutzer haben kein Passwort in der Kanboard Datenbank, Beispiel: LDAP, Google und Github Accounts', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Wenn die Box "Verbiete Login-Formular" angeschaltet ist, werden Eingaben in das Login Formular ignoriert.', + 'Default task color' => 'Voreingestellte Aufgabenfarbe', + 'This feature does not work with all browsers.' => 'Diese Funktion funktioniert nicht mit allen Browsern', + 'There is no destination project available.' => 'Es ist kein Zielprojekt vorhanden.', + 'Trigger automatically subtask time tracking' => 'Teilaufgaben Zeiterfassung automatisch starten', + 'Include closed tasks in the cumulative flow diagram' => 'Geschlossen Aufgaben ins kumulative Flussdiagramm einschließen', + 'Current swimlane: %s' => 'Aktuelle Swimlane: %s', + 'Current column: %s' => 'Aktuelle Spalte: %s', + 'Current category: %s' => 'Aktuelle Kategorie: %s', + 'no category' => 'keine Kategorie', + 'Current assignee: %s' => 'Aktuelle Zuordnung: %s', + 'not assigned' => 'nicht zugeordnet', + 'Author:' => 'Autor:', + 'contributors' => 'Mitwirkende', + 'License:' => 'Lizenz:', + 'License' => 'Lizenz', + 'Enter the text below' => 'Text unten eingeben', + 'Start date:' => 'Startdatum:', + 'Due date:' => 'Ablaufdatum:', + 'People who are project managers' => 'Benutzer die Projektmanager sind', + 'People who are project members' => 'Benutzer die Projektmitglieder sind', + 'NOK - Norwegian Krone' => 'NOK - Norwegische Kronen', + 'Show this column' => 'Spalte anzeigen', + 'Hide this column' => 'Spalte verstecken', + 'End date' => 'Endedatum', + 'Users overview' => 'Benutzerübersicht', + 'Members' => 'Mitglieder', + 'Shared project' => 'Geteiltes Projekt', + 'Project managers' => 'Projektmanager', + 'Projects list' => 'Projektliste', + 'End date:' => 'Endedatum:', + 'Change task color when using a specific task link' => 'Aufgabefarbe ändern bei bestimmter Aufgabenverbindung', + 'Task link creation or modification' => 'Aufgabenverbindung erstellen oder bearbeiten', + 'Milestone' => 'Meilenstein', + 'Reset the search/filter box' => 'Suche/Filter-Box zurücksetzen', + 'Documentation' => 'Dokumentation', + 'Author' => 'Autor', + 'Version' => 'Version', + 'Plugins' => 'Plugins', + 'There is no plugin loaded.' => 'Es ist kein Plugin geladen.', + 'My notifications' => 'Meine Benachrichtigungen', + 'Custom filters' => 'Benutzerdefinierte Filter', + 'Your custom filter has been created successfully.' => 'Benutzerdefinierten Filter erfolgreich erstellt.', + 'Unable to create your custom filter.' => 'Benutzerdefinierter Filter konnte nicht erstellt werden.', + 'Custom filter removed successfully.' => 'Benutzerdefinierten Filter erfolgreich entfernt.', + 'Unable to remove this custom filter.' => 'Benutzerdefinierten Filter konnte nicht entfernt werden.', + 'Edit custom filter' => 'Benutzerdefinierten Filter bearbeiten', + 'Your custom filter has been updated successfully.' => 'Benutzerdefinierten Filter erfolgreich bearbeitet.', + 'Unable to update custom filter.' => 'Benutzerdefinierter Filter konnte nicht geändert werden.', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Neuer Anhang für Aufgabe #%d: %s', + 'New comment on task #%d' => 'Neuer Kommentar für Aufgabe #%d', + 'Comment updated on task #%d' => 'Kommentar geändert für Aufgabe #%d', + 'New subtask on task #%d' => 'Neue Teilaufgabe für Aufgabe #%d', + 'Subtask updated on task #%d' => 'Teilaufgabe geändert für Aufgabe #%d', + 'New task #%d: %s' => 'Neue Aufgabe #%d: %s', + 'Task updated #%d' => 'Aufgabe bearbeitet #%d', + 'Task #%d closed' => 'Aufgabe #%d geschlossen', + 'Task #%d opened' => 'Aufgabe #%d eröffnet', + 'Column changed for task #%d' => 'Spalte geändert von Aufgabe #%d', + 'New position for task #%d' => 'Neue Position für Aufgabe #%d', + 'Swimlane changed for task #%d' => 'Neue Swimlane für Aufgabe #%d', + 'Assignee changed on task #%d' => 'Neue Zuordnung für Aufgabe #%d ', + '%d overdue tasks' => '%d überfällige Aufgaben', + 'No notification.' => 'Keine neuen Benachrichtigungen', + 'Mark all as read' => 'Alles als gelesen markieren', + 'Mark as read' => 'Als gelesen markieren', + 'Total number of tasks in this column across all swimlanes' => 'Anzahl an Aufgaben in dieser Spalte über alle Swimlanes', + 'Collapse swimlane' => 'Swimlane einklappen', + 'Expand swimlane' => 'Swimlane ausklappen', + 'Add a new filter' => 'Neuen Filter hinzufügen', + 'Share with all project members' => 'Mit allen Projektmitgliedern teilen.', + 'Shared' => 'Geteilt', + 'Owner' => 'Eigentümer', + 'Unread notifications' => 'Ungelesene Benachrichtigungen', + 'Notification methods:' => 'Benachrichtigungs-Methoden:', + 'Unable to read your file' => 'Die Datei kann nicht gelesen werden', + '%d task(s) have been imported successfully.' => '%d Aufgabe(n) wurde(n) erfolgreich importiert', + 'Nothing has been imported!' => 'Es wurde nichts importiert!', + 'Import users from CSV file' => 'Importiere Benutzer aus CSV Datei', + '%d user(s) have been imported successfully.' => '%d Benutzer wurde(n) erfolgreich importiert.', + 'Comma' => 'Komma', + 'Semi-colon' => 'Semikolon', + 'Tab' => 'Tabulator', + 'Vertical bar' => 'senkrechter Strich', + 'Double Quote' => 'Doppelte Anführungszeichen', + 'Single Quote' => 'Einfache Anführungszeichen', + '%s attached a file to the task #%d' => '%s hat eine Datei zur Aufgabe #%d hinzugefügt', + 'There is no column or swimlane activated in your project!' => 'Es ist keine Spalte oder Swimlane in Ihrem Projekt aktiviert!', + 'Append filter (instead of replacement)' => 'Filter anhängen (statt zu ersetzen)', + 'Append/Replace' => 'Anhängen/Ersetzen', + 'Append' => 'Anhängen', + 'Replace' => 'Ersetzen', + 'Import' => 'Import', + 'Change sorting' => 'Sortierung ändern', + 'Tasks Importation' => 'Aufgaben Import', + 'Delimiter' => 'Trennzeichen', + 'Enclosure' => 'Textbegrenzer', + 'CSV File' => 'CSV Datei', + 'Instructions' => 'Anweisungen', + 'Your file must use the predefined CSV format' => 'Ihre Datei muss das vorgegebene CSV Format haben', + 'Your file must be encoded in UTF-8' => 'Ihre Datei muss UTF-8 kodiert sein', + 'The first row must be the header' => 'Die erste Zeile muss die Kopfzeile sein', + 'Duplicates are not verified for you' => 'Duplikate werden nicht für Sie geprüft', + 'The due date must use the ISO format: YYYY-MM-DD' => 'Das Fälligkeitsdatum muss das ISO Format haben: YYYY-MM-DD', + 'Download CSV template' => 'CSV Vorlage herunterladen', + 'No external integration registered.' => 'Keine externe Integration registriert', + 'Duplicates are not imported' => 'Duplikate wurden nicht importiert', + 'Usernames must be lowercase and unique' => 'Benutzernamen müssen in Kleinbuchstaben und eindeutig sein', + 'Passwords will be encrypted if present' => 'Passwörter werden verschlüsselt wenn vorhanden', + '%s attached a new file to the task %s' => '%s hat eine neue Datei zur Aufgabe %s hinzugefügt', + 'Link type' => 'Verbindungstyp', + 'Assign automatically a category based on a link' => 'Linkbasiert eine Kategorie automatisch zuordnen', + 'BAM - Konvertible Mark' => 'BAM - Konvertible Mark', + 'Assignee Username' => 'Benutzername des Zuständigen', + 'Assignee Name' => 'Name des Zuständigen', + 'Groups' => 'Gruppen', + 'Members of %s' => 'Mitglied von %s', + 'New group' => 'Neue Gruppe', + 'Group created successfully.' => 'Gruppe erfolgreich angelegt.', + 'Unable to create your group.' => 'Gruppe konnte nicht angelegt werden', + 'Edit group' => 'Gruppe bearbeiten', + 'Group updated successfully.' => 'Gruppe erfolgreich aktualisiert', + 'Unable to update your group.' => 'Gruppe konnte nicht aktualisiert werden', + 'Add group member to "%s"' => 'Gruppenmitglied zu "%s" hinzufügen', + 'Group member added successfully.' => 'Gruppenmitglied erfolgreich hinzugefügt', + 'Unable to add group member.' => 'Gruppenmitglied konnte nicht hinzugefügt werden.', + 'Remove user from group "%s"' => 'Benutzer aus Gruppe "%s" löschen', + 'User removed successfully from this group.' => 'Benutzer erfolgreich aus dieser Gruppe gelöscht.', + 'Unable to remove this user from the group.' => 'Benutzer konnte nicht aus dieser Gruppe gelöscht werden.', + 'Remove group' => 'Gruppe löschen', + 'Group removed successfully.' => 'Gruppe erfolgreich gelöscht.', + 'Unable to remove this group.' => 'Gruppe konnte nicht gelöscht werden.', + 'Project Permissions' => 'Projekt Berechtigungen', + 'Manager' => 'Manager', + 'Project Manager' => 'Projekt Manager', + 'Project Member' => 'Projekt Mitglied', + 'Project Viewer' => 'Projekt Betrachter', + 'Your account is locked for %d minutes' => 'Ihr Zugang wurde für %d Minuten gesperrt', + 'Invalid captcha' => 'Ungültiges Captcha', + 'The name must be unique' => 'Der Name muss eindeutig sein', + 'View all groups' => 'Alle Gruppen anzeigen', + 'There is no user available.' => 'Es ist kein Benutzer verfügbar.', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Wollen Sie den Benutzer "%s" wirklich aus der Gruppe "%s" löschen?', + 'There is no group.' => 'Es gibt keine Gruppe.', + 'Add group member' => 'Gruppenmitglied hinzufügen', + 'Do you really want to remove this group: "%s"?' => 'Wollen Sie die Gruppe "%s" wirklich löschen?', + 'There is no user in this group.' => 'Es gibt keinen Benutzer in dieser Gruppe.', + 'Permissions' => 'Berechtigungen', + 'Allowed Users' => 'Berechtigte Benutzer', + 'No specific user has been allowed.' => 'Keine Benutzer mit ausdrücklicher Berechtigung.', + 'Role' => 'Rolle', + 'Enter user name...' => 'Geben Sie den Benutzernamen ein...', + 'Allowed Groups' => 'Berechtigte Gruppen', + 'No group has been allowed.' => 'Keine Gruppen mit ausdrücklicher Berechtigung.', + 'Group' => 'Gruppe', + 'Group Name' => 'Gruppenname', + 'Enter group name...' => 'Geben Sie den Gruppennamen ein...', + 'Role:' => 'Rolle:', + 'Project members' => 'Projektmitglieder', + '%s mentioned you in the task #%d' => '%s erwähnte Sie in Aufgabe #%d', + '%s mentioned you in a comment on the task #%d' => '%s erwähnte Sie in einem Kommentar zur Aufgabe #%d', + 'You were mentioned in the task #%d' => 'Sie wurden in der Aufgabe #%d erwähnt', + 'You were mentioned in a comment on the task #%d' => 'Sie wurden in einem Kommentar zur Aufgabe #%d erwähnt', + 'Estimated hours: ' => 'Erwarteter Zeitaufwand (Stunden): ', + 'Actual hours: ' => 'Tatsächlich aufgewendete Stunden: ', + 'Hours Spent' => 'Stunden aufgewendet', + 'Hours Estimated' => 'Stunden erwartet', + 'Estimated Time' => 'Erwartete Zeit', + 'Actual Time' => 'Aktuelle Zeit', + 'Estimated vs actual time' => 'Erwarteter vs. tatsächlicher Zeitaufwand', + 'RUB - Russian Ruble' => 'RUB - Russische Rubel', + 'Assign the task to the person who does the action when the column is changed' => 'Aufgabe der Person zuordnen, die die Aktion durchführt, wenn die Spalte geändert wird', + 'Close a task in a specific column' => 'Schließe eine Aufgabe in einer bestimmten Spalte', + 'Time-based One-time Password Algorithm' => 'Zeitbasierter Einmalpasswort Algorithmus', + 'Two-Factor Provider: ' => '2FA Anbieter: ', + 'Disable two-factor authentication' => 'Zwei-Faktor-Authentifizierung deaktivieren', + 'Enable two-factor authentication' => 'Zwei-Faktor-Authentifizierung aktivieren', + 'There is no integration registered at the moment.' => 'Derzeit ist kein externer Dienst registriert.', + 'Password Reset for Kanboard' => 'Zurücksetzen des Passwortes für Kanboard', + 'Forgot password?' => 'Passwort vergessen?', + 'Enable "Forget Password"' => 'Passwortrücksetzung aktivieren', + 'Password Reset' => 'Passwort zurücksetzen', + 'New password' => 'Neues Passwort', + 'Change Password' => 'Passwort ändern', + 'To reset your password click on this link:' => 'Bitte auf den Link klicken, um Ihr Passwort zurückzusetzen.', + 'Last Password Reset' => 'Verlauf der Passwortrücksetzung', + 'The password has never been reinitialized.' => 'Das Passwort wurde noch nie zurückgesetzt.', + 'Creation' => 'Erstellung', + 'Expiration' => 'Ablauf', + 'Password reset history' => 'Verlauf Passwortrücksetzung', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Alle Aufgaben der Spalte "%s" und der Swimlane "%s" wurden erfolgreich geschlossen', + 'Do you really want to close all tasks of this column?' => 'Wollen Sie wirklich alle Aufgaben in dieser Spalte schließen?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d Aufgabe(n) in der Spalte "%s" und in der Swimlane "%s" werden geschlossen.', + 'Close all tasks in this column and this swimlane' => 'Alle Aufgaben in dieser Spalte schließen', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Kein Plugin hat eine Projekt-Benachrichtigungsmethode registriert. Sie können individuelle Meldungen in Ihrem Benutzerprofil konfigurieren', + 'My dashboard' => 'Mein Dashboard', + 'My profile' => 'Mein Profil', + 'Project owner: ' => 'Projekt-Besitzer: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'Die Projekt-Kennung ist optional und muss alphanumerisch sein, beispielsweise: MYPROJECT.', + 'Project owner' => 'Projekt-Besitzer', + 'Personal projects do not have users and groups management.' => 'Private Projekte haben kein Benutzer- und Gruppen-Management.', + 'There is no project member.' => 'Es gibt kein Projekt-Mitglied.', + 'Priority' => 'Priorität', + 'Task priority' => 'Aufgaben-Priorität', + 'General' => 'Allgemein', + 'Dates' => 'Daten', + 'Default priority' => 'Standard-Priorität', + 'Lowest priority' => 'Niedrigste Priorität', + 'Highest priority' => 'Höchste Priorität', + 'Close a task when there is no activity' => 'Schließe eine Aufgabe, wenn keine Aktivitäten vorhanden sind', + 'Duration in days' => 'Dauer in Tagen', + 'Send email when there is no activity on a task' => 'Versende eine E-Mail, wenn keine Aktivitäten an einer Aufgabe vorhanden sind', + 'Unable to fetch link information.' => 'Kann keine Informationen über Verbindungen holen', + 'Daily background job for tasks' => 'Tägliche Hintergrundarbeit für Aufgaben', + 'Auto' => 'Auto', + 'Related' => 'Verbunden', + 'Attachment' => 'Anhang', + 'Web Link' => 'Weblink', + 'External links' => 'Externe Verbindungen', + 'Add external link' => 'Externe Verbindung hinzufügen', + 'Type' => 'Typ', + 'Dependency' => 'Abhängigkeit', + 'Add internal link' => 'Interne Verbindung hinzufügen', + 'Add a new external link' => 'Füge eine neue externe Verbindung hinzu', + 'Edit external link' => 'Externe Verbindung bearbeiten', + 'External link' => 'Externe Verbindung', + 'Copy and paste your link here...' => 'Kopieren Sie Ihren Link hierher...', + 'URL' => 'URL', + 'Internal links' => 'Interne Verbindungen', + 'Assign to me' => 'Mir zuweisen', + 'Me' => 'Mich', + 'Do not duplicate anything' => 'Nichts duplizieren', + 'Projects management' => 'Projektmanagement', + 'Users management' => 'Benutzermanagement', + 'Groups management' => 'Gruppenmanagement', + 'Create from another project' => 'Von einem anderen Projekt erstellen', + 'open' => 'offen', + 'closed' => 'geschlossen', + 'Priority:' => 'Priorität:', + 'Reference:' => 'Referenz:', + 'Complexity:' => 'Komplexität:', + 'Swimlane:' => 'Swimlane:', + 'Column:' => 'Spalte:', + 'Position:' => 'Position:', + 'Creator:' => 'Ersteller:', + 'Time estimated:' => 'Geschätzte Zeit:', + '%s hours' => '%s Stunden', + 'Time spent:' => 'Aufgewendete Zeit:', + 'Created:' => 'Erstellt:', + 'Modified:' => 'Geändert:', + 'Completed:' => 'Abgeschlossen:', + 'Started:' => 'Gestartet:', + 'Moved:' => 'Verschoben:', + 'Task #%d' => 'Aufgabe #%d', + 'Time format' => 'Zeitformat', + 'Start date: ' => 'Anfangsdatum: ', + 'End date: ' => 'Enddatum: ', + 'New due date: ' => 'Neues Fälligkeitsdatum: ', + 'Start date changed: ' => 'Anfangsdatum geändert: ', + 'Disable personal projects' => 'Persönliche Projekte deaktivieren', + 'Do you really want to remove this custom filter: "%s"?' => 'Wollen Sie diesen benutzerdefinierten Filter wirklich entfernen: "%s"?', + 'Remove a custom filter' => 'Benutzerdefinierten Filter entfernen', + 'User activated successfully.' => 'Benutzer erfolgreich aktiviert.', + 'Unable to enable this user.' => 'Dieser Benutzer kann nicht aktiviert werden.', + 'User disabled successfully.' => 'Benutzer erfolgreich deaktiviert.', + 'Unable to disable this user.' => 'Dieser Benutzer kann nicht deaktiviert werden.', + 'All files have been uploaded successfully.' => 'Alle Dateien wurden erfolgreich hochgeladen.', + 'The maximum allowed file size is %sB.' => 'Die maximal erlaubte Dateigröße ist %sB.', + 'Drag and drop your files here' => 'Ziehen Sie Ihre Dateien hier hin', + 'choose files' => 'Dateien auswählen', + 'View profile' => 'Profil ansehen', + 'Two Factor' => 'Zwei-Faktor', + 'Disable user' => 'Benutzer deaktivieren', + 'Do you really want to disable this user: "%s"?' => 'Wollen Sie diesen Benutzer wirklich deaktivieren: "%s"?', + 'Enable user' => 'Benutzer aktivieren', + 'Do you really want to enable this user: "%s"?' => 'Wollen Sie diesen Benutzer wirklich aktivieren: "%s"?', + 'Download' => 'Herunterladen', + 'Uploaded: %s' => 'Hochgeladen: %s', + 'Size: %s' => 'Größe: %s', + 'Uploaded by %s' => 'Hochgeladen von %s', + 'Filename' => 'Dateiname', + 'Size' => 'Größe', + 'Column created successfully.' => 'Spalte erfolgreich erstellt.', + 'Another column with the same name exists in the project' => 'Es gibt bereits eine Spalte mit demselben Namen im Projekt', + 'Default filters' => 'Standard-Filter', + 'Your board doesn\'t have any columns!' => 'Es gibt keine Spalten in diesem Projekt!', + 'Change column position' => 'Position der Spalte ändern', + 'Switch to the project overview' => 'Zur Projektübersicht wechseln', + 'User filters' => 'Benutzer-Filter', + 'Category filters' => 'Kategorie-Filter', + 'Upload a file' => 'Eine Datei hochladen', + 'View file' => 'Datei ansehen', + 'Last activity' => 'Letzte Aktivität', + 'Change subtask position' => 'Position der Teilaufgabe ändern', + 'This value must be greater than %d' => 'Dieser Wert muss größer als %d sein', + 'Another swimlane with the same name exists in the project' => 'Es gibt bereits eine Swimlane mit diesem Namen im Projekt', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Beispiel: https://example.kanboard.org/ (wird zum Erstellen absoluter URLs genutzt)', + 'Actions duplicated successfully.' => 'Aktionen erfolgreich dupliziert', + 'Unable to duplicate actions.' => 'Aktionen können nicht dupliziert werden.', + 'Add a new action' => 'Neue Aktion hinzufügen', + 'Import from another project' => 'Von einem anderen Projekt importieren', + 'There is no action at the moment.' => 'Es gibt zur Zeit keine Aktionen.', + 'Import actions from another project' => 'Aktionen von einem anderen Projekt importieren', + 'There is no available project.' => 'Es ist kein Projekt verfügbar.', + 'Local File' => 'Lokale Datei', + 'Configuration' => 'Konfiguration', + 'PHP version:' => 'PHP Version:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'OS Version:', + 'Database version:' => 'Datenbank Version:', + 'Browser:' => 'Browser:', + 'Task view' => 'Aufgaben Ansicht', + 'Edit task' => 'Aufgabe bearbeiten', + 'Edit description' => 'Beschreibung bearbeiten', + 'New internal link' => 'Neue interne Verbindung', + 'Display list of keyboard shortcuts' => 'Liste der Tastaturkürzel anzeigen', + 'Avatar' => 'Avatar', + 'Upload my avatar image' => 'Mein Avatar Bild hochladen', + 'Remove my image' => 'Mein Bild entfernen', + 'The OAuth2 state parameter is invalid' => 'Der OAuth2 Statusparameter ist ungültig', + 'User not found.' => 'Benutzer nicht gefunden', + 'Search in activity stream' => 'Im Aktivitätenstrom suchen', + 'My activities' => 'Meine Aktivitäten', + 'Activity until yesterday' => 'Aktivitäten bis gestern', + 'Activity until today' => 'Aktivitäten bis heute', + 'Search by creator: ' => 'nach Ersteller suchen:', + 'Search by creation date: ' => 'nach Datum suchen:', + 'Search by task status: ' => 'nach Aufgabenstatus suchen:', + 'Search by task title: ' => 'nach Titel suchen:', + 'Activity stream search' => 'Im Aktivitätenstrom suchen', + 'Projects where "%s" is manager' => 'Projekte in denen "%s" Manager ist', + 'Projects where "%s" is member' => 'Projekte in denen "%s" Mitglied ist', + 'Open tasks assigned to "%s"' => 'Offene Aufgaben, die "%s" zugeteilt sind', + 'Closed tasks assigned to "%s"' => 'Geschlossene Aufgaben, die "%s" zugeteilt sind', + 'Assign automatically a color based on a priority' => 'Eine Farbe basierend auf einer Priorität automatisch zuordnen', + 'Overdue tasks for the project(s) "%s"' => 'Überfällige Aufgaben des/der Projekt/e "%s"', + 'Upload files' => 'Dateien hochladen', + 'Installed Plugins' => 'Installierte Plugins', + 'Plugin Directory' => 'Plugin Verzeichnis', + 'Plugin installed successfully.' => 'Plugin erfolgreich installiert.', + 'Plugin updated successfully.' => 'Plugin erfolgreich aktualisiert.', + 'Plugin removed successfully.' => 'Plugin erfolgreich entfernt.', + 'Subtask converted to task successfully.' => 'Teilaufgabe erfolgreich in Aufgabe umgewandelt.', + 'Unable to convert the subtask.' => 'Teilaufgabe kann nicht umgewandelt werden.', + 'Unable to extract plugin archive.' => 'Plugin Archiv kann nicht entpackt werden.', + 'Plugin not found.' => 'Plugin nicht gefunden.', + 'You don\'t have the permission to remove this plugin.' => 'Sie dürfen dieses Plugin nicht entfernen.', + 'Unable to download plugin archive.' => 'Plugin Archiv kann nicht herunter geladen werden.', + 'Unable to write temporary file for plugin.' => 'Temporäre Dateien für das Plugin können nicht geschrieben werden.', + 'Unable to open plugin archive.' => 'Kann das Plugin Archiv nicht öffnen.', + 'There is no file in the plugin archive.' => 'Es gibt keine Datei im Plugin Archiv.', + 'Create tasks in bulk' => 'Viele Aufgaben auf einmal erstellen', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Ihre Kanboard Installation ist nicht dafür konfiguriert, Plugins mit dem Benutzerinterface zu installieren.', + 'There is no plugin available.' => 'Es gibt kein Plugin.', + 'Install' => 'Installieren', + 'Update' => 'Aktualisieren', + 'Up to date' => 'Aktuell', + 'Not available' => 'Nicht verfügbar', + 'Remove plugin' => 'Plugin entfernen', + 'Do you really want to remove this plugin: "%s"?' => 'Wollen Sie das Plugin "%s" wirklich entfernen?', + 'Uninstall' => 'Deinstallieren', + 'Listing' => 'Auflistung', + 'Metadata' => 'Metadaten', + 'Manage projects' => 'Projekte verwalten', + 'Convert to task' => 'In Aufgabe umwandeln', + 'Convert sub-task to task' => 'Teilaufgabe in Aufgabe umwandeln', + 'Do you really want to convert this sub-task to a task?' => 'Wollen Sie diese Teilaufgabe wirklich in eine Aufgabe umwandeln?', + 'My task title' => 'Mein Aufgabentitel', + 'Enter one task by line.' => 'Geben Sie eine Aufgabe pro Zeile ein.', + 'Number of failed login:' => 'Anzahl fehlgeschlagener Anmeldungen:', + 'Account locked until:' => 'Konto gesperrt bis:', + 'Email settings' => 'E-Mail Einstellungen', + 'Email sender address' => 'E-Mail Absender Adresse', + 'Email transport' => 'E-Mail Verkehr', + 'Webhook token' => 'Webhook Token', + 'Project tags management' => 'Projektbezogenes Schlagwort-Management', + 'Tag created successfully.' => 'Schlagwort erfolgreich erstellt.', + 'Unable to create this tag.' => 'Das Schlagwort kann nicht erstellt werden.', + 'Tag updated successfully.' => 'Schlagwort erfolgreich aktualisiert.', + 'Unable to update this tag.' => 'Das Schlagwort kann nicht aktualisiert werden.', + 'Tag removed successfully.' => 'Schlagwort erfolgreich entfernt.', + 'Unable to remove this tag.' => 'Das Schlagwort kann nicht entfernt werden.', + 'Global tags management' => 'Globales Schlagwort-Management', + 'Tags' => 'Schlagworte', + 'Tags management' => 'Schlagwort-Management', + 'Add new tag' => 'Neues Schlagwort hinzufügen', + 'Edit a tag' => 'Schlagwort bearbeiten', + 'Project tags' => 'Projektbezogene Schlagwörter', + 'There is no specific tag for this project at the moment.' => 'Es gibt zur Zeit kein spezifisches Schlagwort.', + 'Tag' => 'Schlagwort', + 'Remove a tag' => 'Schlagwort entfernen', + 'Do you really want to remove this tag: "%s"?' => 'Soll dieses Schlagwort wirklich entfernt werden: "%s"?', + 'Global tags' => 'Globale Schlagwörter', + 'There is no global tag at the moment.' => 'Es gibt zur Zeit kein globales Schlagwort', + 'This field cannot be empty' => 'Dieses Feld kann nicht leer sein', + 'Close a task when there is no activity in a specific column' => 'Aufgabe schließen wenn es keine Aktivität in einer bestimmten Spalte gibt', + '%s removed a subtask for the task #%d' => '%s hat eine Teilaufgabe der Aufgabe #%d entfernt', + '%s removed a comment on the task #%d' => '%s hat einen Kommentar der Aufgabe #%d entfernt', + 'Comment removed on task #%d' => 'Kommentar der Aufgabe #%d entfernt', + 'Subtask removed on task #%d' => 'Teilaufgabe der Aufgabe #%d entfernt', + 'Hide tasks in this column in the dashboard' => 'Aufgaben in dieser Spalte im Dashboard ausblenden', + '%s removed a comment on the task %s' => '%s hat einen Kommentar in der Aufgabe %s entfernt', + '%s removed a subtask for the task %s' => '%s hat eine Teilaufgabe in der Aufgabe %s entfernt', + 'Comment removed' => 'Kommentar entfernt', + 'Subtask removed' => 'Teilaufgabe entfernt', + '%s set a new internal link for the task #%d' => '%s hat eine neue interne Verbindung in der Aufgabe #%d erstellt', + '%s removed an internal link for the task #%d' => '%s hat eine interne Verbindung von der Aufgabe #%d entfernt', + 'A new internal link for the task #%d has been defined' => 'Eine neue interne Verbindung für die Aufgabe #%d wurde definiert', + 'Internal link removed for the task #%d' => 'Interne Verbindung in der Aufgabe #%d wurde entfernt', + '%s set a new internal link for the task %s' => '%s hat eine neue interne Verbindung in der Aufgabe %s erstellt', + '%s removed an internal link for the task %s' => '%s hat eine interne Verbindung von der Aufgabe %s entfernt', + 'Automatically set the due date on task creation' => 'Ablaufdatum automatisch bei Erstellung einer Aufgabe setzen', + 'Move the task to another column when closed' => 'Aufgabe in eine andere Spalte verschieben, wenn diese geschlossen wird', + 'Move the task to another column when not moved during a given period' => 'Aufgabe in eine andere Spalte verschieben, wenn diese in einer bestimmten Zeit nicht verschoben wurde', + 'Dashboard for %s' => 'Dashboard für %s', + 'Tasks overview for %s' => 'Aufgaben-Übersicht für %s', + 'Subtasks overview for %s' => 'Teilaufgaben-Übersicht für %s', + 'Projects overview for %s' => 'Projekt-Übersicht für %s', + 'Activity stream for %s' => 'Aktivitätenstrom für %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Einer Aufgabe eine Farbe zuweisen, wenn diese in eine bestimmte Swimlane verschoben wird', + 'Assign a priority when the task is moved to a specific swimlane' => 'Einer Aufgabe eine Priorität zuweisen, wenn diese in eine bestimmte Swimlane verschoben wird', + 'User unlocked successfully.' => 'Benutzer erfolgreich entsperrt.', + 'Unable to unlock the user.' => 'Benutzer kann nicht entsperrt werden.', + 'Move a task to another swimlane' => 'Aufgabe in eine andere Swimlane verschieben', + 'Creator Name' => 'Name des Erstellers', + 'Time spent and estimated' => 'Aufgewendete und erwartete Zeit', + 'Move position' => 'Position verschieben', + 'Move task to another position on the board' => 'Aufgabe an eine andere Position im Board verschieben', + 'Insert before this task' => 'Vor dieser Aufgabe einfügen', + 'Insert after this task' => 'Nach dieser Aufgabe einfügen', + 'Unlock this user' => 'Diesen Benutzer entsperren', + 'Custom Project Roles' => 'Benutzerdefinierte Projekt Rollen', + 'Add a new custom role' => 'Neue benutzerdefinierte Rolle erstellen', + 'Restrictions for the role "%s"' => 'Einschränkungen für Rolle "%s"', + 'Add a new project restriction' => 'Neue projektbezogene Einschränkung erstellen', + 'Add a new drag and drop restriction' => 'Neue Drag and Drop Einschränkung erstellen', + 'Add a new column restriction' => 'Neue spaltenbezogene Einschränkung erstellen', + 'Edit this role' => 'Diese Rolle bearbeiten', + 'Remove this role' => 'Diese Rolle entfernen', + 'There is no restriction for this role.' => 'Für diese Rolle gibt es keine Einschränkungen.', + 'Only moving task between those columns is permitted' => 'Verschieben von Aufgaben ist nur zwischen diesen Spalten erlaubt', + 'Close a task in a specific column when not moved during a given period' => 'Aufgabe in einer bestimmten Spalte schließen, wenn sie im angegebenen Zeitraum nicht verschoben wurde', + 'Edit columns' => 'Spalten bearbeiten', + 'The column restriction has been created successfully.' => 'Die Spalteneinschränkung wurde erfolgreich erstellt.', + 'Unable to create this column restriction.' => 'Erstellen der Spalteneinschränkung fehlgeschlagen.', + 'Column restriction removed successfully.' => ' Spalteneinschränkung erfolgreich entfernt.', + 'Unable to remove this restriction.' => 'Entfernen der Spalteneinschränkung fehlgeschlagen.', + 'Your custom project role has been created successfully.' => 'Benutzerdefinierte Projekt Rolle erfolgreich erstellt.', + 'Unable to create custom project role.' => 'Erstellen der benutzerdefinierten Projekt Rolle fehlgeschlagen.', + 'Your custom project role has been updated successfully.' => 'Benutzerdefinierte Projekt Rolle wurde erfolgreich geändert.', + 'Unable to update custom project role.' => 'Ändern der benutzerdefinierten Projekt Rolle fehlgeschlagen.', + 'Custom project role removed successfully.' => 'Benutzerdefinierte Projekt Rolle erfolgreich entfernt.', + 'Unable to remove this project role.' => 'Entfernen der benutzerdefinierten Projekt Rolle fehlgeschlagen.', + 'The project restriction has been created successfully.' => 'Projektbezogene Einschränkung erfolgreich erstellt.', + 'Unable to create this project restriction.' => 'Erstellen der projektbezogenen Einschränkung fehlgeschlagen.', + 'Project restriction removed successfully.' => 'Projektbezogene Einschränkung erfolgreich entfernt.', + 'You cannot create tasks in this column.' => 'Sie können in dieser Spalte keine Aufgaben erzeugen.', + 'Task creation is permitted for this column' => 'Erzeugen von Aufgaben ist für diese Spalte erlaubt.', + 'Closing or opening a task is permitted for this column' => 'Öffnen und Schließen von Aufgaben ist für diese Spalte erlaubt.', + 'Task creation is blocked for this column' => 'Erzeugen von Aufgaben ist für diese Spalte blockiert.', + 'Closing or opening a task is blocked for this column' => 'Öffnen und Schließen von Aufgaben ist für diese Spalte blockiert.', + 'Task creation is not permitted' => 'Erzeugen von Aufgaben ist nicht erlaubt.', + 'Closing or opening a task is not permitted' => 'Öffnen und Schließen von Aufgaben ist nicht erlaubt.', + 'New drag and drop restriction for the role "%s"' => 'Neue drag and drop Einschränkung für Rolle "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Benutzer mit dieser Rolle können Aufgaben nur zwischen Quell- und Zielspalte verschieben.', + 'Remove a column restriction' => 'Spaltenbezogene Einschränkung entfernen', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Wollen Sie diese Spalteneinschränkung wirklich löschen: "%s" nach "%s"?', + 'New column restriction for the role "%s"' => 'Neue spaltenbezogene Einschränkung für Rolle "%s"', + 'Rule' => 'Regel', + 'Do you really want to remove this column restriction?' => 'Wollen Sie diese Spalteneinschränkung wirklich entfernen?', + 'Custom roles' => 'Benutzerdefinierte Rollen', + 'New custom project role' => 'Neue benutzerdefinierte Projekt Rolle', + 'Edit custom project role' => 'Benutzerdefinierte Projekt Rolle bearbeiten', + 'Remove a custom role' => 'Benutzerdefinierte Projekt Rolle entfernen', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Wollen sie diese benutzerdefinierte Rolle wirklich entfernen: "%s"? Alle Benutzer mit dieser Rolle werden zu Projekt-Mitgliedern.', + 'There is no custom role for this project.' => 'Für dieses Projekt gibt es keine benutzerdefinierten Rollen.', + 'New project restriction for the role "%s"' => 'Neue projektbezogene Einschränkung für Rolle "%s"', + 'Restriction' => 'Einschränkung', + 'Remove a project restriction' => 'Projektbezogene Einschränkung entfernen', + 'Do you really want to remove this project restriction: "%s"?' => 'Wollen Sie diese projektbezogene Einschränkung wirklich entfernen: "%s"?', + 'Duplicate to multiple projects' => 'In mehrere Projekte duplizieren', + 'This field is required' => 'Dies ist ein Pflichtfeld', + 'Moving a task is not permitted' => 'Verschieben einer Aufgabe ist nicht erlaubt', + 'This value must be in the range %d to %d' => 'Dieser Wert muss im Bereich %d bis %d sein', + 'You are not allowed to move this task.' => 'Sie haben nicht die Berechtigung, diese Aufgabe zu verschieben.', + 'API User Access' => 'API Benutzerzugriff', + 'Preview' => 'Vorschau', + 'Write' => 'Schreiben', + 'Write your text in Markdown' => 'Schreiben Sie Ihren Text in Markdown', + 'No personal API access token registered.' => 'Keine persönlichen API-Zugriffsinformationen registriert', + 'Your personal API access token is "%s"' => 'Ihre persönlichen API-Zugriffsinformationen: "%s"', + 'Remove your token' => 'Ihre Zugriffsinformationen entfernen', + 'Generate a new token' => 'Neue Zugriffsinformationen generieren', + 'Showing %d-%d of %d' => 'Zeige %d-%d von %d', + 'Outgoing Emails' => 'Ausgehende E-Mails', + 'Add or change currency rate' => 'Wechselkurs hinzufügen oder ändern', + 'Reference currency: %s' => 'Referenzwährung: %s', + 'Add custom filters' => 'Benutzerdefinierten Filter hinzufügen', + 'Export' => 'Exportieren', + 'Add link label' => 'Linkbeschreibung hinzufügen', + 'Incompatible Plugins' => 'Nicht-kompatible Plugins', + 'Compatibility' => 'Kompatibilität', + 'Permissions and ownership' => 'Berechtigungen und Besitz', + 'Priorities' => 'Prioritäten', + 'Close this window' => 'Dieses Fenster schließen', + 'Unable to upload this file.' => 'Diese Datei kann nicht hochgeladen werden', + 'Import tasks' => 'Aufgaben importieren', + 'Choose a project' => 'Wählen Sie ein Projekt', + 'Profile' => 'Profil', + 'Application role' => 'Anwendungsrolle', + '%d invitations were sent.' => '%d Einladungen wurden gesendet.', + '%d invitation was sent.' => '%d Einladung wurde gesendet.', + 'Unable to create this user.' => 'Dieser Benutzer kann nicht erstellt werden.', + 'Kanboard Invitation' => 'Kanboard Einladung', + 'Visible on dashboard' => 'Sichtbar auf dem Dashboard', + 'Created at:' => 'Erstellt am:', + 'Updated at:' => 'Aktualisiert am:', + 'There is no custom filter.' => 'Es gibt keinen benutzerdefinierten Filter.', + 'New User' => 'Neuer Benutzer', + 'Authentication' => 'Authentifizierung', + 'If checked, this user will use a third-party system for authentication.' => 'Wenn aktiviert, verwendet dieser Benutzer ein Drittanbieter-System für die Authentifizierung.', + 'The password is necessary only for local users.' => 'Das Passwort ist nur für lokale Benutzer erforderlich.', + 'You have been invited to register on Kanboard.' => 'Sie wurden eingeladen, sich auf Kanboard zu registrieren.', + 'Click here to join your team' => 'Klicken Sie hier, um Ihrem Team beizutreten', + 'Invite people' => 'Leute einladen', + 'Emails' => 'E-Mail', + 'Enter one email address by line.' => 'Geben Sie eine E-Mail-Adresse pro Zeile ein.', + 'Add these people to this project' => 'Füge diese Personen diesem Projekt hinzu', + 'Add this person to this project' => 'Füge diese Person diesem Projekt hinzu', + 'Sign-up' => 'Anmelden', + 'Credentials' => 'Anmeldeinformationen', + 'New user' => 'Neuer Benutzer', + 'This username is already taken' => 'Dieser Benutzername ist bereits vergeben', + 'Your profile must have a valid email address.' => 'Ihr Profil muss eine gültige E-Mail-Adresse haben.', + 'TRL - Turkish Lira' => 'TRL - Türkische Lira', + 'The project email is optional and could be used by several plugins.' => 'Die Projekt-E-Mail ist optional und kann von mehreren Plugins verwendet werden.', + 'The project email must be unique across all projects' => 'Die Projekt-E-Mail muss für alle Projekte eindeutig sein', + 'The email configuration has been disabled by the administrator.' => 'Die E-Mail-Konfiguration wurde vom Administrator deaktiviert.', + 'Close this project' => 'Dieses Projekt schließen', + 'Open this project' => 'Dieses Projekt öffnen', + 'Close a project' => 'Ein Projekt schließen', + 'Do you really want to close this project: "%s"?' => 'Möchten Sie dieses Projekt wirklich schließen: "%s"?', + 'Reopen a project' => 'Ein Projekt wieder öffnen', + 'Do you really want to reopen this project: "%s"?' => 'Möchten Sie dieses Projekt wirklich wieder öffnen: "%s"?', + 'This project is open' => 'Dieses Projekt ist offen', + 'This project is closed' => 'Dieses Projekt ist geschlossen', + 'Unable to upload files, check the permissions of your data folder.' => 'Dateien können nicht hochgeladen werden, überprüfen Sie die Berechtigungen Ihres Datenordners.', + 'Another category with the same name exists in this project' => 'Eine weitere Kategorie mit demselben Namen existiert in diesem Projekt', + 'Comment sent by email successfully.' => 'Kommentar wurde erfolgreich per E-Mail gesendet.', + 'Sent by email to "%s" (%s)' => 'Wurde per E-Mail an [%s] gesendet "%s"', + 'Unable to read uploaded file.' => 'Die hochgeladene Datei konnte nicht gelesen werden.', + 'Database uploaded successfully.' => 'Die Datenbank wurde erfolgreich hochgeladen.', + 'Task sent by email successfully.' => 'Aufgabe wurde erfolgreich per E-Mail gesendet.', + 'There is no category in this project.' => 'Es gibt keine Kategorie in diesem Projekt', + 'Send by email' => 'Per E-Mail senden', + 'Create and send a comment by email' => 'Erstellen und senden Sie einen Kommentar per E-Mail', + 'Subject' => 'Betreff', + 'Upload the database' => 'Datenbank hochladen', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Sie können die zuvor heruntergeladene SQLite-Datenbank (Gzip-Format) hochladen.', + 'Database file' => 'Datenbankdatei', + 'Upload' => 'Hochladen', + 'Your project must have at least one active swimlane.' => 'Ihr Projekt muss mindestens eine aktive Swimlane haben.', + 'Project: %s' => 'Projekt: %s', + 'Automatic action not found: "%s"' => 'Automatische Aktion nicht gefunden: "%s"', + '%d projects' => '%d Projekte', + '%d project' => '%d Projekt', + 'There is no project.' => 'Es gibt kein Projekt.', + 'Sort' => 'Sortieren', + 'Project ID' => 'Projekt-ID', + 'Project name' => 'Projekt Name', + 'Public' => 'Öffentlich', + 'Personal' => 'Persönlich', + '%d tasks' => '%d Aufgaben', + '%d task' => '%d Aufgabe', + 'Task ID' => 'Aufgaben-ID', + 'Assign automatically a color when due date is expired' => 'Automatisch eine Farbe zuweisen, wenn das Fälligkeitsdatum abgelaufen ist', + 'Total score in this column across all swimlanes' => 'Gesamtpunktzahl in dieser Spalte über alle Swimlanes', + 'HRK - Kuna' => 'HRK - Kroatische Kuna', + 'ARS - Argentine Peso' => 'ARS - Argentinische Peso', + 'COP - Colombian Peso' => 'COP - Kolumbianische Peso', + '%d groups' => '%d Gruppen', + '%d group' => '%d Gruppe', + 'Group ID' => 'Gruppen-ID', + 'External ID' => 'Externe ID', + '%d users' => '%d Benutzer', + '%d user' => '%d Benutzer', + 'Hide subtasks' => 'Teilaufgaben verstecken', + 'Show subtasks' => 'Teilaufgaben anzeigen', + 'Authentication Parameters' => 'Authentifizierungsparameter', + 'API Access' => 'API-Zugriff', + 'No users found.' => 'Keine Benutzer gefunden.', + 'User ID' => 'Benutzer-ID', + 'Notifications are activated' => 'Benachrichtigungen sind aktiviert', + 'Notifications are disabled' => 'Benachrichtigungen sind deaktiviert', + 'User disabled' => 'Benutzer deaktiviert', + '%d notifications' => '%d Benachrichtigungen', + '%d notification' => '%d Benachrichtigung', + 'There is no external integration installed.' => 'Es ist keine externe Integration installiert.', + 'You are not allowed to update tasks assigned to someone else.' => 'Sie sind nicht berechtigt, Aufgaben zu aktualisieren, die jemand anderem zugewiesen wurden.', + 'You are not allowed to change the assignee.' => 'Sie dürfen den Zuständigen nicht ändern.', + 'Task suppression is not permitted' => 'Entfernen von Aufgaben ist nicht erlaubt.', + 'Changing assignee is not permitted' => 'Änderung des Zuständigen ist nicht zulässig', + 'Update only assigned tasks is permitted' => 'Nur zugeordnete Aufgaben dürfen aktualisiert werden', + 'Only for tasks assigned to the current user' => 'Nur für Aufgaben, die dem aktuellen Benutzer zugeordnet sind', + 'My projects' => 'Meine Projekte', + 'You are not a member of any project.' => 'Sie sind nicht Mitglied eines Projektes.', + 'My subtasks' => 'Meine Teilaufgaben', + '%d subtasks' => '%d Teilaufgaben', + '%d subtask' => '%d Teilaufgabe', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Das Bewegen einer Aufgabe zwischen diesen Spalten ist nur für Aufgaben zulässig, die dem aktuellen Benutzer zugewiesen sind', + '[DUPLICATE]' => '[DUPLIKAT]', + 'DKK - Danish Krona' => 'DKK - Dänische Kronen', + 'Remove user from group' => 'Benutzer aus Gruppe löschen', + 'Assign the task to its creator' => 'Aufgabe dem Ersteller zuordnen', + 'This task was sent by email to "%s" with subject "%s".' => 'Diese Aufgabe wurde per Mail an "%s" mit dem Betreff "%s" gesendet.', + 'Predefined Email Subjects' => 'Vordefinierte E-Mail Betreffzeilen', + 'Write one subject by line.' => 'Schreibe ein Betreff pro Zeile.', + 'Create another link' => 'Einen weiteren Link erstellen', + 'BRL - Brazilian Real' => 'BRL - Brasilianische Real', + 'Add a new Kanboard task' => 'Eine neue Kanboard Aufgabe hinzufügen', + 'Subtask not started' => 'Teilaufgabe nicht gestartet', + 'Subtask currently in progress' => 'Teilaufgabe aktuell in Bearbeitung', + 'Subtask completed' => 'Teilaufgabe abgeschlossen', + 'Subtask added successfully.' => 'Teilaufgabe erfolgreich hinzugefügt.', + '%d subtasks added successfully.' => '%d Teilaufgaben erfolgreich hinzugefügt.', + 'Enter one subtask by line.' => 'Geben Sie eine Teilaufgabe pro Zeile ein.', + 'Predefined Contents' => 'Vordefinierte Inhalte', + 'Predefined contents' => 'Vordefinierte Inhalte', + 'Predefined Task Description' => 'Vordefinierte Aufgabenbeschreibung', + 'Do you really want to remove this template? "%s"' => 'Wollen Sie diese Vorlage wirklich löschen? "%s"', + 'Add predefined task description' => 'Vordefinierte Aufgabenbeschreibung hinzufügen', + 'Predefined Task Descriptions' => 'Vordefinierte Aufgabenbeschreibungen', + 'Template created successfully.' => 'Vorlage erfolgreich erstellt.', + 'Unable to create this template.' => 'Erstellen der Vorlage nicht möglich.', + 'Template updated successfully.' => 'Vorlage erfolgreich geändert.', + 'Unable to update this template.' => 'Aktualisierung der Vorlage nicht möglich.', + 'Template removed successfully.' => 'Vorlage erfolgreich gelöscht.', + 'Unable to remove this template.' => 'Löschen der Vorlage nicht möglich.', + 'Template for the task description' => 'Vorlage für die Aufgabenbeschreibung', + 'The start date is greater than the end date' => 'Das Startdatum ist größer als das Enddatum', + 'Tags must be separated by a comma' => 'Schlagworte müssen per Komma getrennt werden', + 'Only the task title is required' => 'Nur der Aufgaben-Titel wird benötigt', + 'Creator Username' => 'Ersteller Benutzername', + 'Color Name' => 'Farbname', + 'Column Name' => 'Spaltenname', + 'Swimlane Name' => 'Swimlane-Name', + 'Time Estimated' => 'geschätzte Zeit', + 'Time Spent' => 'Zeitaufwand', + 'External Link' => 'externer Link', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Diese Funktion aktiviert den iCal Feed, RSS Feed und die öffentliche Board-Ansicht', + 'Stop the timer of all subtasks when moving a task to another column' => 'Beenden des Timers für alle Unteraufgaben, wenn die Aufgabe in eine andere Spalte verschoben wird', + 'Subtask Title' => 'Titel der Teilaufgabe', + 'Add a subtask and activate the timer when moving a task to another column' => 'Teilaufgabe hinzufügen und den Timer aktivieren, wenn die Aufgabe in eine andere Spalte verschoben wird', + 'days' => 'Tage', + 'minutes' => 'Minuten', + 'seconds' => 'Sekunden', + 'Assign automatically a color when preset start date is reached' => 'Automatisch eine Farbe zuweisen, wenn das Startdatum erreicht ist', + 'Move the task to another column once a predefined start date is reached' => 'Verschieben des Tasks in eine andere Spalte, wenn das definierte Startdatum erreicht ist', + 'This task is now linked to the task %s with the relation "%s"' => 'Diese Aufgabe ist jetzt verknüpft mit der Aufgabe %s mit der Relation "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'Die Verknüpfung mit der Relation "%s" zur Aufgabe %s wurde entfernt', + 'Custom Filter:' => 'Benutzerdefinierter Filter:', + 'Unable to find this group.' => 'Diese Gruppe konnte nicht gefunden werden', + '%s moved the task #%d to the column "%s"' => '%s hat die Aufgabe #%d in die Spalte "%s" verschoben', + '%s moved the task #%d to the position %d in the column "%s"' => '%s hat die Aufgabe #%d auf die Position %d in der Spalte "%s" verschoben', + '%s moved the task #%d to the swimlane "%s"' => '%s hat die Aufgabe #%d in die Swimlane "%s" verschoben', + '%sh spent' => '%sh aufgewendet', + '%sh estimated' => '%sh angesetzt', + 'Select All' => 'Alle auswählen', + 'Unselect All' => 'Keine auswählen', + 'Apply action' => 'Aktion anwenden', + 'Move selected tasks to another column or swimlane' => 'Ausgewählte Aufgaben in andere Spalte verschieben', + 'Edit tasks in bulk' => 'Massenbearbeitung', + 'Choose the properties that you would like to change for the selected tasks.' => 'Wählen Sie die Eigenschaften aus, die Sie für die ausgewählten Aufgaben ändern möchten.', + 'Configure this project' => 'Projekteinstellungen', + 'Start now' => 'Jetzt starten', + '%s removed a file from the task #%d' => '%s hat eine Datei aus der Aufgabe #%d entfernt.', + 'Attachment removed from task #%d: %s' => 'Anhang aus Aufgabe #%d entfernt: %s', + 'No color' => 'Keine Farbe', + 'Attachment removed "%s"' => 'Anhang entfernt "%s"', + '%s removed a file from the task %s' => '%s hat eine Datei aus der Aufgabe entfernt %s', + 'Move the task to another swimlane when assigned to a user' => 'Verschieben der Aufgabe in eine andere Swimlane, wenn sie einem Benutzer zugewiesen wird', + 'Destination swimlane' => 'Ziel Swimlane', + 'Assign a category when the task is moved to a specific swimlane' => 'Kategorie zuweisen, wenn Aufgabe in eine bestimmte Swimlane verschoben wird', + 'Move the task to another swimlane when the category is changed' => 'Verschiebe die Aufgabe in eine andere Swimlane, wenn die Kategorie geändert wird', + 'Reorder this column by priority (ASC)' => 'Spalte nach Priorität ordnen (aufsteigend)', + 'Reorder this column by priority (DESC)' => 'Spalte nach Priorität ordnen (absteigend)', + 'Reorder this column by assignee and priority (ASC)' => 'Spalte nach Zuständigem und Priorität ordnen (aufsteigend)', + 'Reorder this column by assignee and priority (DESC)' => 'Spalte nach Zuständigem und Priorität ordnen (absteigend)', + 'Reorder this column by assignee (A-Z)' => 'Spalte nach Zuständigem ordnen (A-Z)', + 'Reorder this column by assignee (Z-A)' => 'Spalte nach Zuständigem ordnen (Z-A)', + 'Reorder this column by due date (ASC)' => 'Spalte nach Fälligkeitsdatum ordnen (aufsteigend)', + 'Reorder this column by due date (DESC)' => 'Spalte nach Fälligkeitsdatum ordnen (absteigend)', + 'Reorder this column by id (ASC)' => 'Diese Spalte nach ID aufsteigend sortieren', + 'Reorder this column by id (DESC)' => 'Diese Spalte nach ID absteigend sortieren', + '%s moved the task #%d "%s" to the project "%s"' => '%s hat die Aufgabe #%d "%s" in das Projekt "%s" verschoben', + 'Task #%d "%s" has been moved to the project "%s"' => 'Aufgabe #%d "%s" wurde in das Projekt "%s" verschoben', + 'Move the task to another column when the due date is less than a certain number of days' => 'Verschieben der Aufgabe in eine andere Spalte, wenn die Fälligkeit kleiner als eine bestimmte Anzahl von Tagen ist', + 'Automatically update the start date when the task is moved away from a specific column' => 'Aktualisiert automatisch das Startdatum, wenn die Aufgabe aus einer bestimmten Spalte genommen wird', + 'HTTP Client:' => 'HTTP-Client:', + 'Assigned' => 'Zugeordnet', + 'Task limits apply to each swimlane individually' => 'Aufgabenlimit gilt pro Swimlane', + 'Column task limits apply to each swimlane individually' => 'Spaltenaufgabenlimit für jede Swimlane einzeln anwenden', + 'Column task limits are applied to each swimlane individually' => 'Spaltenaufgabenlimit wird für jede Swimlane einzeln angewendet', + 'Column task limits are applied across swimlanes' => 'Spaltenaufgabenlimit wird swimlaneübergreifend angewendet', + 'Task limit: ' => 'Aufgabenlimit', + 'Change to global tag' => 'Zu globalem Schlagwort machen', + 'Do you really want to make the tag "%s" global?' => 'Das Schlagwort "%s" wirklich global machen?', + 'Enable global tags for this project' => 'Globale Schlagworte für dieses Projekt aktivieren', + 'Group membership(s):' => 'Gruppen-Mitgliedschaft(en):', + '%s is a member of the following group(s): %s' => '%s ist Mitglied in der/den folgenden Gruppe(n): %s', + '%d/%d group(s) shown' => '%d/%d Gruppe(n) angezeigt', + 'Subtask creation or modification' => 'Teilaufgabe erstellen oder ändern', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Aufgabe einem bestimmten Nutzer zuordnen, wenn die Aufgabe in eine bestimmte Swimlane verschoben wird', + 'Comment' => 'Kommentar', + 'Collapse vertically' => 'Vertikal zuklappen', + 'Expand vertically' => 'Vertikal erweitern', + 'MXN - Mexican Peso' => 'MXN - Mexikanischer Peso', + 'Estimated vs actual time per column' => 'Geschätzte vs. tatsächliche Zeit pro Spalte', + 'HUF - Hungarian Forint' => 'HUF - Ungarischer Forint', + 'XBT - Bitcoin' => 'XBT - Bitcoin', + 'You must select a file to upload as your avatar!' => 'Sie müssen eine Datei auswählen, um sie als Ihr Avatar hochzuladen!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'Die hochgeladene Datei ist kein gültiges Bild! (Nur *.gif, *.jpg, *.jpeg und *.png sind erlaubt!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Fälligkeitsdatum automatisch setzen, wenn die Aufgabe aus einer bestimmten Spalte verschoben wird', + 'No other projects found.' => 'Keine weiteren Projekte gefunden.', + 'Tasks copied successfully.' => 'Aufgaben erfolgreich kopiert.', + 'Unable to copy tasks.' => 'Aufgaben können nicht kopiert werden.', + 'Theme' => 'Thema', + 'Theme:' => 'Thema:', + 'Light theme' => 'Helles Thema', + 'Dark theme' => 'Dunkles Thema', + 'Automatic theme - Sync with system' => 'Automatisches Thema - Mit System synchronisieren', + 'Application managers or more' => 'Anwendungsmanager oder mehr', + 'Administrators' => 'Administratoren', + 'Visibility:' => 'Sichtbarkeit:', + 'Standard users' => 'Standardbenutzer', + 'Visibility is required' => 'Sichtbarkeit ist erforderlich', + 'The visibility should be an app role' => 'Die Sichtbarkeit sollte eine App-Rolle sein', + 'Reply' => 'Antworten', + '%s wrote: ' => '%s schrieb: ', + 'Number of visible tasks in this column and swimlane' => 'Anzahl sichtbarer Aufgaben in dieser Spalte und Swimlane', + 'Number of tasks in this swimlane' => 'Anzahl der Aufgaben in dieser Swimlane', + 'Unable to find another subtask in progress, you can close this window.' => 'Es konnte keine weitere Teilaufgabe in Bearbeitung gefunden werden, Sie können dieses Fenster schließen.', + 'This theme is invalid' => 'Dieses Thema ist ungültig', + 'This role is invalid' => 'Diese Rolle ist ungültig', + 'This timezone is invalid' => 'Diese Zeitzone ist ungültig', + 'This language is invalid' => 'Diese Sprache ist ungültig', + 'This URL is invalid' => 'Diese URL ist ungültig', + 'Date format invalid' => 'Ungültiges Datumsformat', + 'Time format invalid' => 'Ungültiges Zeitformat', + 'Invalid Mail transport' => 'Ungültiger Mail-Transport', + 'Color invalid' => 'Ungültige Farbe', + 'This value must be greater or equal to %d' => 'Dieser Wert muss größer oder gleich %d sein', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Fügen Sie am Anfang der Datei ein BOM hinzu (erforderlich für Microsoft Excel)', + 'Just add these tag(s)' => 'Nur diese Tags hinzufügen', + 'Remove internal link(s)' => 'Interne Links entfernen', + 'Import tasks from another project' => 'Aufgaben aus einem anderen Projekt importieren', + 'Select the project to copy tasks from' => 'Wählen Sie das Projekt aus, aus dem Sie Aufgaben kopieren möchten', + 'The total maximum allowed attachments size is %sB.' => 'Die maximal zulässige Gesamtgröße für Anhänge beträgt %sB.', + 'Add attachments' => 'Anhänge hinzufügen', + 'Task #%d "%s" is overdue' => 'Aufgabe #%d "%s" ist überfällig', + 'Enable notifications by default for all new users' => 'Benachrichtigungen standardmäßig für alle neuen Benutzer aktivieren', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Aufgabe dem Ersteller zuweisen, wenn sie in einer ausgewählten Spalte ohne Bearbeiter ist.', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Aufgabe dem angemeldeten Nutzer bei Spaltenänderung zuweisen, wenn eine Aufgabe in die ausgewählte Spalte verschoben wird.', +]; diff --git a/app/Locale/de_DE_du/translations.php b/app/Locale/de_DE_du/translations.php new file mode 100644 index 0000000..640cf62 --- /dev/null +++ b/app/Locale/de_DE_du/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => '.', + 'None' => 'Keines', + 'Edit' => 'Bearbeiten', + 'Remove' => 'Entfernen', + 'Yes' => 'Ja', + 'No' => 'Nein', + 'cancel' => 'Abbrechen', + 'or' => 'oder', + 'Yellow' => 'Gelb', + 'Blue' => 'Blau', + 'Green' => 'Grün', + 'Purple' => 'Violett', + 'Red' => 'Rot', + 'Orange' => 'Orange', + 'Grey' => 'Grau', + 'Brown' => 'Braun', + 'Deep Orange' => 'Dunkelorange', + 'Dark Grey' => 'Dunkelgrau', + 'Pink' => 'Pink', + 'Teal' => 'Türkis', + 'Cyan' => 'Cyan', + 'Lime' => 'Limette', + 'Light Green' => 'Hellgrün', + 'Amber' => 'Bernstein', + 'Save' => 'Speichern', + 'Login' => 'Anmelden', + 'Official website:' => 'Offizielle Webseite:', + 'Unassigned' => 'Nicht zugeordnet', + 'View this task' => 'Aufgabe ansehen', + 'Remove user' => 'Benutzer löschen', + 'Do you really want to remove this user: "%s"?' => 'Soll dieser Benutzer wirklich gelöscht werden: "%s"?', + 'All users' => 'Alle Benutzer', + 'Username' => 'Benutzername', + 'Password' => 'Passwort', + 'Administrator' => 'Administrator', + 'Sign in' => 'Anmelden', + 'Users' => 'Benutzer', + 'Forbidden' => 'Verboten', + 'Access Forbidden' => 'Zugriff verboten', + 'Edit user' => 'Benutzer bearbeiten', + 'Logout' => 'Abmelden', + 'Bad username or password' => 'Falscher Benutzername oder Passwort', + 'Edit project' => 'Projekt bearbeiten', + 'Name' => 'Name', + 'Projects' => 'Projekte', + 'No project' => 'Keine Projekte', + 'Project' => 'Projekt', + 'Status' => 'Status', + 'Tasks' => 'Aufgaben', + 'Board' => 'Pinnwand', + 'Actions' => 'Aktionen', + 'Inactive' => 'Inaktiv', + 'Active' => 'Aktiv', + 'Unable to update this board.' => 'Ändern dieser Pinnwand nicht möglich.', + 'Disable' => 'Deaktivieren', + 'Enable' => 'Aktivieren', + 'New project' => 'Neues Projekt', + 'Do you really want to remove this project: "%s"?' => 'Soll dieses Projekt wirklich gelöscht werden: "%s"?', + 'Remove project' => 'Projekt löschen', + 'Edit the board for "%s"' => 'Pinnwand für "%s" bearbeiten', + 'Add a new column' => 'Neue Spalte hinzufügen', + 'Title' => 'Titel', + 'Assigned to %s' => 'Zuständig: %s', + 'Remove a column' => 'Spalte löschen', + 'Unable to remove this column.' => 'Löschen dieser Spalte nicht möglich.', + 'Do you really want to remove this column: "%s"?' => 'Soll diese Spalte wirklich gelöscht werden: "%s"?', + 'Settings' => 'Einstellungen', + 'Application settings' => 'Anwendungskonfiguration', + 'Language' => 'Sprache', + 'Webhook token:' => 'Webhook Token:', + 'API token:' => 'API Token:', + 'Database size:' => 'Datenbankgröße:', + 'Download the database' => 'Datenbank herunterladen', + 'Optimize the database' => 'Datenbank optimieren', + '(VACUUM command)' => '(VACUUM Befehl)', + '(Gzip compressed Sqlite file)' => '(Gzip-komprimierte SQLite-Datei)', + 'Close a task' => 'Aufgabe abschließen', + 'Column' => 'Spalte', + 'Color' => 'Farbe', + 'Assignee' => 'Zuständiger', + 'Create another task' => 'Weitere Aufgabe erstellen', + 'New task' => 'Neue Aufgabe', + 'Open a task' => 'Öffne eine Aufgabe', + 'Do you really want to open this task: "%s"?' => 'Soll diese Aufgabe wirklich wieder geöffnet werden: "%s"?', + 'Back to the board' => 'Zurück zur Pinnwand', + 'There is nobody assigned' => 'Die Aufgabe wurde niemandem zugewiesen', + 'Column on the board:' => 'Spalte:', + 'Close this task' => 'Aufgabe schließen', + 'Open this task' => 'Aufgabe wieder öffnen', + 'There is no description.' => 'Keine Beschreibung vorhanden.', + 'Add a new task' => 'Neue Aufgabe hinzufügen', + 'The username is required' => 'Der Benutzername wird benötigt', + 'The maximum length is %d characters' => 'Die maximale Länge beträgt %d Zeichen', + 'The minimum length is %d characters' => 'Die minimale Länge beträgt %d Zeichen', + 'The password is required' => 'Das Passwort wird benötigt', + 'This value must be an integer' => 'Dieser Wert muss eine ganze Zahl sein', + 'The username must be unique' => 'Der Benutzername muss eindeutig sein', + 'The user id is required' => 'Die Benutzer-ID ist anzugeben', + 'Passwords don\'t match' => 'Passwörter nicht gleich', + 'The confirmation is required' => 'Die Bestätigung ist erforderlich', + 'The project is required' => 'Das Projekt ist anzugeben', + 'The id is required' => 'Die ID ist anzugeben', + 'The project id is required' => 'Die Projekt-ID ist anzugeben', + 'The project name is required' => 'Der Projektname ist anzugeben', + 'The title is required' => 'Der Titel ist anzugeben', + 'Settings saved successfully.' => 'Einstellungen erfolgreich gespeichert.', + 'Unable to save your settings.' => 'Speichern der Einstellungen nicht möglich.', + 'Database optimization done.' => 'Optimieren der Datenbank abgeschlossen.', + 'Your project has been created successfully.' => 'Das Projekt wurde erfolgreich erstellt.', + 'Unable to create your project.' => 'Erstellen des Projekts nicht möglich.', + 'Project updated successfully.' => 'Projekt erfolgreich geändert.', + 'Unable to update this project.' => 'Änderung des Projekts nicht möglich.', + 'Unable to remove this project.' => 'Löschen des Projekts nicht möglich.', + 'Project removed successfully.' => 'Projekt erfolgreich gelöscht.', + 'Project activated successfully.' => 'Projekt erfolgreich aktiviert.', + 'Unable to activate this project.' => 'Aktivieren des Projekts nicht möglich.', + 'Project disabled successfully.' => 'Projekt erfolgreich deaktiviert.', + 'Unable to disable this project.' => 'Deaktivieren des Projekts nicht möglich.', + 'Unable to open this task.' => 'Wiedereröffnung der Aufgabe nicht möglich.', + 'Task opened successfully.' => 'Aufgabe erfolgreich wieder geöffnet.', + 'Unable to close this task.' => 'Abschließen der Aufgabe nicht möglich.', + 'Task closed successfully.' => 'Aufgabe erfolgreich geschlossen.', + 'Unable to update your task.' => 'Aktualisieren der Aufgabe nicht möglich.', + 'Task updated successfully.' => 'Aufgabe erfolgreich aktualisiert.', + 'Unable to create your task.' => 'Erstellen der Aufgabe nicht möglich.', + 'Task created successfully.' => 'Aufgabe erfolgreich erstellt.', + 'User created successfully.' => 'Benutzer erfolgreich erstellt.', + 'Unable to create your user.' => 'Erstellen des Benutzers nicht möglich.', + 'User updated successfully.' => 'Benutzer erfolgreich geändert.', + 'User removed successfully.' => 'Benutzer erfolgreich gelöscht.', + 'Unable to remove this user.' => 'Löschen des Benutzers nicht möglich.', + 'Board updated successfully.' => 'Pinnwand erfolgreich geändert.', + 'Ready' => 'Bereit', + 'Backlog' => 'Ideen', + 'Work in progress' => 'In Arbeit', + 'Done' => 'Erledigt', + 'Application version:' => 'Version:', + 'Id' => 'ID', + 'Public link' => 'Öffentlicher Link', + 'Timezone' => 'Zeitzone', + 'Sorry, I didn\'t find this information in my database!' => 'Diese Information wurde in der Datenbank nicht gefunden!', + 'Page not found' => 'Seite nicht gefunden', + 'Complexity' => 'Komplexität', + 'Task limit' => 'Maximale Anzahl von Aufgaben', + 'Task count' => 'Aufgabenanzahl', + 'User' => 'Benutzer', + 'Comments' => 'Kommentare', + 'Comment is required' => 'Ein Kommentar wird benötigt', + 'Comment added successfully.' => 'Kommentar erfolgreich hinzugefügt.', + 'Unable to create your comment.' => 'Hinzufügen eines Kommentars nicht möglich.', + 'Due Date' => 'Fällig am', + 'Invalid date' => 'Ungültiges Datum', + 'Automatic actions' => 'Automatische Aktionen', + 'Your automatic action has been created successfully.' => 'Die automatische Aktion wurde erfolgreich erstellt.', + 'Unable to create your automatic action.' => 'Erstellen der automatischen Aktion nicht möglich.', + 'Remove an action' => 'Aktion löschen', + 'Unable to remove this action.' => 'Löschen der Aktion nicht möglich.', + 'Action removed successfully.' => 'Aktion erfolgreich gelöscht.', + 'Automatic actions for the project "%s"' => 'Automatische Aktionen für das Projekt "%s"', + 'Add an action' => 'Aktion hinzufügen', + 'Event name' => 'Ereignisname', + 'Action' => 'Aktion', + 'Event' => 'Ereignis', + 'When the selected event occurs execute the corresponding action.' => 'Wenn das gewählte Ereignis eintritt, führe die zugehörige Aktion aus.', + 'Next step' => 'Weiter', + 'Define action parameters' => 'Aktionsparameter definieren', + 'Do you really want to remove this action: "%s"?' => 'Soll diese Aktion wirklich gelöscht werden: "%s"?', + 'Remove an automatic action' => 'Löschen einer automatischen Aktion', + 'Assign the task to a specific user' => 'Aufgabe einem Benutzer zuordnen', + 'Assign the task to the person who does the action' => 'Aufgabe dem Benutzer zuordnen, der die Aktion ausgeführt hat', + 'Duplicate the task to another project' => 'Aufgabe in ein anderes Projekt kopieren', + 'Move a task to another column' => 'Aufgabe in andere Spalte verschieben', + 'Task modification' => 'Aufgabe ändern', + 'Task creation' => 'Aufgabe erstellen', + 'Closing a task' => 'Aufgabe abschließen', + 'Assign a color to a specific user' => 'Einem Benutzer eine Farbe zuordnen', + 'Position' => 'Position', + 'Duplicate to project' => 'In ein anderes Projekt duplizieren', + 'Duplicate' => 'Duplizieren', + 'Link' => 'Link', + 'Comment updated successfully.' => 'Kommentar erfolgreich aktualisiert.', + 'Unable to update your comment.' => 'Aktualisierung des Kommentars nicht möglich.', + 'Remove a comment' => 'Kommentar löschen', + 'Comment removed successfully.' => 'Kommentar erfolgreich gelöscht.', + 'Unable to remove this comment.' => 'Löschen des Kommentars nicht möglich.', + 'Do you really want to remove this comment?' => 'Soll dieser Kommentar wirklich gelöscht werden?', + 'Current password for the user "%s"' => 'Aktuelles Passwort des Benutzers "%s"', + 'The current password is required' => 'Das aktuelle Passwort wird benötigt', + 'Wrong password' => 'Falsches Passwort', + 'Unknown' => 'Unbekannt', + 'Last logins' => 'Letzte Anmeldungen', + 'Login date' => 'Anmeldedatum', + 'Authentication method' => 'Authentisierungsmethode', + 'IP address' => 'IP-Adresse', + 'User agent' => 'User-Agent', + 'Persistent connections' => 'Bestehende Verbindungen', + 'No session.' => 'Keine Sitzung.', + 'Expiration date' => 'Ablaufdatum', + 'Remember Me' => 'Angemeldet bleiben', + 'Creation date' => 'Erstellungsdatum', + 'Everybody' => 'Alle', + 'Open' => 'Offen', + 'Closed' => 'Abgeschlossen', + 'Search' => 'Suchen', + 'Nothing found.' => 'Nichts gefunden.', + 'Due date' => 'Fälligkeitsdatum', + 'Description' => 'Beschreibung', + '%d comments' => '%d Kommentare', + '%d comment' => '%d Kommentar', + 'Email address invalid' => 'Ungültige E-Mail-Adresse', + 'Your external account is not linked anymore to your profile.' => 'Dein externer Account ist nicht mehr mit deinem Profil verbunden.', + 'Unable to unlink your external account.' => 'Externer Account konnte nicht getrennt werden.', + 'External authentication failed' => 'Externe Authentifizierung fehlgeschlagen', + 'Your external account is linked to your profile successfully.' => 'Dein externer Account wurde erfolgreich mit deinem Profil verbunden', + 'Email' => 'E-Mail', + 'Task removed successfully.' => 'Aufgabe erfolgreich gelöscht.', + 'Unable to remove this task.' => 'Löschen der Aufgabe nicht möglich.', + 'Remove a task' => 'Aufgabe löschen', + 'Do you really want to remove this task: "%s"?' => 'Soll diese Aufgabe wirklich gelöscht werden: "%s"?', + 'Assign automatically a color based on a category' => 'Automatisch eine Farbe anhand der Kategorie zuweisen', + 'Assign automatically a category based on a color' => 'Automatisch eine Kategorie anhand der Farbe zuweisen', + 'Task creation or modification' => 'Aufgabe erstellen oder ändern', + 'Category' => 'Kategorie', + 'Category:' => 'Kategorie:', + 'Categories' => 'Kategorien', + 'Your category has been created successfully.' => 'Kategorie erfolgreich erstellt.', + 'This category has been updated successfully.' => 'Kategorie erfolgreich aktualisiert.', + 'Unable to update this category.' => 'Änderung der Kategorie nicht möglich.', + 'Remove a category' => 'Kategorie löschen', + 'Category removed successfully.' => 'Kategorie erfolgreich gelöscht.', + 'Unable to remove this category.' => 'Löschen der Kategorie nicht möglich.', + 'Category modification for the project "%s"' => 'Kategorie für das Projekt "%s" bearbeiten', + 'Category Name' => 'Kategoriename', + 'Add a new category' => 'Neue Kategorie', + 'Do you really want to remove this category: "%s"?' => 'Soll diese Kategorie wirklich gelöscht werden: "%s"?', + 'All categories' => 'Alle Kategorien', + 'No category' => 'Keine Kategorie', + 'The name is required' => 'Der Name ist erforderlich', + 'Remove a file' => 'Datei löschen', + 'Unable to remove this file.' => 'Löschen der Datei nicht möglich.', + 'File removed successfully.' => 'Datei erfolgreich gelöscht.', + 'Attach a document' => 'Dokument anhängen', + 'Do you really want to remove this file: "%s"?' => 'Soll diese Datei wirklich gelöscht werden: "%s"?', + 'Attachments' => 'Anhänge', + 'Edit the task' => 'Aufgabe bearbeiten', + 'Add a comment' => 'Kommentar hinzufügen', + 'Edit a comment' => 'Kommentar bearbeiten', + 'Summary' => 'Zusammenfassung', + 'Time tracking' => 'Zeiterfassung', + 'Estimate:' => 'Geschätzt:', + 'Spent:' => 'Aufgewendet:', + 'Do you really want to remove this sub-task?' => 'Soll diese Teilaufgabe wirklich gelöscht werden?', + 'Remaining:' => 'Verbleibend:', + 'hours' => 'Stunden', + 'estimated' => 'geschätzt', + 'Sub-Tasks' => 'Teilaufgaben', + 'Add a sub-task' => 'Teilaufgabe anlegen', + 'Original estimate' => 'Geschätzter Aufwand', + 'Create another sub-task' => 'Weitere Teilaufgabe anlegen', + 'Time spent' => 'Aufgewendete Zeit', + 'Edit a sub-task' => 'Teilaufgabe bearbeiten', + 'Remove a sub-task' => 'Teilaufgabe löschen', + 'The time must be a numeric value' => 'Zeit nur als nummerische Angabe', + 'Todo' => 'Nicht gestartet', + 'In progress' => 'In Bearbeitung', + 'Sub-task removed successfully.' => 'Teilaufgabe erfolgreich gelöscht.', + 'Unable to remove this sub-task.' => 'Löschen der Teilaufgabe nicht möglich.', + 'Sub-task updated successfully.' => 'Teilaufgabe erfolgreich aktualisiert.', + 'Unable to update your sub-task.' => 'Aktualisieren der Teilaufgabe nicht möglich.', + 'Unable to create your sub-task.' => 'Erstellen der Teilaufgabe nicht möglich.', + 'Maximum size: ' => 'Maximalgröße: ', + 'Display another project' => 'Zu Projekt wechseln', + 'Created by %s' => 'Erstellt durch %s', + 'Tasks Export' => 'Aufgaben exportieren', + 'Start Date' => 'Anfangsdatum', + 'Execute' => 'Ausführen', + 'Task Id' => 'Aufgaben-ID', + 'Creator' => 'Erstellt von', + 'Modification date' => 'Änderungsdatum', + 'Completion date' => 'Abschlussdatum', + 'Clone' => 'duplizieren', + 'Project cloned successfully.' => 'Projekt wurde dupliziert.', + 'Unable to clone this project.' => 'Duplizieren dieses Projekts schlug fehl.', + 'Enable email notifications' => 'E-Mail-Benachrichtigungen einschalten', + 'Task position:' => 'Position der Aufgabe:', + 'The task #%d has been opened.' => 'Die Aufgabe #%d wurde geöffnet.', + 'The task #%d has been closed.' => 'Die Aufgabe #%d wurde geschlossen.', + 'Sub-task updated' => 'Teilaufgabe aktualisiert', + 'Title:' => 'Titel:', + 'Status:' => 'Status:', + 'Assignee:' => 'Zuständigkeit:', + 'Time tracking:' => 'Zeiterfassung:', + 'New sub-task' => 'Neue Teilaufgabe', + 'New attachment added "%s"' => 'Neuer Anhang "%s" wurde hinzugefügt.', + 'New comment posted by %s' => 'Neuer Kommentar verfasst durch %s', + 'New comment' => 'Neuer Kommentar', + 'Comment updated' => 'Kommentar wurde aktualisiert', + 'New subtask' => 'Neue Teilaufgabe', + 'I only want to receive notifications for these projects:' => 'Ich möchte nur für diese Projekte Benachrichtigungen erhalten:', + 'view the task on Kanboard' => 'diese Aufgabe auf dem Kanboard zeigen', + 'Public access' => 'Öffentlicher Zugriff', + 'Disable public access' => 'Öffentlichen Zugriff deaktivieren', + 'Enable public access' => 'Öffentlichen Zugriff aktivieren', + 'Public access disabled' => 'Öffentlicher Zugriff deaktiviert', + 'Move the task to another project' => 'Aufgabe in ein anderes Projekt verschieben', + 'Move to project' => 'In anderes Projekt verschieben', + 'Do you really want to duplicate this task?' => 'Möchtest du diese Aufgabe wirklich duplizieren?', + 'Duplicate a task' => 'Aufgabe duplizieren', + 'External accounts' => 'Externe Accounts', + 'Account type' => 'Accounttyp', + 'Local' => 'Lokal', + 'Remote' => 'Remote', + 'Enabled' => 'angeschaltet', + 'Disabled' => 'abgeschaltet', + 'Login:' => 'Benutzername:', + 'Full Name:' => 'Vollständiger Name:', + 'Email:' => 'E-Mail:', + 'Notifications:' => 'Benachrichtigungen:', + 'Notifications' => 'Benachrichtigungen', + 'Account type:' => 'Accounttyp:', + 'Edit profile' => 'Profil bearbeiten', + 'Change password' => 'Passwort ändern', + 'Password modification' => 'Passwortänderung', + 'External authentications' => 'Externe Authentisierungsmethoden', + 'Never connected.' => 'Noch nie verbunden.', + 'No external authentication enabled.' => 'Es sind keine externen Authentisierungsmethoden aktiv.', + 'Password modified successfully.' => 'Passwort wurde erfolgreich geändert.', + 'Unable to change the password.' => 'Passwort konnte nicht geändert werden.', + 'Change category' => 'Kategorie ändern', + '%s updated the task %s' => '%s hat die Aufgabe %s aktualisiert', + '%s opened the task %s' => '%s hat die Aufgabe %s geöffnet', + '%s moved the task %s to the position #%d in the column "%s"' => '%s hat die Aufgabe %s auf die Position #%d in der Spalte "%s" verschoben', + '%s moved the task %s to the column "%s"' => '%s hat die Aufgabe %s in die Spalte "%s" verschoben', + '%s created the task %s' => '%s hat die Aufgabe %s angelegt', + '%s closed the task %s' => '%s hat die Aufgabe %s geschlossen', + '%s created a subtask for the task %s' => '%s hat eine Teilaufgabe für die Aufgabe %s angelegt', + '%s updated a subtask for the task %s' => '%s hat eine Teilaufgabe der Aufgabe %s verändert', + 'Assigned to %s with an estimate of %s/%sh' => 'An %s zugewiesen mit einer Schätzung von %s/%s Stunden', + 'Not assigned, estimate of %sh' => 'Nicht zugewiesen, Schätzung von %s Stunden', + '%s updated a comment on the task %s' => '%s hat einen Kommentar der Aufgabe %s aktualisiert', + '%s commented the task %s' => '%s hat die Aufgabe %s kommentiert', + '%s\'s activity' => '%s\'s Aktivität', + 'RSS feed' => 'RSS Feed', + '%s updated a comment on the task #%d' => '%s hat einen Kommentar der Aufgabe #%d aktualisiert', + '%s commented on the task #%d' => '%s hat die Aufgabe #%d kommentiert', + '%s updated a subtask for the task #%d' => '%s hat eine Teilaufgabe der Aufgabe #%d aktualisiert', + '%s created a subtask for the task #%d' => '%s hat eine Teilaufgabe der Aufgabe #%d angelegt', + '%s updated the task #%d' => '%s hat die Aufgabe #%d aktualisiert', + '%s created the task #%d' => '%s hat die Aufgabe #%d angelegt', + '%s closed the task #%d' => '%s hat die Aufgabe #%d geschlossen', + '%s opened the task #%d' => '%s hat die Aufgabe #%d geöffnet', + 'Activity' => 'Aktivität', + 'Default values are "%s"' => 'Die Standardwerte sind "%s"', + 'Default columns for new projects (Comma-separated)' => 'Standardspalten für neue Projekte (Komma getrennt)', + 'Task assignee change' => 'Zuständigkeit geändert', + '%s changed the assignee of the task #%d to %s' => '%s hat die Zuständigkeit der Aufgabe #%d geändert zu %s', + '%s changed the assignee of the task %s to %s' => '%s hat die Zuständigkeit der Aufgabe %s geändert zu %s', + 'New password for the user "%s"' => 'Neues Passwort des Benutzers "%s"', + 'Choose an event' => 'Aktion wählen', + 'Create a task from an external provider' => 'Eine Aufgabe durch einen externen Provider hinzufügen', + 'Change the assignee based on an external username' => 'Zuordnung ändern basierend auf externem Benutzernamen', + 'Change the category based on an external label' => 'Kategorie basierend auf einer externen Kennzeichnung ändern', + 'Reference' => 'Referenz', + 'Label' => 'Kennzeichnung', + 'Database' => 'Datenbank', + 'About' => 'Über', + 'Database driver:' => 'Datenbanktreiber:', + 'Board settings' => 'Pinnwandeinstellungen', + 'Webhook settings' => 'Webhook-Einstellungen', + 'Reset token' => 'Token zurücksetzen', + 'API endpoint:' => 'API-Endpunkt:', + 'Refresh interval for personal board' => 'Aktualisierungsintervall für persönliche Pinnwände', + 'Refresh interval for public board' => 'Aktualisierungsintervall für öffentliche Pinnwände', + 'Task highlight period' => 'Aufgaben-Hervorhebungsdauer', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Dauer (in Sekunden), wie lange eine Aufgabe als kürzlich verändert gilt (0 um diese Funktion zu deaktivieren, standardmäßig 2 Tage)', + 'Frequency in second (60 seconds by default)' => 'Frequenz in Sekunden (standardmäßig 60 Sekunden)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frequenz in Sekunden (0 um diese Funktion zu deaktivieren, standardmäßig 10 Sekunden)', + 'Application URL' => 'Applikations-URL', + 'Token regenerated.' => 'Token wurde neu generiert.', + 'Date format' => 'Datumsformat', + 'ISO format is always accepted, example: "%s" and "%s"' => 'ISO Format wird immer akzeptiert, z.B.: "%s" und "%s"', + 'New personal project' => 'Neues persönliches Projekt', + 'This project is personal' => 'Dies ist ein persönliches Projekt', + 'Add' => 'Hinzufügen', + 'Start date' => 'Startdatum', + 'Time estimated' => 'Geschätzte Zeit', + 'There is nothing assigned to you.' => 'Dir ist nichts zugewiesen.', + 'My tasks' => 'Meine Aufgaben', + 'Activity stream' => 'Letzte Aktivitäten', + 'Dashboard' => 'Dashboard', + 'Confirmation' => 'Wiederholung', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Create a comment from an external provider' => 'Kommentar eines externen Providers hinzufügen', + 'Project management' => 'Projektmanagement', + 'Columns' => 'Spalten', + 'Task' => 'Aufgabe', + 'Percentage' => 'Prozentsatz', + 'Number of tasks' => 'Anzahl an Aufgaben', + 'Task distribution' => 'Aufgabenverteilung', + 'Analytics' => 'Analyse', + 'Subtask' => 'Teilaufgabe', + 'User repartition' => 'Benutzerverteilung', + 'Clone this project' => 'Projekt kopieren', + 'Column removed successfully.' => 'Spalte erfolgreich entfernt.', + 'Not enough data to show the graph.' => 'Nicht genügend Daten, um die Grafik zu zeigen.', + 'Previous' => 'Vorherige', + 'The id must be an integer' => 'Die ID muss eine ganze Zahl sein', + 'The project id must be an integer' => 'Der Projekt-ID muss eine ganze Zahl sein', + 'The status must be an integer' => 'Der Status muss eine ganze Zahl sein', + 'The subtask id is required' => 'Die Teilaufgaben-ID ist benötigt', + 'The subtask id must be an integer' => 'Die Teilaufgaben-ID muss eine ganze Zahl sein', + 'The task id is required' => 'Die Aufgaben-ID ist benötigt', + 'The task id must be an integer' => 'Die Aufgaben-ID muss eine ganze Zahl sein', + 'The user id must be an integer' => 'Die Benutzer-ID muss eine ganze Zahl sein', + 'This value is required' => 'Dieser Wert ist erforderlich', + 'This value must be numeric' => 'Dieser Wert muss nummerisch sein', + 'Unable to create this task.' => 'Diese Aufgabe kann nicht erstellt werden', + 'Cumulative flow diagram' => 'Kumulatives Flussdiagramm', + 'Daily project summary' => 'Tägliche Projektzusammenfassung', + 'Daily project summary export' => 'Export der täglichen Projektzusammenfassung', + 'Exports' => 'Exporte', + 'This export contains the number of tasks per column grouped per day.' => 'Dieser Export enthält die Anzahl der Aufgaben pro Spalte nach Tagen gruppiert.', + 'Active swimlanes' => 'Aktive Swimlane', + 'Add a new swimlane' => 'Eine neue Swimlane hinzufügen', + 'Default swimlane' => 'Standard-Swimlane', + 'Do you really want to remove this swimlane: "%s"?' => 'Diese Swimlane wirklich entfernen: "%s"?', + 'Inactive swimlanes' => 'Inaktive Swimlane', + 'Remove a swimlane' => 'Swimlane entfernen', + 'Swimlane modification for the project "%s"' => 'Swimlane-Änderung für das Projekt "%s"', + 'Swimlane removed successfully.' => 'Swimlane erfolgreich entfernt.', + 'Swimlanes' => 'Swimlanes', + 'Swimlane updated successfully.' => 'Swimlane erfolgreich geändert.', + 'Unable to remove this swimlane.' => 'Es ist nicht möglich, die Swimlane zu entfernen.', + 'Unable to update this swimlane.' => 'Es ist nicht möglich, die Swimlane zu ändern.', + 'Your swimlane has been created successfully.' => 'Die Swimlane wurde erfolgreich angelegt.', + '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)', + 'Integrations' => 'Integration', + 'Integration with third-party services' => 'Integration von externen Diensten', + 'Subtask Id' => 'Teilaufgaben-ID', + 'Subtasks' => 'Teilaufgaben', + 'Subtasks Export' => 'Export von Teilaufgaben', + 'Task Title' => 'Aufgaben-Titel', + 'Untitled' => 'unbetitelt', + 'Application default' => 'Anwendungsstandard', + 'Language:' => 'Sprache:', + 'Timezone:' => 'Zeitzone:', + 'All columns' => 'Alle Spalten', + 'Next' => 'Nächste', + '#%d' => 'Nr %d', + 'All swimlanes' => 'Alle Swimlanes', + 'All colors' => 'Alle Farben', + 'Moved to column %s' => 'In Spalte %s verschoben', + 'User dashboard' => 'Benutzer-Dashboard', + 'Allow only one subtask in progress at the same time for a user' => 'Erlaube nur eine Teilaufgabe pro Benutzer zu bearbeiten', + 'Edit column "%s"' => 'Spalte "%s" bearbeiten', + 'Select the new status of the subtask: "%s"' => 'Wähle einen neuen Status für Teilaufgabe: "%s"', + 'Subtask timesheet' => 'Teilaufgaben Zeiterfassung', + 'There is nothing to show.' => 'Es ist nichts zum Anzeigen vorhanden.', + 'Time Tracking' => 'Zeiterfassung', + 'You already have one subtask in progress' => 'Bereits eine Teilaufgabe in Bearbeitung', + 'Which parts of the project do you want to duplicate?' => 'Welcher Teil des Projekts soll kopiert werden?', + 'Disallow login form' => 'Verbiete Login-Formular', + 'Start' => 'Start', + 'End' => 'Ende', + 'Task age in days' => 'Aufgabenalter in Tagen', + 'Days in this column' => 'Tage in dieser Spalte', + '%dd' => '%dT', + 'Add a new link' => 'Neue Verbindung hinzufügen', + 'Do you really want to remove this link: "%s"?' => 'Die Verbindung "%s" wirklich löschen?', + 'Do you really want to remove this link with task #%d?' => 'Die Verbindung mit der Aufgabe #%d wirklich löschen?', + 'Field required' => 'Feld erforderlich', + 'Link added successfully.' => 'Verbindung erfolgreich hinzugefügt.', + 'Link updated successfully.' => 'Verbindung erfolgreich aktualisiert.', + 'Link removed successfully.' => 'Verbindung erfolgreich gelöscht.', + 'Link labels' => 'Verbindungsbeschriftung', + 'Link modification' => 'Verbindung ändern', + 'Opposite label' => 'Gegenteil', + 'Remove a link' => 'Verbindung entfernen', + 'The labels must be different' => 'Die Beschriftung muss unterschiedlich sein', + 'There is no link.' => 'Es gibt keine Verbindung', + 'This label must be unique' => 'Die Beschriftung muss einzigartig sein', + 'Unable to create your link.' => 'Verbindung kann nicht erstellt werden.', + 'Unable to update your link.' => 'Verbindung kann nicht aktualisiert werden.', + 'Unable to remove this link.' => 'Verbindung kann nicht entfernt werden', + 'relates to' => 'gehört zu', + 'blocks' => 'blockiert', + 'is blocked by' => 'ist blockiert von', + 'duplicates' => 'doppelt', + 'is duplicated by' => 'ist gedoppelt von', + 'is a child of' => 'ist ein untergeordnetes Element von', + 'is a parent of' => 'ist ein übergeordnetes Element von', + 'targets milestone' => 'betrifft Meilenstein', + 'is a milestone of' => 'ist ein Meilenstein von', + 'fixes' => 'behebt', + 'is fixed by' => 'wird behoben von', + 'This task' => 'Diese Aufgabe', + '<1h' => '<1Std', + '%dh' => '%dStd', + 'Expand tasks' => 'Aufgaben aufklappen', + 'Collapse tasks' => 'Aufgaben zusammenklappen', + 'Expand/collapse tasks' => 'Aufgaben auf/zuklappen', + 'Close dialog box' => 'Dialog schließen', + 'Submit a form' => 'Formular abschicken', + 'Board view' => 'Pinnwand Ansicht', + 'Keyboard shortcuts' => 'Tastaturkürzel', + 'Open board switcher' => 'Pinnwandauswahl öffnen', + 'Application' => 'Anwendung', + 'Compact view' => 'Kompaktansicht', + 'Horizontal scrolling' => 'Horizontales Scrollen', + 'Compact/wide view' => 'Kompakt/Breite-Ansicht', + 'Currency' => 'Währung', + 'Personal project' => 'privates Projekt', + 'AUD - Australian Dollar' => 'AUD - Australische Dollar', + 'CAD - Canadian Dollar' => 'CAD - Kanadische Dollar', + 'CHF - Swiss Francs' => 'CHF - Schweizer Franken', + 'Custom Stylesheet' => 'benutzerdefiniertes Stylesheet', + 'EUR - Euro' => 'EUR - Euro', + 'GBP - British Pound' => 'GBP - Britische Pfund', + 'INR - Indian Rupee' => 'INR - Indische Rupien', + 'JPY - Japanese Yen' => 'JPY - Japanische Yen', + 'NZD - New Zealand Dollar' => 'NZD - Neuseeland-Dollar', + 'PEN - Peruvian Sol' => 'PEN - Peruanischer Sol', + 'RSD - Serbian dinar' => 'RSD - Serbische Dinar', + 'CNY - Chinese Yuan' => 'CNY - Chinesische Renminbi', + 'USD - US Dollar' => 'USD - US-Dollar', + 'VES - Venezuelan Bolívar' => 'VES - Venezolanische Bolívar', + 'Destination column' => 'Zielspalte', + 'Move the task to another column when assigned to a user' => 'Aufgabe in eine andere Spalte verschieben, wenn ein Benutzer 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', + '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', + 'Reference currency' => 'Referenzwährung', + 'The currency rate has been added successfully.' => 'Der Währungskurs wurde erfolgreich hinzugefügt.', + 'Unable to add this currency rate.' => 'Währungskurs konnte nicht hinzugefügt werden', + 'Webhook URL' => 'Webhook-URL', + '%s removed the assignee of the task %s' => '%s Zuordnung für die Aufgabe %s entfernen', + 'Information' => 'Information', + 'Check two factor authentication code' => 'Prüfe Zwei-Faktor-Authentifizierungscode', + 'The two factor authentication code is not valid.' => 'Der Zwei-Faktor-Authentifizierungscode ist ungültig.', + 'The two factor authentication code is valid.' => 'Der Zwei-Faktor-Authentifizierungscode ist gültig.', + 'Code' => 'Code', + 'Two factor authentication' => 'Zwei-Faktor-Authentifizierung', + 'This QR code contains the key URI: ' => 'Dieser QR-Code beinhaltet die Schlüssel-URI: ', + 'Check my code' => 'Überprüfe meinen Code', + 'Secret key: ' => 'Geheimer Schlüssel: ', + 'Test your device' => 'Teste dein Gerät', + 'Assign a color when the task is moved to a specific column' => 'Weise eine Farbe zu, wenn die Aufgabe zu einer bestimmten Spalte bewegt wird', + '%s via Kanboard' => '%s via Kanboard', + 'Burndown chart' => 'Burndown-Diagramm', + 'This chart show the task complexity over the time (Work Remaining).' => 'Dieses Diagramm zeigt die Aufgabenkomplexität über den Faktor Zeit (Verbleibende Arbeit).', + 'Screenshot taken %s' => 'Screenshot aufgenommen %s', + 'Add a screenshot' => 'Screenshot hinzufügen', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Nimm einen Screenshot auf und drücke STRG+V oder ⌘+V um ihn hier einzufügen.', + 'Screenshot uploaded successfully.' => 'Screenshot erfolgreich hochgeladen.', + 'SEK - Swedish Krona' => 'SEK - Schwedische Kronen', + 'Identifier' => 'Identifikator', + 'Disable two factor authentication' => 'Deaktiviere Zwei-Faktor-Authentifizierung', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Willst du wirklich für folgenden Benutzer die Zwei-Faktor-Authentifizierung deaktivieren: "%s"?', + 'Edit link' => 'Verbindung bearbeiten', + 'Start to type task title...' => 'Beginne mit der Titeleingabe...', + 'A task cannot be linked to itself' => 'Eine Aufgabe kann nicht mit sich selber verbunden werden', + 'The exact same link already exists' => 'Diese Verbindung existiert bereits', + 'Recurrent task is scheduled to be generated' => 'Wiederkehrende Aufgabe ist zur Generierung eingeplant', + 'Score' => 'Wertung', + 'The identifier must be unique' => 'Der Schlüssel muss einzigartig sein', + 'This linked task id doesn\'t exists' => 'Die verbundene Aufgabe existiert nicht', + 'This value must be alphanumeric' => 'Der Wert muss alphanumerisch sein', + 'Edit recurrence' => 'Wiederholung bearbeiten', + 'Generate recurrent task' => 'Wiederkehrende Aufgabe generieren', + 'Trigger to generate recurrent task' => 'Auslöser für wiederkehrende Aufgabe', + 'Factor to calculate new due date' => 'Faktor zur Berechnung für neues Ablaufdatum', + 'Timeframe to calculate new due date' => 'Zeitfenster zur Berechnung für neues Ablaufdatum', + 'Base date to calculate new due date' => 'Basisdatum zur Berechnung für neues Ablaufdatum', + 'Action date' => 'Aktionsdatum', + 'Base date to calculate new due date: ' => 'Basisdatum zur Berechnung für neues Ablaufdatum: ', + 'This task has created this child task: ' => 'Diese Aufgabe hat diese Teilaufgabe erstellt: ', + 'Day(s)' => 'Tag(e)', + 'Existing due date' => 'Existierendes Ablaufdatum', + 'Factor to calculate new due date: ' => 'Faktor zur Berechnung für neues Ablaufdatum: ', + 'Month(s)' => 'Monat(e)', + 'This task has been created by: ' => 'Diese Aufgabe wurde erstellt von: ', + 'Recurrent task has been generated:' => 'Wiederkehrende Aufgabe wurde erstellt:', + 'Timeframe to calculate new due date: ' => 'Zeitfenster zur Berechnung für neues Ablaufdatum: ', + 'Trigger to generate recurrent task: ' => 'Auslöser für wiederkehrende Aufgabe: ', + 'When task is closed' => 'Wenn Aufgabe geschlossen wird', + 'When task is moved from first column' => 'Wenn Aufgabe von erster Spalte verschoben wird', + 'When task is moved to last column' => 'Wenn Aufgabe in letzte Spalte verschoben wird', + 'Year(s)' => 'Jahr(e)', + 'Project settings' => 'Projekteinstellungen', + 'Automatically update the start date' => 'Beginndatum automatisch aktualisieren', + 'iCal feed' => 'iCal Feed', + 'Preferences' => 'Einstellungen', + 'Security' => 'Sicherheit', + 'Two factor authentication disabled' => 'Zwei-Faktor-Authentifizierung deaktiviert', + 'Two factor authentication enabled' => 'Zwei-Faktor-Authentifizierung aktiviert', + 'Unable to update this user.' => 'Benutzer kann nicht bearbeitet werden', + 'There is no user management for personal projects.' => 'Es gibt keine Benutzerverwaltung für persönliche Projekte', + 'User that will receive the email' => 'Empfänger der E-Mail', + 'Email subject' => 'E-Mail-Betreff', + 'Date' => 'Datum', + 'Add a comment log when moving the task between columns' => 'Kommentar hinzufügen, wenn Aufgabe in andere Spalte verschoben wird', + 'Move the task to another column when the category is changed' => 'Aufgabe in andere Spalte verschieben, wenn Kategorie geändert wird', + 'Send a task by email to someone' => 'Aufgabe per E-Mail versenden', + 'Reopen a task' => 'Aufgabe wieder öffnen', + 'Notification' => 'Benachrichtigungen', + '%s moved the task #%d to the first swimlane' => '%s hat die Aufgabe #%d in die erste Swimlane verschoben', + 'Swimlane' => 'Swimlane', + '%s moved the task %s to the first swimlane' => '%s hat die Aufgabe %s in die erste Swimlane verschoben', + '%s moved the task %s to the swimlane "%s"' => '%s hat die Aufgaben %s in die Swimlane "%s" verschoben', + 'This report contains all subtasks information for the given date range.' => 'Der Bericht beinhaltet alle Teilaufgaben im gewählten Zeitraum', + 'This report contains all tasks information for the given date range.' => 'Der Bericht beinhaltet alle Aufgaben im gewählten Zeitraum', + 'Project activities for %s' => 'Projektaktivitäten für %s', + 'view the board on Kanboard' => 'Pinnwand in Kanboard anzeigen', + 'The task has been moved to the first swimlane' => 'Die Aufgabe wurde in die erste Swimlane verschoben', + 'The task has been moved to another swimlane:' => 'Die Aufgaben wurde in eine andere Swimlane verschoben:', + 'New title: %s' => 'Neuer Titel: %s', + 'The task is not assigned anymore' => 'Die Aufgabe ist nicht mehr zugewiesen', + 'New assignee: %s' => 'Neue Zuordnung: %s', + 'There is no category now' => 'Es gibt keine Kategorie im Moment', + 'New category: %s' => 'Neue Kategorie: %s', + 'New color: %s' => 'Neue Farbe: %s', + 'New complexity: %d' => 'Neue Komplexität: %d', + 'The due date has been removed' => 'Das Ablaufdatum wurde entfernt', + 'There is no description anymore' => 'Es gibt keine Beschreibung mehr', + 'Recurrence settings has been modified' => 'Die Einstellungen für Wiederholung wurden geändert', + 'Time spent changed: %sh' => 'Verbrauchte Zeit geändert: %sh', + 'Time estimated changed: %sh' => 'Geschätzte Zeit geändert: %sh', + 'The field "%s" has been updated' => 'Das Feld "%s" wurde verändert', + 'The description has been modified:' => 'Die Beschreibung wurde geändert:', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Soll die Aufgabe "%s" wirklich geschlossen werden? (einschließlich Teilaufgaben)', + 'I want to receive notifications for:' => 'Ich möchte Benachrichtigungen erhalten für:', + 'All tasks' => 'Alle Aufgaben', + 'Only for tasks assigned to me' => 'nur mir zugeordnete Aufgaben', + 'Only for tasks created by me' => 'nur von mir erstellte Aufgaben', + 'Only for tasks created by me and tasks assigned to me' => 'nur mir zugeordnete und von mir erstellte Aufgaben', + '%%Y-%%m-%%d' => '%%d.%%m.%%Y', + 'Total for all columns' => 'Gesamt für alle Spalten', + 'You need at least 2 days of data to show the chart.' => 'Es werden mindestens 2 Tage zur Darstellung benötigt', + '<15m' => '<15min', + '<30m' => '<30min', + 'Stop timer' => 'Stoppe Timer', + 'Start timer' => 'Starte Timer', + 'My activity stream' => 'Aktivitätsstream', + 'Search tasks' => 'Suche nach Aufgaben', + 'Reset filters' => 'Filter zurücksetzen', + 'My tasks due tomorrow' => 'Meine morgen fälligen Aufgaben', + 'Tasks due today' => 'Heute fällige Aufgaben', + 'Tasks due tomorrow' => 'Morgen fällige Aufgaben', + 'Tasks due yesterday' => 'Gestern fällige Aufgaben', + 'Closed tasks' => 'Abgeschlossene Aufgaben', + 'Open tasks' => 'Offene Aufgaben', + 'Not assigned' => 'Nicht zugewiesen', + 'View advanced search syntax' => 'Zur erweiterten Suchsyntax', + 'Overview' => 'Überblick', + 'Board/Calendar/List view' => 'Board-/Kalender-/Listen-Ansicht', + 'Switch to the board view' => 'Zur Board-Ansicht', + 'Switch to the list view' => 'Zur Listen-Ansicht', + 'Go to the search/filter box' => 'Zum Such- und Filterfeld', + 'There is no activity yet.' => 'Es gibt bislang keine Aktivitäten.', + 'No tasks found.' => 'Keine Aufgaben gefunden.', + 'Keyboard shortcut: "%s"' => 'Tastaturkürzel: "%s"', + 'List' => 'Liste', + 'Filter' => 'Filter', + 'Advanced search' => 'Fortgeschrittene Suche', + 'Example of query: ' => 'Beispiel einer Abfrage: ', + 'Search by project: ' => 'Suche nach Projekt: ', + 'Search by column: ' => 'Suche nach Spalte: ', + 'Search by assignee: ' => 'Suche nach zugeordnetem Benutzer: ', + 'Search by color: ' => 'Suche nach Farbe: ', + 'Search by category: ' => 'Suche nach Kategorie: ', + 'Search by description: ' => 'Suche nach Beschreibung: ', + 'Search by due date: ' => 'Suche nach Fälligkeitsdatum: ', + 'Average time spent in each column' => 'Durchschnittszeit in jeder Spalte', + 'Average time spent' => 'Durchschnittlicher Zeitverbrauch', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Dieses Diagramm zeigt die durchschnittliche Zeit in jeder Spalte der letzten %d Aufgaben.', + 'Average Lead and Cycle time' => 'Durchschnittliche Zyklus- und Durchlaufzeit', + 'Average lead time: ' => 'Durchschnittliche Durchlaufzeit: ', + 'Average cycle time: ' => 'Durchschnittliche Zykluszeit: ', + 'Cycle Time' => 'Zykluszeit', + 'Lead Time' => 'Durchlaufzeit', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Das Diagramm zeigt die durchschnittliche Durchlauf- und Zykluszeit der letzten %d Aufgaben über die Zeit an.', + 'Average time into each column' => 'Durchschnittszeit in jeder Spalte', + 'Lead and cycle time' => 'Durchlauf- und Zykluszeit', + 'Lead time: ' => 'Durchlaufzeit: ', + 'Cycle time: ' => 'Zykluszeit: ', + 'Time spent in each column' => 'zeit verbracht in jeder Spalte', + 'The lead time is the duration between the task creation and the completion.' => 'Die Durchlaufzeit ist die Dauer zwischen Erstellung und Fertigstellung.', + 'The cycle time is the duration between the start date and the completion.' => 'Die Zykluszeit ist die Dauer zwischen Start und Fertigstellung.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Wenn die Aufgabe nicht geschlossen ist, wird die aktuelle Zeit statt der Fertigstellung verwendet.', + 'Set the start date automatically' => 'Setze Startdatum automatisch', + 'Edit Authentication' => 'Authentifizierung bearbeiten', + 'Remote user' => 'Remote-Benutzer', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Remote-Benutzer haben kein Passwort in der Kanboard Datenbank, Beispiel: LDAP, Google und Github Accounts', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Wenn die Box "Verbiete Login-Formular" angeschaltet ist, werden Eingaben in das Login Formular ignoriert.', + 'Default task color' => 'Voreingestellte Aufgabenfarbe', + 'This feature does not work with all browsers.' => 'Diese Funktion funktioniert nicht mit allen Browsern', + 'There is no destination project available.' => 'Es ist kein Zielprojekt vorhanden.', + 'Trigger automatically subtask time tracking' => 'Teilaufgaben Zeiterfassung automatisch starten', + 'Include closed tasks in the cumulative flow diagram' => 'Geschlossen Aufgaben ins kumulative Flussdiagramm einschließen', + 'Current swimlane: %s' => 'Aktuelle Swimlane: %s', + 'Current column: %s' => 'Aktuelle Spalte: %s', + 'Current category: %s' => 'Aktuelle Kategorie: %s', + 'no category' => 'keine Kategorie', + 'Current assignee: %s' => 'Aktuelle Zuordnung: %s', + 'not assigned' => 'nicht zugeordnet', + 'Author:' => 'Autor:', + 'contributors' => 'Mitwirkende', + 'License:' => 'Lizenz:', + 'License' => 'Lizenz', + 'Enter the text below' => 'Text unten eingeben', + 'Start date:' => 'Startdatum:', + 'Due date:' => 'Ablaufdatum:', + 'People who are project managers' => 'Benutzer die Projektmanager sind', + 'People who are project members' => 'Benutzer die Projektmitglieder sind', + 'NOK - Norwegian Krone' => 'NOK - Norwegische Kronen', + 'Show this column' => 'Spalte anzeigen', + 'Hide this column' => 'Spalte verstecken', + 'End date' => 'Endedatum', + 'Users overview' => 'Benutzerübersicht', + 'Members' => 'Mitglieder', + 'Shared project' => 'Geteiltes Projekt', + 'Project managers' => 'Projektmanager', + 'Projects list' => 'Projektliste', + 'End date:' => 'Endedatum:', + 'Change task color when using a specific task link' => 'Aufgabefarbe ändern bei bestimmter Aufgabenverbindung', + 'Task link creation or modification' => 'Aufgabenverbindung erstellen oder bearbeiten', + 'Milestone' => 'Meilenstein', + 'Reset the search/filter box' => 'Suche/Filter-Box zurücksetzen', + 'Documentation' => 'Dokumentation', + 'Author' => 'Autor', + 'Version' => 'Version', + 'Plugins' => 'Plugins', + 'There is no plugin loaded.' => 'Es ist kein Plugin geladen.', + 'My notifications' => 'Meine Benachrichtigungen', + 'Custom filters' => 'Benutzerdefinierte Filter', + 'Your custom filter has been created successfully.' => 'Benutzerdefinierten Filter erfolgreich erstellt.', + 'Unable to create your custom filter.' => 'Benutzerdefinierter Filter konnte nicht erstellt werden.', + 'Custom filter removed successfully.' => 'Benutzerdefinierten Filter erfolgreich entfernt.', + 'Unable to remove this custom filter.' => 'Benutzerdefinierten Filter konnte nicht entfernt werden.', + 'Edit custom filter' => 'Benutzerdefinierten Filter bearbeiten', + 'Your custom filter has been updated successfully.' => 'Benutzerdefinierten Filter erfolgreich bearbeitet.', + 'Unable to update custom filter.' => 'Benutzerdefinierter Filter konnte nicht geändert werden.', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Neuer Anhang für Aufgabe #%d: %s', + 'New comment on task #%d' => 'Neuer Kommentar für Aufgabe #%d', + 'Comment updated on task #%d' => 'Kommentar geändert für Aufgabe #%d', + 'New subtask on task #%d' => 'Neue Teilaufgabe für Aufgabe #%d', + 'Subtask updated on task #%d' => 'Teilaufgabe geändert für Aufgabe #%d', + 'New task #%d: %s' => 'Neue Aufgabe #%d: %s', + 'Task updated #%d' => 'Aufgabe bearbeitet #%d', + 'Task #%d closed' => 'Aufgabe #%d geschlossen', + 'Task #%d opened' => 'Aufgabe #%d eröffnet', + 'Column changed for task #%d' => 'Spalte geändert von Aufgabe #%d', + 'New position for task #%d' => 'Neue Position für Aufgabe #%d', + 'Swimlane changed for task #%d' => 'Neue Swimlane für Aufgabe #%d', + 'Assignee changed on task #%d' => 'Neue Zuordnung für Aufgabe #%d ', + '%d overdue tasks' => '%d überfällige Aufgaben', + 'No notification.' => 'Keine neuen Benachrichtigungen', + 'Mark all as read' => 'Alles als gelesen markieren', + 'Mark as read' => 'Als gelesen markieren', + 'Total number of tasks in this column across all swimlanes' => 'Anzahl an Aufgaben in dieser Spalte über alle Swimlanes', + 'Collapse swimlane' => 'Swimlane einklappen', + 'Expand swimlane' => 'Swimlane ausklappen', + 'Add a new filter' => 'Neuen Filter hinzufügen', + 'Share with all project members' => 'Mit allen Projektmitgliedern teilen.', + 'Shared' => 'Geteilt', + 'Owner' => 'Eigentümer', + 'Unread notifications' => 'Ungelesene Benachrichtigungen', + 'Notification methods:' => 'Benachrichtigungs-Methoden:', + 'Unable to read your file' => 'Die Datei kann nicht gelesen werden', + '%d task(s) have been imported successfully.' => '%d Aufgabe(n) wurde(n) erfolgreich importiert', + 'Nothing has been imported!' => 'Es wurde nichts importiert!', + 'Import users from CSV file' => 'Importiere Benutzer aus CSV Datei', + '%d user(s) have been imported successfully.' => '%d Benutzer wurde(n) erfolgreich importiert.', + 'Comma' => 'Komma', + 'Semi-colon' => 'Semikolon', + 'Tab' => 'Tabulator', + 'Vertical bar' => 'senkrechter Strich', + 'Double Quote' => 'Doppelte Anführungszeichen', + 'Single Quote' => 'Einfache Anführungszeichen', + '%s attached a file to the task #%d' => '%s hat eine Datei zur Aufgabe #%d hinzugefügt', + 'There is no column or swimlane activated in your project!' => 'Es ist keine Spalte oder Swimlane in deinem Projekt aktiviert!', + 'Append filter (instead of replacement)' => 'Filter anhängen (statt zu ersetzen)', + 'Append/Replace' => 'Anhängen/Ersetzen', + 'Append' => 'Anhängen', + 'Replace' => 'Ersetzen', + 'Import' => 'Import', + 'Change sorting' => 'Sortierung ändern', + 'Tasks Importation' => 'Aufgaben Import', + 'Delimiter' => 'Trennzeichen', + 'Enclosure' => 'Textbegrenzer', + 'CSV File' => 'CSV Datei', + 'Instructions' => 'Anweisungen', + 'Your file must use the predefined CSV format' => 'Deine Datei muss das vorgegebene CSV Format haben', + 'Your file must be encoded in UTF-8' => 'Deine Datei muss UTF-8 kodiert sein', + 'The first row must be the header' => 'Die erste Zeile muss die Kopfzeile sein', + 'Duplicates are not verified for you' => 'Duplikate werden nicht für dich geprüft', + 'The due date must use the ISO format: YYYY-MM-DD' => 'Das Fälligkeitsdatum muss das ISO Format haben: YYYY-MM-DD', + 'Download CSV template' => 'CSV Vorlage herunterladen', + 'No external integration registered.' => 'Keine externe Integration registriert', + 'Duplicates are not imported' => 'Duplikate wurden nicht importiert', + 'Usernames must be lowercase and unique' => 'Benutzernamen müssen in Kleinbuchstaben und eindeutig sein', + 'Passwords will be encrypted if present' => 'Passwörter werden verschlüsselt wenn vorhanden', + '%s attached a new file to the task %s' => '%s hat eine neue Datei zur Aufgabe %s hinzugefügt', + 'Link type' => 'Verbindungstyp', + 'Assign automatically a category based on a link' => 'Linkbasiert eine Kategorie automatisch zuordnen', + 'BAM - Konvertible Mark' => 'BAM - Konvertible Mark', + 'Assignee Username' => 'Benutzername des Zuständigen', + 'Assignee Name' => 'Name des Zuständigen', + 'Groups' => 'Gruppen', + 'Members of %s' => 'Mitglied von %s', + 'New group' => 'Neue Gruppe', + 'Group created successfully.' => 'Gruppe erfolgreich angelegt.', + 'Unable to create your group.' => 'Gruppe konnte nicht angelegt werden', + 'Edit group' => 'Gruppe bearbeiten', + 'Group updated successfully.' => 'Gruppe erfolgreich aktualisiert', + 'Unable to update your group.' => 'Gruppe konnte nicht aktualisiert werden', + 'Add group member to "%s"' => 'Gruppenmitglied zu "%s" hinzufügen', + 'Group member added successfully.' => 'Gruppenmitglied erfolgreich hinzugefügt', + 'Unable to add group member.' => 'Gruppenmitglied konnte nicht hinzugefügt werden.', + 'Remove user from group "%s"' => 'Benutzer aus Gruppe "%s" löschen', + 'User removed successfully from this group.' => 'Benutzer erfolgreich aus dieser Gruppe gelöscht.', + 'Unable to remove this user from the group.' => 'Benutzer konnte nicht aus dieser Gruppe gelöscht werden.', + 'Remove group' => 'Gruppe löschen', + 'Group removed successfully.' => 'Gruppe erfolgreich gelöscht.', + 'Unable to remove this group.' => 'Gruppe konnte nicht gelöscht werden.', + 'Project Permissions' => 'Projekt Berechtigungen', + 'Manager' => 'Manager', + 'Project Manager' => 'Projekt Manager', + 'Project Member' => 'Projekt Mitglied', + 'Project Viewer' => 'Projekt Betrachter', + 'Your account is locked for %d minutes' => 'Dein Zugang wurde für %d Minuten gesperrt', + 'Invalid captcha' => 'Ungültiges Captcha', + 'The name must be unique' => 'Der Name muss eindeutig sein', + 'View all groups' => 'Alle Gruppen anzeigen', + 'There is no user available.' => 'Es ist kein Benutzer verfügbar.', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Willst du den Benutzer "%s" wirklich aus der Gruppe "%s" löschen?', + 'There is no group.' => 'Es gibt keine Gruppe.', + 'Add group member' => 'Gruppenmitglied hinzufügen', + 'Do you really want to remove this group: "%s"?' => 'Willst du die Gruppe "%s" wirklich löschen?', + 'There is no user in this group.' => 'Es gibt keinen Benutzer in dieser Gruppe.', + 'Permissions' => 'Berechtigungen', + 'Allowed Users' => 'Berechtigte Benutzer', + 'No specific user has been allowed.' => 'Keine Benutzer mit ausdrücklicher Berechtigung.', + 'Role' => 'Rolle', + 'Enter user name...' => 'Gib den Benutzernamen ein...', + 'Allowed Groups' => 'Berechtigte Gruppen', + 'No group has been allowed.' => 'Keine Gruppen mit ausdrücklicher Berechtigung.', + 'Group' => 'Gruppe', + 'Group Name' => 'Gruppenname', + 'Enter group name...' => 'Gib den Gruppennamen ein...', + 'Role:' => 'Rolle:', + 'Project members' => 'Projektmitglieder', + '%s mentioned you in the task #%d' => '%s erwähnte dich in Aufgabe #%d', + '%s mentioned you in a comment on the task #%d' => '%s erwähnte dich in einem Kommentar zur Aufgabe #%d', + 'You were mentioned in the task #%d' => 'Du wurdest in der Aufgabe #%d erwähnt', + 'You were mentioned in a comment on the task #%d' => 'Du wurdest in einem Kommentar zur Aufgabe #%d erwähnt', + 'Estimated hours: ' => 'Erwarteter Zeitaufwand (Stunden): ', + 'Actual hours: ' => 'Tatsächlich aufgewendete Stunden: ', + 'Hours Spent' => 'Stunden aufgewendet', + 'Hours Estimated' => 'Stunden erwartet', + 'Estimated Time' => 'Erwartete Zeit', + 'Actual Time' => 'Aktuelle Zeit', + 'Estimated vs actual time' => 'Erwarteter vs. tatsächlicher Zeitaufwand', + 'RUB - Russian Ruble' => 'RUB - Russische Rubel', + 'Assign the task to the person who does the action when the column is changed' => 'Aufgabe der Person zuordnen, die die Aktion durchführt, wenn die Spalte geändert wird', + 'Close a task in a specific column' => 'Schließe eine Aufgabe in einer bestimmten Spalte', + 'Time-based One-time Password Algorithm' => 'Zeitbasierter Einmalpasswort Algorithmus', + 'Two-Factor Provider: ' => '2FA Anbieter: ', + 'Disable two-factor authentication' => 'Zwei-Faktor-Authentifizierung deaktivieren', + 'Enable two-factor authentication' => 'Zwei-Faktor-Authentifizierung aktivieren', + 'There is no integration registered at the moment.' => 'Derzeit ist kein externer Dienst registriert.', + 'Password Reset for Kanboard' => 'Zurücksetzen des Passwortes für Kanboard', + 'Forgot password?' => 'Passwort vergessen?', + 'Enable "Forget Password"' => 'Passwortrücksetzung aktivieren', + 'Password Reset' => 'Passwort zurücksetzen', + 'New password' => 'Neues Passwort', + 'Change Password' => 'Passwort ändern', + 'To reset your password click on this link:' => 'Bitte auf den Link klicken, um dein Passwort zurückzusetzen.', + 'Last Password Reset' => 'Verlauf der Passwortrücksetzung', + 'The password has never been reinitialized.' => 'Das Passwort wurde noch nie zurückgesetzt.', + 'Creation' => 'Erstellung', + 'Expiration' => 'Ablauf', + 'Password reset history' => 'Verlauf Passwortrücksetzung', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Alle Aufgaben der Spalte "%s" und der Swimlane "%s" wurden erfolgreich geschlossen', + 'Do you really want to close all tasks of this column?' => 'Willst du wirklich alle Aufgaben in dieser Spalte schließen?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d Aufgabe(n) in der Spalte "%s" und in der Swimlane "%s" werden geschlossen.', + 'Close all tasks in this column and this swimlane' => 'Alle Aufgaben in dieser Spalte schließen', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Kein Plugin hat eine Projekt-Benachrichtigungsmethode registriert. Du kannst individuelle Meldungen in deinem Benutzerprofil konfigurieren', + 'My dashboard' => 'Mein Dashboard', + 'My profile' => 'Mein Profil', + 'Project owner: ' => 'Projekt-Besitzer: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'Die Projekt-Kennung ist optional und muss alphanumerisch sein, beispielsweise: MYPROJECT.', + 'Project owner' => 'Projekt-Besitzer', + 'Personal projects do not have users and groups management.' => 'Private Projekte haben kein Benutzer- und Gruppen-Management.', + 'There is no project member.' => 'Es gibt kein Projekt-Mitglied.', + 'Priority' => 'Priorität', + 'Task priority' => 'Aufgaben-Priorität', + 'General' => 'Allgemein', + 'Dates' => 'Daten', + 'Default priority' => 'Standard-Priorität', + 'Lowest priority' => 'Niedrigste Priorität', + 'Highest priority' => 'Höchste Priorität', + 'Close a task when there is no activity' => 'Schließe eine Aufgabe, wenn keine Aktivitäten vorhanden sind', + 'Duration in days' => 'Dauer in Tagen', + 'Send email when there is no activity on a task' => 'Versende eine E-Mail, wenn keine Aktivitäten an einer Aufgabe vorhanden sind', + 'Unable to fetch link information.' => 'Kann keine Informationen über Verbindungen holen', + 'Daily background job for tasks' => 'Tägliche Hintergrundarbeit für Aufgaben', + 'Auto' => 'Auto', + 'Related' => 'Verbunden', + 'Attachment' => 'Anhang', + 'Web Link' => 'Weblink', + 'External links' => 'Externe Verbindungen', + 'Add external link' => 'Externe Verbindung hinzufügen', + 'Type' => 'Typ', + 'Dependency' => 'Abhängigkeit', + 'Add internal link' => 'Interne Verbindung hinzufügen', + 'Add a new external link' => 'Füge eine neue externe Verbindung hinzu', + 'Edit external link' => 'Externe Verbindung bearbeiten', + 'External link' => 'Externe Verbindung', + 'Copy and paste your link here...' => 'Kopiere deinen Link hierher...', + 'URL' => 'URL', + 'Internal links' => 'Interne Verbindungen', + 'Assign to me' => 'Mir zuweisen', + 'Me' => 'Mich', + 'Do not duplicate anything' => 'Nichts duplizieren', + 'Projects management' => 'Projektmanagement', + 'Users management' => 'Benutzermanagement', + 'Groups management' => 'Gruppenmanagement', + 'Create from another project' => 'Von einem anderen Projekt erstellen', + 'open' => 'offen', + 'closed' => 'geschlossen', + 'Priority:' => 'Priorität:', + 'Reference:' => 'Referenz:', + 'Complexity:' => 'Komplexität:', + 'Swimlane:' => 'Swimlane:', + 'Column:' => 'Spalte:', + 'Position:' => 'Position:', + 'Creator:' => 'Ersteller:', + 'Time estimated:' => 'Geschätzte Zeit:', + '%s hours' => '%s Stunden', + 'Time spent:' => 'Aufgewendete Zeit:', + 'Created:' => 'Erstellt:', + 'Modified:' => 'Geändert:', + 'Completed:' => 'Abgeschlossen:', + 'Started:' => 'Gestartet:', + 'Moved:' => 'Verschoben:', + 'Task #%d' => 'Aufgabe #%d', + 'Time format' => 'Zeitformat', + 'Start date: ' => 'Anfangsdatum: ', + 'End date: ' => 'Enddatum: ', + 'New due date: ' => 'Neues Fälligkeitsdatum: ', + 'Start date changed: ' => 'Anfangsdatum geändert: ', + 'Disable personal projects' => 'Persönliche Projekte deaktivieren', + 'Do you really want to remove this custom filter: "%s"?' => 'Willst du diesen benutzerdefinierten Filter wirklich entfernen: "%s"?', + 'Remove a custom filter' => 'Benutzerdefinierten Filter entfernen', + 'User activated successfully.' => 'Benutzer erfolgreich aktiviert.', + 'Unable to enable this user.' => 'Dieser Benutzer kann nicht aktiviert werden.', + 'User disabled successfully.' => 'Benutzer erfolgreich deaktiviert.', + 'Unable to disable this user.' => 'Dieser Benutzer kann nicht deaktiviert werden.', + 'All files have been uploaded successfully.' => 'Alle Dateien wurden erfolgreich hochgeladen.', + 'The maximum allowed file size is %sB.' => 'Die maximal erlaubte Dateigröße ist %sB.', + 'Drag and drop your files here' => 'Ziehe deine Dateien hier hin', + 'choose files' => 'Dateien auswählen', + 'View profile' => 'Profil ansehen', + 'Two Factor' => 'Zwei-Faktor', + 'Disable user' => 'Benutzer deaktivieren', + 'Do you really want to disable this user: "%s"?' => 'Willst du diesen Benutzer wirklich deaktivieren: "%s"?', + 'Enable user' => 'Benutzer aktivieren', + 'Do you really want to enable this user: "%s"?' => 'Willst du diesen Benutzer wirklich aktivieren: "%s"?', + 'Download' => 'Herunterladen', + 'Uploaded: %s' => 'Hochgeladen: %s', + 'Size: %s' => 'Größe: %s', + 'Uploaded by %s' => 'Hochgeladen von %s', + 'Filename' => 'Dateiname', + 'Size' => 'Größe', + 'Column created successfully.' => 'Spalte erfolgreich erstellt.', + 'Another column with the same name exists in the project' => 'Es gibt bereits eine Spalte mit demselben Namen im Projekt', + 'Default filters' => 'Standard-Filter', + 'Your board doesn\'t have any columns!' => 'Es gibt keine Spalten in diesem Projekt!', + 'Change column position' => 'Position der Spalte ändern', + 'Switch to the project overview' => 'Zur Projektübersicht wechseln', + 'User filters' => 'Benutzer-Filter', + 'Category filters' => 'Kategorie-Filter', + 'Upload a file' => 'Eine Datei hochladen', + 'View file' => 'Datei ansehen', + 'Last activity' => 'Letzte Aktivität', + 'Change subtask position' => 'Position der Teilaufgabe ändern', + 'This value must be greater than %d' => 'Dieser Wert muss größer als %d sein', + 'Another swimlane with the same name exists in the project' => 'Es gibt bereits eine Swimlane mit diesem Namen im Projekt', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Beispiel: https://example.kanboard.org/ (wird zum Erstellen absoluter URLs genutzt)', + 'Actions duplicated successfully.' => 'Aktionen erfolgreich dupliziert', + 'Unable to duplicate actions.' => 'Aktionen können nicht dupliziert werden.', + 'Add a new action' => 'Neue Aktion hinzufügen', + 'Import from another project' => 'Von einem anderen Projekt importieren', + 'There is no action at the moment.' => 'Es gibt zur Zeit keine Aktionen.', + 'Import actions from another project' => 'Aktionen von einem anderen Projekt importieren', + 'There is no available project.' => 'Es ist kein Projekt verfügbar.', + 'Local File' => 'Lokale Datei', + 'Configuration' => 'Konfiguration', + 'PHP version:' => 'PHP Version:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'OS Version:', + 'Database version:' => 'Datenbank Version:', + 'Browser:' => 'Browser:', + 'Task view' => 'Aufgaben Ansicht', + 'Edit task' => 'Aufgabe bearbeiten', + 'Edit description' => 'Beschreibung bearbeiten', + 'New internal link' => 'Neue interne Verbindung', + 'Display list of keyboard shortcuts' => 'Liste der Tastaturkürzel anzeigen', + 'Avatar' => 'Avatar', + 'Upload my avatar image' => 'Mein Avatar Bild hochladen', + 'Remove my image' => 'Mein Bild entfernen', + 'The OAuth2 state parameter is invalid' => 'Der OAuth2 Statusparameter ist ungültig', + 'User not found.' => 'Benutzer nicht gefunden', + 'Search in activity stream' => 'Im Aktivitätenstrom suchen', + 'My activities' => 'Meine Aktivitäten', + 'Activity until yesterday' => 'Aktivitäten bis gestern', + 'Activity until today' => 'Aktivitäten bis heute', + 'Search by creator: ' => 'nach Ersteller suchen:', + 'Search by creation date: ' => 'nach Datum suchen:', + 'Search by task status: ' => 'nach Aufgabenstatus suchen:', + 'Search by task title: ' => 'nach Titel suchen:', + 'Activity stream search' => 'Im Aktivitätenstrom suchen', + 'Projects where "%s" is manager' => 'Projekte in denen "%s" Manager ist', + 'Projects where "%s" is member' => 'Projekte in denen "%s" Mitglied ist', + 'Open tasks assigned to "%s"' => 'Offene Aufgaben, die "%s" zugeteilt sind', + 'Closed tasks assigned to "%s"' => 'Geschlossene Aufgaben, die "%s" zugeteilt sind', + 'Assign automatically a color based on a priority' => 'Eine Farbe basierend auf einer Priorität automatisch zuordnen', + 'Overdue tasks for the project(s) "%s"' => 'Überfällige Aufgaben des/der Projekt/e "%s"', + 'Upload files' => 'Dateien hochladen', + 'Installed Plugins' => 'Installierte Plugins', + 'Plugin Directory' => 'Plugin Verzeichnis', + 'Plugin installed successfully.' => 'Plugin erfolgreich installiert.', + 'Plugin updated successfully.' => 'Plugin erfolgreich aktualisiert.', + 'Plugin removed successfully.' => 'Plugin erfolgreich entfernt.', + 'Subtask converted to task successfully.' => 'Teilaufgabe erfolgreich in Aufgabe umgewandelt.', + 'Unable to convert the subtask.' => 'Teilaufgabe kann nicht umgewandelt werden.', + 'Unable to extract plugin archive.' => 'Plugin Archiv kann nicht entpackt werden.', + 'Plugin not found.' => 'Plugin nicht gefunden.', + 'You don\'t have the permission to remove this plugin.' => 'Du darfst dieses Plugin nicht entfernen.', + 'Unable to download plugin archive.' => 'Plugin Archiv kann nicht herunter geladen werden.', + 'Unable to write temporary file for plugin.' => 'Temporäre Dateien für das Plugin können nicht geschrieben werden.', + 'Unable to open plugin archive.' => 'Kann das Plugin Archiv nicht öffnen.', + 'There is no file in the plugin archive.' => 'Es gibt keine Datei im Plugin Archiv.', + 'Create tasks in bulk' => 'Viele Aufgaben auf einmal erstellen', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Deine Kanboard Installation ist nicht dafür konfiguriert, Plugins mit dem Benutzerinterface zu installieren.', + 'There is no plugin available.' => 'Es gibt kein Plugin.', + 'Install' => 'Installieren', + 'Update' => 'Aktualisieren', + 'Up to date' => 'Aktuell', + 'Not available' => 'Nicht verfügbar', + 'Remove plugin' => 'Plugin entfernen', + 'Do you really want to remove this plugin: "%s"?' => 'Willst du das Plugin "%s" wirklich entfernen?', + 'Uninstall' => 'Deinstallieren', + 'Listing' => 'Auflistung', + 'Metadata' => 'Metadaten', + 'Manage projects' => 'Projekte verwalten', + 'Convert to task' => 'In Aufgabe umwandeln', + 'Convert sub-task to task' => 'Teilaufgabe in Aufgabe umwandeln', + 'Do you really want to convert this sub-task to a task?' => 'Willst du diese Teilaufgabe wirklich in eine Aufgabe umwandeln?', + 'My task title' => 'Mein Aufgabentitel', + 'Enter one task by line.' => 'Gib eine Aufgabe pro Zeile ein.', + 'Number of failed login:' => 'Anzahl fehlgeschlagener Anmeldungen:', + 'Account locked until:' => 'Konto gesperrt bis:', + 'Email settings' => 'E-Mail Einstellungen', + 'Email sender address' => 'E-Mail Absender Adresse', + 'Email transport' => 'E-Mail Verkehr', + 'Webhook token' => 'Webhook Token', + 'Project tags management' => 'Projektbezogenes Schlagwort-Management', + 'Tag created successfully.' => 'Schlagwort erfolgreich erstellt.', + 'Unable to create this tag.' => 'Das Schlagwort kann nicht erstellt werden.', + 'Tag updated successfully.' => 'Schlagwort erfolgreich aktualisiert.', + 'Unable to update this tag.' => 'Das Schlagwort kann nicht aktualisiert werden.', + 'Tag removed successfully.' => 'Schlagwort erfolgreich entfernt.', + 'Unable to remove this tag.' => 'Das Schlagwort kann nicht entfernt werden.', + 'Global tags management' => 'Globales Schlagwort-Management', + 'Tags' => 'Schlagworte', + 'Tags management' => 'Schlagwort-Management', + 'Add new tag' => 'Neues Schlagwort hinzufügen', + 'Edit a tag' => 'Schlagwort bearbeiten', + 'Project tags' => 'Projektbezogene Schlagwörter', + 'There is no specific tag for this project at the moment.' => 'Es gibt zur Zeit kein spezifisches Schlagwort.', + 'Tag' => 'Schlagwort', + 'Remove a tag' => 'Schlagwort entfernen', + 'Do you really want to remove this tag: "%s"?' => 'Soll dieses Schlagwort wirklich entfernt werden: "%s"?', + 'Global tags' => 'Globale Schlagwörter', + 'There is no global tag at the moment.' => 'Es gibt zur Zeit kein globales Schlagwort', + 'This field cannot be empty' => 'Dieses Feld kann nicht leer sein', + 'Close a task when there is no activity in a specific column' => 'Aufgabe schließen wenn es keine Aktivität in einer bestimmten Spalte gibt', + '%s removed a subtask for the task #%d' => '%s hat eine Teilaufgabe der Aufgabe #%d entfernt', + '%s removed a comment on the task #%d' => '%s hat einen Kommentar der Aufgabe #%d entfernt', + 'Comment removed on task #%d' => 'Kommentar der Aufgabe #%d entfernt', + 'Subtask removed on task #%d' => 'Teilaufgabe der Aufgabe #%d entfernt', + 'Hide tasks in this column in the dashboard' => 'Aufgaben in dieser Spalte im Dashboard ausblenden', + '%s removed a comment on the task %s' => '%s hat einen Kommentar in der Aufgabe %s entfernt', + '%s removed a subtask for the task %s' => '%s hat eine Teilaufgabe in der Aufgabe %s entfernt', + 'Comment removed' => 'Kommentar entfernt', + 'Subtask removed' => 'Teilaufgabe entfernt', + '%s set a new internal link for the task #%d' => '%s hat eine neue interne Verbindung in der Aufgabe #%d erstellt', + '%s removed an internal link for the task #%d' => '%s hat eine interne Verbindung von der Aufgabe #%d entfernt', + 'A new internal link for the task #%d has been defined' => 'Eine neue interne Verbindung für die Aufgabe #%d wurde definiert', + 'Internal link removed for the task #%d' => 'Interne Verbindung in der Aufgabe #%d wurde entfernt', + '%s set a new internal link for the task %s' => '%s hat eine neue interne Verbindung in der Aufgabe %s erstellt', + '%s removed an internal link for the task %s' => '%s hat eine interne Verbindung von der Aufgabe %s entfernt', + 'Automatically set the due date on task creation' => 'Ablaufdatum automatisch bei Erstellung einer Aufgabe setzen', + 'Move the task to another column when closed' => 'Aufgabe in eine andere Spalte verschieben, wenn diese geschlossen wird', + 'Move the task to another column when not moved during a given period' => 'Aufgabe in eine andere Spalte verschieben, wenn diese in einer bestimmten Zeit nicht verschoben wurde', + 'Dashboard for %s' => 'Dashboard für %s', + 'Tasks overview for %s' => 'Aufgaben-Übersicht für %s', + 'Subtasks overview for %s' => 'Teilaufgaben-Übersicht für %s', + 'Projects overview for %s' => 'Projekt-Übersicht für %s', + 'Activity stream for %s' => 'Aktivitätenstrom für %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Einer Aufgabe eine Farbe zuweisen, wenn diese in eine bestimmte Swimlane verschoben wird', + 'Assign a priority when the task is moved to a specific swimlane' => 'Einer Aufgabe eine Priorität zuweisen, wenn diese in eine bestimmte Swimlane verschoben wird', + 'User unlocked successfully.' => 'Benutzer erfolgreich entsperrt.', + 'Unable to unlock the user.' => 'Benutzer kann nicht entsperrt werden.', + 'Move a task to another swimlane' => 'Aufgabe in eine andere Swimlane verschieben', + 'Creator Name' => 'Name des Erstellers', + 'Time spent and estimated' => 'Aufgewendete und erwartete Zeit', + 'Move position' => 'Position verschieben', + 'Move task to another position on the board' => 'Aufgabe an eine andere Position im Board verschieben', + 'Insert before this task' => 'Vor dieser Aufgabe einfügen', + 'Insert after this task' => 'Nach dieser Aufgabe einfügen', + 'Unlock this user' => 'Diesen Benutzer entsperren', + 'Custom Project Roles' => 'Benutzerdefinierte Projekt Rollen', + 'Add a new custom role' => 'Neue benutzerdefinierte Rolle erstellen', + 'Restrictions for the role "%s"' => 'Einschränkungen für Rolle "%s"', + 'Add a new project restriction' => 'Neue projektbezogene Einschränkung erstellen', + 'Add a new drag and drop restriction' => 'Neue Drag and Drop Einschränkung erstellen', + 'Add a new column restriction' => 'Neue spaltenbezogene Einschränkung erstellen', + 'Edit this role' => 'Diese Rolle bearbeiten', + 'Remove this role' => 'Diese Rolle entfernen', + 'There is no restriction for this role.' => 'Für diese Rolle gibt es keine Einschränkungen.', + 'Only moving task between those columns is permitted' => 'Verschieben von Aufgaben ist nur zwischen diesen Spalten erlaubt', + 'Close a task in a specific column when not moved during a given period' => 'Aufgabe in einer bestimmten Spalte schließen, wenn sie im angegebenen Zeitraum nicht verschoben wurde', + 'Edit columns' => 'Spalten bearbeiten', + 'The column restriction has been created successfully.' => 'Die Spalteneinschränkung wurde erfolgreich erstellt.', + 'Unable to create this column restriction.' => 'Erstellen der Spalteneinschränkung fehlgeschlagen.', + 'Column restriction removed successfully.' => ' Spalteneinschränkung erfolgreich entfernt.', + 'Unable to remove this restriction.' => 'Entfernen der Spalteneinschränkung fehlgeschlagen.', + 'Your custom project role has been created successfully.' => 'Benutzerdefinierte Projekt Rolle erfolgreich erstellt.', + 'Unable to create custom project role.' => 'Erstellen der benutzerdefinierten Projekt Rolle fehlgeschlagen.', + 'Your custom project role has been updated successfully.' => 'Benutzerdefinierte Projekt Rolle wurde erfolgreich geändert.', + 'Unable to update custom project role.' => 'Ändern der benutzerdefinierten Projekt Rolle fehlgeschlagen.', + 'Custom project role removed successfully.' => 'Benutzerdefinierte Projekt Rolle erfolgreich entfernt.', + 'Unable to remove this project role.' => 'Entfernen der benutzerdefinierten Projekt Rolle fehlgeschlagen.', + 'The project restriction has been created successfully.' => 'Projektbezogene Einschränkung erfolgreich erstellt.', + 'Unable to create this project restriction.' => 'Erstellen der projektbezogenen Einschränkung fehlgeschlagen.', + 'Project restriction removed successfully.' => 'Projektbezogene Einschränkung erfolgreich entfernt.', + 'You cannot create tasks in this column.' => 'Du kannst in dieser Spalte keine Aufgaben erzeugen.', + 'Task creation is permitted for this column' => 'Erzeugen von Aufgaben ist für diese Spalte erlaubt.', + 'Closing or opening a task is permitted for this column' => 'Öffnen und Schließen von Aufgaben ist für diese Spalte erlaubt.', + 'Task creation is blocked for this column' => 'Erzeugen von Aufgaben ist für diese Spalte blockiert.', + 'Closing or opening a task is blocked for this column' => 'Öffnen und Schließen von Aufgaben ist für diese Spalte blockiert.', + 'Task creation is not permitted' => 'Erzeugen von Aufgaben ist nicht erlaubt.', + 'Closing or opening a task is not permitted' => 'Öffnen und Schließen von Aufgaben ist nicht erlaubt.', + 'New drag and drop restriction for the role "%s"' => 'Neue drag and drop Einschränkung für Rolle "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Benutzer mit dieser Rolle können Aufgaben nur zwischen Quell- und Zielspalte verschieben.', + 'Remove a column restriction' => 'Spaltenbezogene Einschränkung entfernen', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Willst du diese Spalteneinschränkung wirklich löschen: "%s" nach "%s"?', + 'New column restriction for the role "%s"' => 'Neue spaltenbezogene Einschränkung für Rolle "%s"', + 'Rule' => 'Regel', + 'Do you really want to remove this column restriction?' => 'Willst du diese Spalteneinschränkung wirklich entfernen?', + 'Custom roles' => 'Benutzerdefinierte Rollen', + 'New custom project role' => 'Neue benutzerdefinierte Projekt Rolle', + 'Edit custom project role' => 'Benutzerdefinierte Projekt Rolle bearbeiten', + 'Remove a custom role' => 'Benutzerdefinierte Projekt Rolle entfernen', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Willst du diese benutzerdefinierte Rolle wirklich entfernen: "%s"? Alle Benutzer mit dieser Rolle werden zu Projekt-Mitgliedern.', + 'There is no custom role for this project.' => 'Für dieses Projekt gibt es keine benutzerdefinierten Rollen.', + 'New project restriction for the role "%s"' => 'Neue projektbezogene Einschränkung für Rolle "%s"', + 'Restriction' => 'Einschränkung', + 'Remove a project restriction' => 'Projektbezogene Einschränkung entfernen', + 'Do you really want to remove this project restriction: "%s"?' => 'Willst du diese projektbezogene Einschränkung wirklich entfernen: "%s"?', + 'Duplicate to multiple projects' => 'In mehrere Projekte duplizieren', + 'This field is required' => 'Dies ist ein Pflichtfeld', + 'Moving a task is not permitted' => 'Verschieben einer Aufgabe ist nicht erlaubt', + 'This value must be in the range %d to %d' => 'Dieser Wert muss im Bereich %d bis %d sein', + 'You are not allowed to move this task.' => 'Du hast nicht die Berechtigung, diese Aufgabe zu verschieben.', + 'API User Access' => 'API Benutzerzugriff', + 'Preview' => 'Vorschau', + 'Write' => 'Schreiben', + 'Write your text in Markdown' => 'Schreibe deinen Text in Markdown', + 'No personal API access token registered.' => 'Keine persönlichen API-Zugriffsinformationen registriert', + 'Your personal API access token is "%s"' => 'Deine persönlichen API-Zugriffsinformationen: "%s"', + 'Remove your token' => 'Deine Zugriffsinformationen entfernen', + 'Generate a new token' => 'Neue Zugriffsinformationen generieren', + 'Showing %d-%d of %d' => 'Zeige %d-%d von %d', + 'Outgoing Emails' => 'Ausgehende E-Mails', + 'Add or change currency rate' => 'Wechselkurs hinzufügen oder ändern', + 'Reference currency: %s' => 'Referenzwährung: %s', + 'Add custom filters' => 'Benutzerdefinierten Filter hinzufügen', + 'Export' => 'Exportieren', + 'Add link label' => 'Linkbeschreibung hinzufügen', + 'Incompatible Plugins' => 'Nicht-kompatible Plugins', + 'Compatibility' => 'Kompatibilität', + 'Permissions and ownership' => 'Berechtigungen und Besitz', + 'Priorities' => 'Prioritäten', + 'Close this window' => 'Dieses Fenster schließen', + 'Unable to upload this file.' => 'Diese Datei kann nicht hochgeladen werden', + 'Import tasks' => 'Aufgaben importieren', + 'Choose a project' => 'Wähle ein Projekt', + 'Profile' => 'Profil', + 'Application role' => 'Anwendungsrolle', + '%d invitations were sent.' => '%d Einladungen wurden gesendet.', + '%d invitation was sent.' => '%d Einladung wurde gesendet.', + 'Unable to create this user.' => 'Dieser Benutzer kann nicht erstellt werden.', + 'Kanboard Invitation' => 'Kanboard Einladung', + 'Visible on dashboard' => 'Sichtbar auf dem Dashboard', + 'Created at:' => 'Erstellt am:', + 'Updated at:' => 'Aktualisiert am:', + 'There is no custom filter.' => 'Es gibt keinen benutzerdefinierten Filter.', + 'New User' => 'Neuer Benutzer', + 'Authentication' => 'Authentifizierung', + 'If checked, this user will use a third-party system for authentication.' => 'Wenn aktiviert, verwendet dieser Benutzer ein Drittanbieter-System für die Authentifizierung.', + 'The password is necessary only for local users.' => 'Das Passwort ist nur für lokale Benutzer erforderlich.', + 'You have been invited to register on Kanboard.' => 'Du wurdest eingeladen, dich auf Kanboard zu registrieren.', + 'Click here to join your team' => 'Klicke hier, um deinem Team beizutreten', + 'Invite people' => 'Leute einladen', + 'Emails' => 'E-Mail', + 'Enter one email address by line.' => 'Gib eine E-Mail-Adresse pro Zeile ein.', + 'Add these people to this project' => 'Füge diese Personen diesem Projekt hinzu', + 'Add this person to this project' => 'Füge diese Person diesem Projekt hinzu', + 'Sign-up' => 'Anmelden', + 'Credentials' => 'Anmeldeinformationen', + 'New user' => 'Neuer Benutzer', + 'This username is already taken' => 'Dieser Benutzername ist bereits vergeben', + 'Your profile must have a valid email address.' => 'Dein Profil muss eine gültige E-Mail-Adresse haben.', + 'TRL - Turkish Lira' => 'TRL - Türkische Lira', + 'The project email is optional and could be used by several plugins.' => 'Die Projekt-E-Mail ist optional und kann von mehreren Plugins verwendet werden.', + 'The project email must be unique across all projects' => 'Die Projekt-E-Mail muss für alle Projekte eindeutig sein', + 'The email configuration has been disabled by the administrator.' => 'Die E-Mail-Konfiguration wurde vom Administrator deaktiviert.', + 'Close this project' => 'Dieses Projekt schließen', + 'Open this project' => 'Dieses Projekt öffnen', + 'Close a project' => 'Ein Projekt schließen', + 'Do you really want to close this project: "%s"?' => 'Möchtest du dieses Projekt wirklich schließen: "%s"?', + 'Reopen a project' => 'Ein Projekt wieder öffnen', + 'Do you really want to reopen this project: "%s"?' => 'Möchtest du dieses Projekt wirklich wieder öffnen: "%s"?', + 'This project is open' => 'Dieses Projekt ist offen', + 'This project is closed' => 'Dieses Projekt ist geschlossen', + 'Unable to upload files, check the permissions of your data folder.' => 'Dateien können nicht hochgeladen werden, überprüfe die Berechtigungen deines Datenordners.', + 'Another category with the same name exists in this project' => 'Eine weitere Kategorie mit demselben Namen existiert in diesem Projekt', + 'Comment sent by email successfully.' => 'Kommentar wurde erfolgreich per E-Mail gesendet.', + 'Sent by email to "%s" (%s)' => 'Wurde per E-Mail an [%s] gesendet "%s"', + 'Unable to read uploaded file.' => 'Die hochgeladene Datei konnte nicht gelesen werden.', + 'Database uploaded successfully.' => 'Die Datenbank wurde erfolgreich hochgeladen.', + 'Task sent by email successfully.' => 'Aufgabe wurde erfolgreich per E-Mail gesendet.', + 'There is no category in this project.' => 'Es gibt keine Kategorie in diesem Projekt', + 'Send by email' => 'Per E-Mail senden', + 'Create and send a comment by email' => 'Erstelle und sende einen Kommentar per E-Mail', + 'Subject' => 'Betreff', + 'Upload the database' => 'Datenbank hochladen', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Du kannst die zuvor heruntergeladene SQLite-Datenbank (Gzip-Format) hochladen.', + 'Database file' => 'Datenbankdatei', + 'Upload' => 'Hochladen', + 'Your project must have at least one active swimlane.' => 'Dein Projekt muss mindestens eine aktive Swimlane haben.', + 'Project: %s' => 'Projekt: %s', + 'Automatic action not found: "%s"' => 'Automatische Aktion nicht gefunden: "%s"', + '%d projects' => '%d Projekte', + '%d project' => '%d Projekt', + 'There is no project.' => 'Es gibt kein Projekt.', + 'Sort' => 'Sortieren', + 'Project ID' => 'Projekt-ID', + 'Project name' => 'Projekt Name', + 'Public' => 'Öffentlich', + 'Personal' => 'Persönlich', + '%d tasks' => '%d Aufgaben', + '%d task' => '%d Aufgabe', + 'Task ID' => 'Aufgaben-ID', + 'Assign automatically a color when due date is expired' => 'Automatisch eine Farbe zuweisen, wenn das Fälligkeitsdatum abgelaufen ist', + 'Total score in this column across all swimlanes' => 'Gesamtpunktzahl in dieser Spalte über alle Swimlanes', + 'HRK - Kuna' => 'HRK - Kroatische Kuna', + 'ARS - Argentine Peso' => 'ARS - Argentinische Peso', + 'COP - Colombian Peso' => 'COP - Kolumbianische Peso', + '%d groups' => '%d Gruppen', + '%d group' => '%d Gruppe', + 'Group ID' => 'Gruppen-ID', + 'External ID' => 'Externe ID', + '%d users' => '%d Benutzer', + '%d user' => '%d Benutzer', + 'Hide subtasks' => 'Teilaufgaben verstecken', + 'Show subtasks' => 'Teilaufgaben anzeigen', + 'Authentication Parameters' => 'Authentifizierungsparameter', + 'API Access' => 'API-Zugriff', + 'No users found.' => 'Keine Benutzer gefunden.', + 'User ID' => 'Benutzer-ID', + 'Notifications are activated' => 'Benachrichtigungen sind aktiviert', + 'Notifications are disabled' => 'Benachrichtigungen sind deaktiviert', + 'User disabled' => 'Benutzer deaktiviert', + '%d notifications' => '%d Benachrichtigungen', + '%d notification' => '%d Benachrichtigung', + 'There is no external integration installed.' => 'Es ist keine externe Integration installiert.', + 'You are not allowed to update tasks assigned to someone else.' => 'Du bist nicht berechtigt, Aufgaben zu aktualisieren, die jemand anderem zugewiesen wurden.', + 'You are not allowed to change the assignee.' => 'Du darfst den Zuständigen nicht ändern.', + 'Task suppression is not permitted' => 'Entfernen von Aufgaben ist nicht erlaubt.', + 'Changing assignee is not permitted' => 'Änderung des Zuständigen ist nicht zulässig', + 'Update only assigned tasks is permitted' => 'Nur zugeordnete Aufgaben dürfen aktualisiert werden', + 'Only for tasks assigned to the current user' => 'Nur für Aufgaben, die dem aktuellen Benutzer zugeordnet sind', + 'My projects' => 'Meine Projekte', + 'You are not a member of any project.' => 'Du bist nicht Mitglied eines Projektes.', + 'My subtasks' => 'Meine Teilaufgaben', + '%d subtasks' => '%d Teilaufgaben', + '%d subtask' => '%d Teilaufgabe', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Das Bewegen einer Aufgabe zwischen diesen Spalten ist nur für Aufgaben zulässig, die dem aktuellen Benutzer zugewiesen sind', + '[DUPLICATE]' => '[DUPLIKAT]', + 'DKK - Danish Krona' => 'DKK - Dänische Kronen', + 'Remove user from group' => 'Benutzer aus Gruppe löschen', + 'Assign the task to its creator' => 'Aufgabe dem Ersteller zuordnen', + 'This task was sent by email to "%s" with subject "%s".' => 'Diese Aufgabe wurde per Mail an "%s" mit dem Betreff "%s" gesendet.', + 'Predefined Email Subjects' => 'Vordefinierte E-Mail Betreffzeilen', + 'Write one subject by line.' => 'Schreibe ein Betreff pro Zeile.', + 'Create another link' => 'Einen weiteren Link erstellen', + 'BRL - Brazilian Real' => 'BRL - Brasilianische Real', + 'Add a new Kanboard task' => 'Eine neue Kanboard Aufgabe hinzufügen', + 'Subtask not started' => 'Teilaufgabe nicht gestartet', + 'Subtask currently in progress' => 'Teilaufgabe aktuell in Bearbeitung', + 'Subtask completed' => 'Teilaufgabe abgeschlossen', + 'Subtask added successfully.' => 'Teilaufgabe erfolgreich hinzugefügt.', + '%d subtasks added successfully.' => '%d Teilaufgaben erfolgreich hinzugefügt.', + 'Enter one subtask by line.' => 'Gib eine Teilaufgabe pro Zeile ein.', + 'Predefined Contents' => 'Vordefinierte Inhalte', + 'Predefined contents' => 'Vordefinierte Inhalte', + 'Predefined Task Description' => 'Vordefinierte Aufgabenbeschreibung', + 'Do you really want to remove this template? "%s"' => 'Willst du diese Vorlage wirklich löschen? "%s"', + 'Add predefined task description' => 'Vordefinierte Aufgabenbeschreibung hinzufügen', + 'Predefined Task Descriptions' => 'Vordefinierte Aufgabenbeschreibungen', + 'Template created successfully.' => 'Vorlage erfolgreich erstellt.', + 'Unable to create this template.' => 'Erstellen der Vorlage nicht möglich.', + 'Template updated successfully.' => 'Vorlage erfolgreich geändert.', + 'Unable to update this template.' => 'Aktualisierung der Vorlage nicht möglich.', + 'Template removed successfully.' => 'Vorlage erfolgreich gelöscht.', + 'Unable to remove this template.' => 'Löschen der Vorlage nicht möglich.', + 'Template for the task description' => 'Vorlage für die Aufgabenbeschreibung', + 'The start date is greater than the end date' => 'Das Startdatum ist größer als das Enddatum', + 'Tags must be separated by a comma' => 'Schlagworte müssen per Komma getrennt werden', + 'Only the task title is required' => 'Nur der Aufgaben-Titel wird benötigt', + 'Creator Username' => 'Ersteller Benutzername', + 'Color Name' => 'Farbname', + 'Column Name' => 'Spaltenname', + 'Swimlane Name' => 'Swimlane-Name', + 'Time Estimated' => 'geschätzte Zeit', + 'Time Spent' => 'Zeitaufwand', + 'External Link' => 'externer Link', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Diese Funktion aktiviert den iCal Feed, RSS Feed und die öffentliche Board-Ansicht', + 'Stop the timer of all subtasks when moving a task to another column' => 'Beenden des Timers für alle Unteraufgaben, wenn die Aufgabe in eine andere Spalte verschoben wird', + 'Subtask Title' => 'Titel der Teilaufgabe', + 'Add a subtask and activate the timer when moving a task to another column' => 'Teilaufgabe hinzufügen und den Timer aktivieren, wenn die Aufgabe in eine andere Spalte verschoben wird', + 'days' => 'Tage', + 'minutes' => 'Minuten', + 'seconds' => 'Sekunden', + 'Assign automatically a color when preset start date is reached' => 'Automatisch eine Farbe zuweisen, wenn das Startdatum erreicht ist', + 'Move the task to another column once a predefined start date is reached' => 'Verschieben des Tasks in eine andere Spalte, wenn das definierte Startdatum erreicht ist', + 'This task is now linked to the task %s with the relation "%s"' => 'Diese Aufgabe ist jetzt verknüpft mit der Aufgabe %s mit der Relation "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'Die Verknüpfung mit der Relation "%s" zur Aufgabe %s wurde entfernt', + 'Custom Filter:' => 'Benutzerdefinierter Filter:', + 'Unable to find this group.' => 'Diese Gruppe konnte nicht gefunden werden', + '%s moved the task #%d to the column "%s"' => '%s hat die Aufgabe #%d in die Spalte "%s" verschoben', + '%s moved the task #%d to the position %d in the column "%s"' => '%s hat die Aufgabe #%d auf die Position %d in der Spalte "%s" verschoben', + '%s moved the task #%d to the swimlane "%s"' => '%s hat die Aufgabe #%d in die Swimlane "%s" verschoben', + '%sh spent' => '%sh aufgewendet', + '%sh estimated' => '%sh angesetzt', + 'Select All' => 'Alle auswählen', + 'Unselect All' => 'Keine auswählen', + 'Apply action' => 'Aktion anwenden', + 'Move selected tasks to another column or swimlane' => 'Ausgewählte Aufgaben in andere Spalte verschieben', + 'Edit tasks in bulk' => 'Massenbearbeitung', + 'Choose the properties that you would like to change for the selected tasks.' => 'Wähle die Eigenschaften aus, die du für die ausgewählten Aufgaben ändern möchtest.', + 'Configure this project' => 'Projekteinstellungen', + 'Start now' => 'Jetzt starten', + '%s removed a file from the task #%d' => '%s hat eine Datei aus der Aufgabe #%d entfernt.', + 'Attachment removed from task #%d: %s' => 'Anhang aus Aufgabe #%d entfernt: %s', + 'No color' => 'Keine Farbe', + 'Attachment removed "%s"' => 'Anhang entfernt "%s"', + '%s removed a file from the task %s' => '%s hat eine Datei aus der Aufgabe entfernt %s', + 'Move the task to another swimlane when assigned to a user' => 'Verschieben der Aufgabe in eine andere Swimlane, wenn sie einem Benutzer zugewiesen wird', + 'Destination swimlane' => 'Ziel Swimlane', + 'Assign a category when the task is moved to a specific swimlane' => 'Kategorie zuweisen, wenn Aufgabe in eine bestimmte Swimlane verschoben wird', + 'Move the task to another swimlane when the category is changed' => 'Verschiebe die Aufgabe in eine andere Swimlane, wenn die Kategorie geändert wird', + 'Reorder this column by priority (ASC)' => 'Spalte nach Priorität ordnen (aufsteigend)', + 'Reorder this column by priority (DESC)' => 'Spalte nach Priorität ordnen (absteigend)', + 'Reorder this column by assignee and priority (ASC)' => 'Spalte nach Zuständigem und Priorität ordnen (aufsteigend)', + 'Reorder this column by assignee and priority (DESC)' => 'Spalte nach Zuständigem und Priorität ordnen (absteigend)', + 'Reorder this column by assignee (A-Z)' => 'Spalte nach Zuständigem ordnen (A-Z)', + 'Reorder this column by assignee (Z-A)' => 'Spalte nach Zuständigem ordnen (Z-A)', + 'Reorder this column by due date (ASC)' => 'Spalte nach Fälligkeitsdatum ordnen (aufsteigend)', + 'Reorder this column by due date (DESC)' => 'Spalte nach Fälligkeitsdatum ordnen (absteigend)', + 'Reorder this column by id (ASC)' => 'Ordne diese Spalte nach ID neu (aufsteigend)', + 'Reorder this column by id (DESC)' => 'Ordne diese Spalte nach ID neu (absteigend)', + '%s moved the task #%d "%s" to the project "%s"' => '%s hat die Aufgabe #%d "%s" in das Projekt "%s" verschoben', + 'Task #%d "%s" has been moved to the project "%s"' => 'Aufgabe #%d "%s" wurde in das Projekt "%s" verschoben', + 'Move the task to another column when the due date is less than a certain number of days' => 'Verschieben der Aufgabe in eine andere Spalte, wenn die Fälligkeit kleiner als eine bestimmte Anzahl von Tagen ist', + 'Automatically update the start date when the task is moved away from a specific column' => 'Aktualisiert automatisch das Startdatum, wenn die Aufgabe aus einer bestimmten Spalte genommen wird', + 'HTTP Client:' => 'HTTP-Client:', + 'Assigned' => 'Zugeordnet', + 'Task limits apply to each swimlane individually' => 'Aufgabenlimit gilt pro Swimlane', + 'Column task limits apply to each swimlane individually' => 'Spaltenaufgabenlimit für jede Swimlane einzeln anwenden', + 'Column task limits are applied to each swimlane individually' => 'Spaltenaufgabenlimit wird für jede Swimlane einzeln angewendet', + 'Column task limits are applied across swimlanes' => 'Spaltenaufgabenlimit wird swimlaneübergreifend angewendet', + 'Task limit: ' => 'Aufgabenlimit', + 'Change to global tag' => 'Zu globalem Schlagwort machen', + 'Do you really want to make the tag "%s" global?' => 'Das Schlagwort "%s" wirklich global machen?', + 'Enable global tags for this project' => 'Globale Schlagworte für dieses Projekt aktivieren', + 'Group membership(s):' => 'Gruppen-Mitgliedschaft(en):', + '%s is a member of the following group(s): %s' => '%s ist Mitglied in der/den folgenden Gruppe(n): %s', + '%d/%d group(s) shown' => '%d/%d Gruppe(n) angezeigt', + 'Subtask creation or modification' => 'Teilaufgabe erstellen oder ändern', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Aufgabe einem bestimmten Nutzer zuordnen, wenn die Aufgabe in eine bestimmte Swimlane verschoben wird', + 'Comment' => 'Kommentar', + 'Collapse vertically' => 'Vertikal zuklappen', + 'Expand vertically' => 'Vertikal aufklappen', + 'MXN - Mexican Peso' => 'MXN - Mexikanischer Peso', + 'Estimated vs actual time per column' => 'Geschätzte vs. tatsächliche Zeit pro Spalte', + 'HUF - Hungarian Forint' => 'HUF - Ungarischer Forint', + 'XBT - Bitcoin' => 'XBT - Bitcoin', + 'You must select a file to upload as your avatar!' => 'Du musst eine Datei auswählen, die als Avatar hochgeladen werden soll!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'Die hochgeladene Datei ist kein gültiges Bild! (Nur *.gif, *.jpg, *.jpeg and *.png sind erlaubt!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Automatisches Setzen des Fälligkeitsdatums, wenn die Aufgabe aus einer bestimmten Spalte heraus verschoben wird', + 'No other projects found.' => 'Keine weiteren Projekte vorhanden.', + 'Tasks copied successfully.' => 'Die Aufgaben wurden erfolgreich kopiert.', + 'Unable to copy tasks.' => 'Die Aufgaben konnten nicht kopiert werden.', + 'Theme' => 'Thema', + 'Theme:' => 'Thema:', + 'Light theme' => 'Helles Thema', + 'Dark theme' => 'Dunkles Thema', + 'Automatic theme - Sync with system' => 'Automatisches Thema - Mit System synchronisieren', + 'Application managers or more' => 'Anwendungsmanager oder mehr', + 'Administrators' => 'Administratoren', + 'Visibility:' => 'Sichtbarkeit:', + 'Standard users' => 'Standardbenutzer', + 'Visibility is required' => 'Sichtbarkeit ist erforderlich', + 'The visibility should be an app role' => 'Die Sichtbarkeit sollte eine App-Rolle sein', + 'Reply' => 'Antworten', + '%s wrote: ' => '%s schrieb:', + 'Number of visible tasks in this column and swimlane' => 'Anzahl der sichtbaren Aufgaben in dieser Spalte und Lane', + 'Number of tasks in this swimlane' => 'Anzahl der Aufgaben in dieser Lane', + 'Unable to find another subtask in progress, you can close this window.' => 'Es konnte kein weiterer Teilaufgabe in Bearbeitung gefunden werden, du kannst dieses Fenster schließen.', + 'This theme is invalid' => 'Dieses Thema ist ungültig', + 'This role is invalid' => 'Diese Rolle ist ungültig', + 'This timezone is invalid' => 'Diese Zeitzone ist ungültig', + 'This language is invalid' => 'Diese Sprache ist ungültig', + 'This URL is invalid' => 'Diese URL ist ungültig', + 'Date format invalid' => 'Ungültiges Datumsformat', + 'Time format invalid' => 'Ungültiges Zeitformat', + 'Invalid Mail transport' => 'Ungültiger Mail-Transport', + 'Color invalid' => 'Farbe ungültig', + 'This value must be greater or equal to %d' => 'Dieser Wert muss größer oder gleich %d sein', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Füge eine BOM am Anfang der Datei hinzu (für Microsoft Excel erforderlich)', + 'Just add these tag(s)' => 'Schlagwort(e) nur hinzufügen (bereits zugeordnete nicht löschen)', + 'Remove internal link(s)' => 'Interne Verbindung(en) entfernen', + 'Import tasks from another project' => 'Aufgaben aus einem anderen Projekt importieren', + 'Select the project to copy tasks from' => 'Wähle das Projekt aus, aus dem Aufgaben kopiert werden sollen', + 'The total maximum allowed attachments size is %sB.' => 'Die maximal zulässige Gesamtgröße für Anhänge beträgt %sB.', + 'Add attachments' => 'Anhänge hinzufügen', + 'Task #%d "%s" is overdue' => 'Aufgabe #%d "%s" ist überfällig', + 'Enable notifications by default for all new users' => 'Aktiviere Benachrichtigungen standardmäßig für alle neuen Benutzer', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Aufgabe dem Ersteller zuweisen, wenn sie in einer ausgewählten Spalte ohne Bearbeiter ist.', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Aufgabe dem angemeldeten Nutzer bei Spaltenänderung zuweisen, wenn eine Aufgabe in die ausgewählte Spalte verschoben wird.', +]; diff --git a/app/Locale/el_GR/translations.php b/app/Locale/el_GR/translations.php new file mode 100644 index 0000000..5b23996 --- /dev/null +++ b/app/Locale/el_GR/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => '.', + 'None' => 'Κανένα', + 'Edit' => 'Επεξεργασία', + 'Remove' => 'Αφαίρεση', + 'Yes' => 'Ναι', + 'No' => 'Όχι', + 'cancel' => 'ακύρωση', + 'or' => 'ή', + 'Yellow' => 'Κίτρινο', + 'Blue' => 'Μπλε', + 'Green' => 'Πράσινο', + 'Purple' => 'Βιολετί', + 'Red' => 'Κόκκινο', + 'Orange' => 'Πορτοκαλί', + 'Grey' => 'Γκρίζο', + 'Brown' => 'Καφέ', + 'Deep Orange' => 'Βαθύ πορτοκαλί', + 'Dark Grey' => 'Βαθύ γκρί', + 'Pink' => 'Ροζ', + 'Teal' => 'Τυρκουάζ', + 'Cyan' => 'Γαλάζιο', + 'Lime' => 'Λεμονί', + 'Light Green' => 'Ανοιχτό πράσινο', + 'Amber' => 'Κεχριμπαρί', + 'Save' => 'Αποθήκευση', + 'Login' => 'Είσοδος', + 'Official website:' => 'Επίσημη ιστοσελίδα:', + 'Unassigned' => 'Χωρίς ανάθεση', + 'View this task' => 'Προβολή της εργασίας', + 'Remove user' => 'Αφαίρεση χρήστη', + 'Do you really want to remove this user: "%s"?' => 'Είστε σίγουροι για την αφαίρεση του χρήστη: «%s»;', + 'All users' => 'Όλοι οι χρήστες', + 'Username' => 'Όνομα χρήστη', + 'Password' => 'Κωδικός πρόσβασης', + 'Administrator' => 'Διαχειριστής', + 'Sign in' => 'Είσοδος', + 'Users' => 'Χρήστες', + 'Forbidden' => 'Δεν επιτρέπεται η πρόσβαση', + 'Access Forbidden' => 'Δεν επιτρέπεται η πρόσβαση', + 'Edit user' => 'Επεξεργασία χρήστη', + 'Logout' => 'Αποσύνδεση', + 'Bad username or password' => 'Λάθος όνομα χρήστη ή κωδικός πρόσβασης', + 'Edit project' => 'Επεξεργασία έργου', + 'Name' => 'Όνομα', + 'Projects' => 'Έργα', + 'No project' => 'Δεν υπάρχουν μέλη για το έργο', + 'Project' => 'Έργο', + 'Status' => 'Κατάσταση', + 'Tasks' => 'Εργασίες', + 'Board' => 'Κεντρικό ταμπλό', + 'Actions' => 'Ενέργειες', + 'Inactive' => 'Ανενεργός', + 'Active' => 'Ενεργός', + 'Unable to update this board.' => 'Δεν ήταν δυνατή η ενημέρωση αυτού του πίνακα.', + 'Disable' => 'Απενεργοποίηση', + 'Enable' => 'Ενεργοποίηση', + 'New project' => 'Νέο έργο', + 'Do you really want to remove this project: "%s"?' => 'Είστε σίγουροι για την αφαίρεση του έργου: «%s»;', + 'Remove project' => 'Αφαίρεση του έργου', + 'Edit the board for "%s"' => 'Επεξεργασία του πίνακα για τον/την «%s»', + 'Add a new column' => 'Προσθήκη στήλης', + 'Title' => 'Τίτλος', + 'Assigned to %s' => 'Ανατεθειμένο στον/στην %s', + 'Remove a column' => 'Αφαίρεση στήλης', + 'Unable to remove this column.' => 'Δεν ήταν δυνατή η αφαίρεση της στήλης.', + 'Do you really want to remove this column: "%s"?' => 'Είστε σίγουροι για την αφαίρεση της στήλης: «%s»;', + 'Settings' => 'Προτιμήσεις', + 'Application settings' => 'Ρυθμίσεις εφαρμογής', + 'Language' => 'Γλώσσα', + 'Webhook token:' => 'Διακριτικό ασφαλείας (token) webhooks:', + 'API token:' => 'Διακριτικό ασφαλείας (token) API:', + 'Database size:' => 'Μέγεθος βάσης δεδομένων:', + 'Download the database' => 'Κατέβασμα της βάσης δεδομένων', + 'Optimize the database' => 'Βελτιστοποίηση της βάσης δεδομένων', + '(VACUUM command)' => '(Εντολή VACUUM)', + '(Gzip compressed Sqlite file)' => '(Συμπιεσμένο κατά Gzip αρχείο Sqlite)', + 'Close a task' => 'Κλείσιμο εργασίας', + 'Column' => 'Στήλη', + 'Color' => 'Χρώμα', + 'Assignee' => 'Ανατιθέμενος', + 'Create another task' => 'Δημιουργία άλλης εργασίας', + 'New task' => 'Νέα εργασία', + 'Open a task' => 'Άνοιγμα εργασίας', + 'Do you really want to open this task: "%s"?' => 'Είστε σίγουροι για το άνοιγμα της εργασίας: «%s»;', + 'Back to the board' => 'Επιστροφή στον κεντρικό πίνακα έργου', + 'There is nobody assigned' => 'Δεν έχει ανατεθεί σε κάποιον', + 'Column on the board:' => 'Στήλη στον κεντρικό πίνακα:', + 'Close this task' => 'Κλείσιμο εργασίας', + 'Open this task' => 'Άνοιγμα εργασίας', + 'There is no description.' => 'Δεν υπάρχει περιγραφή.', + 'Add a new task' => 'Προσθήκη νέας εργασίας', + 'The username is required' => 'Απαιτείται το όνομα χρήστη', + 'The maximum length is %d characters' => 'Ο μέγιστος αριθμός χαρακτήρων είναι %d χαρακτήρες', + 'The minimum length is %d characters' => 'Ο ελάχιστος αριθμός χαρακτήρων είναι %d χαρακτήρες', + 'The password is required' => 'Απαιτείται ο κωδικός πρόσβασης', + 'This value must be an integer' => 'Η τιμή πρέπει να είναι ακέραιος', + 'The username must be unique' => 'Το όνομα χρήστη πρέπει να είναι μοναδικό', + 'The user id is required' => 'Απαιτείται το αναγνωριστικό χρήστη', + 'Passwords don\'t match' => 'Οι κωδικοί πρόσβασης δεν ταιριάζουν', + 'The confirmation is required' => 'Απαιτείται η επιβεβαίωση', + 'The project is required' => 'Απαιτείται το έργο', + 'The id is required' => 'Απαιτείται το αναγνωριστικό', + 'The project id is required' => 'Απαιτείται το αναγνωριστικό έργου', + 'The project name is required' => 'Απαιτείται η ονομασία έργου', + 'The title is required' => 'Απαιτείται ο τίτλος', + 'Settings saved successfully.' => 'Οι προτιμήσεις αποθηκεύθηκαν με επιτυχία.', + 'Unable to save your settings.' => 'Δεν ήταν δυνατή ή αποθήκευση των προτιμήσεων.', + 'Database optimization done.' => 'Η βελτιστοποίηση της βάσης δεδομένων έγινε με επιτυχία.', + 'Your project has been created successfully.' => 'Το έργο δημιουργήθηκε.', + 'Unable to create your project.' => 'Δεν ήταν δυνατή η δημιουργία του έργου', + 'Project updated successfully.' => 'Το έργο ενημερώθηκε με επιτυχία.', + 'Unable to update this project.' => 'Δεν ήταν δυνατή η ενημέρωση του έργου.', + 'Unable to remove this project.' => 'Δεν ήταν δυνατή η διαγραφή του έργου', + 'Project removed successfully.' => 'Το έργο αφαιρέθηκε με επιτυχία.', + 'Project activated successfully.' => 'Το έργο ενεργοποιήθηκε με επιτυχία', + 'Unable to activate 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.' => 'Δεν ήταν δυνατή η ενημέρωση της εργασίας', + 'Task updated successfully.' => 'Η εργασία ενημερώθηκε με επιτυχία', + 'Unable to create your task.' => 'Δεν ήταν δυνατή η δημιουργία της εργασίας', + 'Task created successfully.' => 'Η εργασία δημιουργήθηκε με επιτυχία', + 'User created successfully.' => 'Ο χρήστης δημιουργήθηκε με επιτυχία', + 'Unable to create your user.' => 'Δεν ήταν δυνατή η δημιουργία χρήστη', + 'User updated successfully.' => 'Ο χρήστης ενημερώθηκε με επιτυχία', + 'User removed successfully.' => 'Ο χρήστης αφαιρέθηκε με επιτυχία.', + 'Unable to remove this user.' => 'Δεν ήταν δυνατή η αφαίρεση χρήστη.', + 'Board updated successfully.' => 'Ο πίνακας ενημερώθηκε με επιτυχία.', + 'Ready' => 'Έτοιμα', + 'Backlog' => 'Σε αναμονή', + 'Work in progress' => 'Εργασία σε εξέλιξη', + 'Done' => 'Ολοκληρωμένα', + 'Application version:' => 'Έκδοση εφαρμογής:', + 'Id' => 'Αναγνωριστικό', + 'Public link' => 'Δημόσιος σύνδεσμος', + 'Timezone' => 'Ώρα ζώνης', + 'Sorry, I didn\'t find this information in my database!' => 'Δυστυχώς δε βρέθηκε αυτή η πληροφορία στη βάση δεδομένων!', + 'Page not found' => 'Δε βρέθηκε η σελίδα', + 'Complexity' => 'Πολυπλοκότητα', + 'Task limit' => 'Όριο εργασιών', + 'Task count' => 'Αρίθμηση εργασιών', + 'User' => 'Χρήστης', + 'Comments' => 'Σχόλια', + 'Comment is required' => 'Απαιτείται το σχόλιο', + 'Comment added successfully.' => 'Το σχόλιο προστέθηκε με επιτυχία.', + 'Unable to create your comment.' => 'Δεν ήταν δυνατή η προσθήκη του σχολίου σας.', + 'Due Date' => 'Ημερομηνία προθεσμίας', + 'Invalid date' => 'Μη έγκυρη ημερομηνία', + 'Automatic actions' => 'Αυτόματες ενέργειες', + 'Your automatic action has been created successfully.' => 'Η αυτόματη ενέργειά σας δημιουργήθηκε με επιτυχία.', + 'Unable to create your automatic action.' => 'Δεν ήταν δυνατή η δημιουργία της αυτόματης ενέργειάς σας.', + 'Remove an action' => 'Αφαίρεση ενέργειας', + 'Unable to remove this action.' => 'Δεν ήταν δυνατή η αφαίρεση αυτής της ενέργειας.', + 'Action removed successfully.' => 'Η ενέργεια αφαιρέθηκε με επιτυχία.', + 'Automatic actions for the project "%s"' => 'Αυτόματες ενέργειες για το έργο «%s»', + 'Add an action' => 'Προσθήκη ενέργειας', + 'Event name' => 'Ονομασία συμβάντος', + 'Action' => 'Ενέργεια', + 'Event' => 'Συμβάν', + 'When the selected event occurs execute the corresponding action.' => 'Όταν εμφανίζεται το επιλεγμένο συμβάν να εκτελείται η αντίστοιχη ενέργεια.', + 'Next step' => 'Επόμενο βήμα', + 'Define action parameters' => 'Ορισμός παραμέτρων ενέργειας', + 'Do you really want to remove this action: "%s"?' => 'Είστε σίγουροι για την αφαίρεση της ενέργειας: «%s»;', + 'Remove an automatic action' => 'Αφαίρεση αυτόματης ενέργειας', + 'Assign the task to a specific user' => 'Ανάθεση της εργασίας σε συγκεκριμένο χρήστη', + 'Assign the task to the person who does the action' => 'Ανάθεση της εργασίας στο άτομο που εκτελεί την ενέργεια', + 'Duplicate the task to another project' => 'Αντιγραφή της εργασίας σε άλλο έργο', + 'Move a task to another column' => 'Μεταφορά εργασίας σε άλλη στήλη', + 'Task modification' => 'Τροποποίηση εργασίας', + 'Task creation' => 'Δημιουργία εργασίας', + 'Closing a task' => 'Κλείσιμο εργασίας', + 'Assign a color to a specific user' => 'Ανάθεση χρώματος σε συγκεκριμένο χρήστη', + 'Position' => 'Θέση', + 'Duplicate to project' => 'Αντιγραφή σε άλλο έργο', + 'Duplicate' => 'Αντιγραφή', + 'Link' => 'Σύνδεσμος', + 'Comment updated successfully.' => 'Το σχόλιο ενημερώθηκε με επιτυχία.', + 'Unable to update your comment.' => 'Δεν ήταν δυνατή η ενημέρωση του σχολίου.', + 'Remove a comment' => 'Διαγραφή σχολίου', + 'Comment removed successfully.' => 'Το σχόλιο διαγράφηκε με επιτυχία.', + 'Unable to remove this comment.' => 'Δεν ήταν δυνατή η διαγραφή του σχολίου.', + 'Do you really want to remove this comment?' => 'Είστε σίγουροι για την αφαίρεση του σχολίου;', + 'Current password for the user "%s"' => 'Ο τρέχων κωδικός πρόσβασης για τον χρήστη «%s»', + 'The current password is required' => 'Απαιτείται ο τρέχων κωδικός πρόσβασης', + 'Wrong password' => 'Λάθος κωδικός πρόσβασης', + 'Unknown' => 'Άγνωστο', + 'Last logins' => 'Τελευταίες συνδέσεις', + 'Login date' => 'Ημερομηνία σύνδεσης', + 'Authentication method' => 'Μέθοδος αυθεντικοποίησης', + 'IP address' => 'Διεύθυνση IP', + 'User agent' => 'Πρόγραμμα πελάτη', + 'Persistent connections' => 'Μόνιμες συνδέσεις', + 'No session.' => 'Καμία συνεδρία.', + 'Expiration date' => 'Ημερομηνία λήξης', + 'Remember Me' => 'Να με θυμάσαι', + 'Creation date' => 'Ημερομηνία δημιουργίας', + 'Everybody' => 'Όλοι', + 'Open' => 'Ανοικτά', + 'Closed' => 'Κλειστά', + 'Search' => 'Αναζήτηση', + 'Nothing found.' => 'Δεν βρέθηκε κάτι.', + 'Due date' => 'Ημερομηνία προθεσμίας', + 'Description' => 'Περιγραφή', + '%d comments' => '%d σχόλια', + '%d comment' => '%d σχόλιο', + 'Email address invalid' => 'Μη αποδεκτή διεύθυνση email', + 'Your external account is not linked anymore to your profile.' => 'Ο λογαριασμός σας δεν συνδέεται πλέον με το προφίλ σας.', + 'Unable to unlink your external account.' => 'Δεν ήταν δυνατή η αποσύνδεση του εξωτερικού σας λογαριασμού.', + 'External authentication failed' => 'Αποτυχία εξωτερικής αυθεντικοποίησης', + 'Your external account is linked to your profile successfully.' => 'Ο λογαριασμός σας συνδέθηκε με το προφίλ σας με επιτυχία.', + 'Email' => 'Email', + 'Task removed successfully.' => 'Η εργασία αφαιρέθηκε με επιτυχία.', + 'Unable to remove this task.' => 'Δεν ήταν δυνατή η αφαίρεση της εργασίας.', + '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' => 'Αυτόματη εκχώρηση μιας κατηγορίας με βάση το χρώμα', + 'Task creation or modification' => 'Δημιουργία ή τροποποίηση εργασίας', + 'Category' => 'Κατηγορία', + 'Category:' => 'Κατηγορία:', + 'Categories' => 'Κατηγορίες', + 'Your category has been created successfully.' => 'Η κατηγορία δημιουργήθηκε.', + 'This category has been updated successfully.' => 'Η ενημέρωση της κατηγορίας έγινε με επιτυχία.', + 'Unable to update this category.' => 'Δεν ήταν δυνατή η ενημέρωση της κατηγορίας.', + 'Remove a category' => 'Διαγραφή κατηγορίας', + 'Category removed successfully.' => 'Η κατηγορία διαγράφηκε με επιτυχία.', + 'Unable to remove this category.' => 'Δεν ήταν δυνατή η διαγραφή της κατηγορίας.', + 'Category modification for the project "%s"' => 'Τροποποίηση κατηγορίας για το έργο «%s»', + 'Category Name' => 'Ονομασία κατηγορίας', + 'Add a new category' => 'Προσθήκη νέας κατηγορίας', + 'Do you really want to remove this category: "%s"?' => 'Είστε σίγουροι για την διαγραφή της κατηγορίας: «%s»;', + 'All categories' => 'Όλες οι κατηγορίες', + 'No category' => 'Χωρίς κατηγορία', + 'The name is required' => 'Απαιτείται η ονομασία', + 'Remove a file' => 'Αφαίρεση αρχείου', + 'Unable to remove this file.' => 'Δεν ήταν δυνατή η αφαίρεση του αρχείου.', + 'File removed successfully.' => 'Το αρχείο αφαιρέθηκε με επιτυχία.', + 'Attach a document' => 'Προσθήκη εγγράφου', + 'Do you really want to remove this file: "%s"?' => 'Είστε σίγουροι για την αφαίρεση του αρχείου: «%s»;', + 'Attachments' => 'Συνημμένα', + 'Edit the task' => 'Επεξεργασία της εργασίας', + 'Add a comment' => 'Προσθήκη σχολίου', + 'Edit a comment' => 'Επεξεργασία σχολίου', + 'Summary' => 'Περίληψη', + 'Time tracking' => 'Παρακολούθηση χρόνου', + 'Estimate:' => 'Κατ\' εκτίμηση:', + 'Spent:' => 'Δαπανήθηκε:', + 'Do you really want to remove this sub-task?' => 'Είστε σίγουροι για την διαγραφή της υπο-εργασίας;', + 'Remaining:' => 'Απομένει:', + 'hours' => 'ώρες', + 'estimated' => 'κατ\' εκτίμηση', + 'Sub-Tasks' => 'Υπο-Εργασίες', + 'Add a sub-task' => 'Προσθήκη υπο-εργασίας', + 'Original estimate' => 'Αρχική πρόβλεψη χρόνου', + 'Create another sub-task' => 'Δημιουργία κι άλλης υπο-εργασίας', + 'Time spent' => 'Χρόνος που δαπανήθηκε', + 'Edit a sub-task' => 'Επεξεργασία υπο-εργασίας', + 'Remove a sub-task' => 'Διαγραφή υπο-εργασίας', + 'The time must be a numeric value' => 'Ο χρόνος πρέπει να είναι αριθμός', + 'Todo' => 'Πρέπει να γίνουν', + 'In progress' => 'Σε πρόοδο', + 'Sub-task removed successfully.' => 'Η υπο-εργασία αφαιρέθηκε με επιτυχία.', + 'Unable to remove this sub-task.' => 'Δεν ήταν δυνατή η αφαίρεση της υπο-εργασίας.', + 'Sub-task updated successfully.' => 'Η υπο-εργασία ενημερώθηκε με επιτυχία.', + 'Unable to update your sub-task.' => 'Αδύνατο να ενημερωθεί η υπο-εργασία.', + 'Unable to create your sub-task.' => 'Αδύνατο να δημιουργηθεί η υπο-εργασία.', + 'Maximum size: ' => 'Μέγιστο μέγεθος: ', + 'Display another project' => 'Εμφάνιση άλλου έργου', + 'Created by %s' => 'Δημιουργήθηκε από %s', + 'Tasks Export' => 'Εξαγωγή εργασιών', + 'Start Date' => 'Ημερομηνία έναρξης', + 'Execute' => 'Εκτέλεση', + 'Task Id' => 'Αναγνωριστικό εργασίας', + 'Creator' => 'Δημιουργός', + 'Modification date' => 'Ημερομηνία τροποποίησης', + 'Completion date' => 'Ημερομηνία ολοκλήρωσης', + 'Clone' => 'Κλωνοποίηση', + 'Project cloned successfully.' => 'Το έργο κλωνοποιήθηκε με επιτυχία.', + 'Unable to clone this project.' => 'Αδύνατο να κλωνοποιηθεί το έργο.', + 'Enable email notifications' => 'Ενεργοποίηση ειδοποιήσεων ηλεκτρονικού ταχυδρομείου', + 'Task position:' => 'Θέση έργου:', + 'The task #%d has been opened.' => 'Η εργασία #%d έχει ανοίξει.', + 'The task #%d has been closed.' => 'Η εργασία #%d έχει κλείσει.', + 'Sub-task updated' => 'Η υπο-εργασία ενημερώθηκε', + 'Title:' => 'Τίτλος:', + 'Status:' => 'Κατάσταση:', + 'Assignee:' => 'Ανατιθέμενος:', + 'Time tracking:' => 'Παρακολούθηση του χρόνου:', + 'New sub-task' => 'Νέα υπο-εργασία', + 'New attachment added "%s"' => 'Νέα επικόλληση προστέθηκε «%s»', + 'New comment posted by %s' => 'Νέο σχόλιο από τον χρήστη «%s»', + 'New comment' => 'Νέο σχόλιο', + 'Comment updated' => 'Το σχόλιο ενημερώθηκε', + 'New subtask' => 'Νέα υπο-εργασία', + 'I only want to receive notifications for these projects:' => 'Θέλω να ενημερώνομαι αποκλειστικά για τα κάτωθι έργα:', + 'view the task on Kanboard' => 'Προβολή της εργασίας στο Kanboard', + 'Public access' => 'Δημόσια πρόσβαση', + 'Disable public access' => 'Απενεργοποίηση δημόσιας πρόσβασης', + 'Enable public access' => 'Ενεργοποίηση δημόσιας πρόσβασης', + 'Public access disabled' => 'Η δημόσια πρόσβαση απενεργοποιήθηκε', + 'Move the task to another project' => 'Μεταφορά της εργασίας σε άλλο έργο', + 'Move to project' => 'Μεταφορά σε άλλο έργο', + 'Do you really want to duplicate this task?' => 'Είστε σίγουροι για την αντιγραφή της εργασίας;', + 'Duplicate a task' => 'Αντιγραφή εργασίας', + 'External accounts' => 'Εξωτερικοί λογαριασμοί', + 'Account type' => 'Τύπος λογαριασμού', + 'Local' => 'Τοπικός', + 'Remote' => 'Απομακρυσμένος', + 'Enabled' => 'Ενεργός', + 'Disabled' => 'Απενεργοποιημένος', + 'Login:' => 'Ονομα χρήστη:', + 'Full Name:' => 'Πλήρες όνομα:', + 'Email:' => 'Email:', + 'Notifications:' => 'Ειδοποιήσεις:', + 'Notifications' => 'Ειδοποιήσεις', + 'Account type:' => 'Τύπος λογαριασμού:', + 'Edit profile' => 'Επεξεργασία προφίλ', + 'Change password' => 'Αλλαγή κωδικού', + 'Password modification' => 'Τροποποίηση κωδικού', + 'External authentications' => 'Εξωτερικές αυθεντικοποιήσεις', + 'Never connected.' => 'Ποτέ δεν συνδέθηκε.', + 'No external authentication enabled.' => 'Καμία εξωτερική αυθεντικοποίηση ενεργοποιημένη.', + 'Password modified successfully.' => 'Ο κωδικός τροποποιήθηκε με επιτυχία.', + 'Unable to change the password.' => 'Δεν ήταν δυνατή η αλλαγή του κωδικού.', + 'Change category' => 'Αλλαγή κατηγορίας', + '%s updated the task %s' => 'Ο/Η %s ενημέρωσε την εργασία %s', + '%s opened the task %s' => 'Ο/Η %s άνοιξε την εργασία %s', + '%s moved the task %s to the position #%d in the column "%s"' => 'Ο/Η %s μετακίνησε την εργασία %s στη θέση #%d στη στήλη «%s»', + '%s moved the task %s to the column "%s"' => 'Ο/Η %s μετακίνησε την εργασία %s στη στήλη «%s»', + '%s created the task %s' => 'Ο/Η %s δημιούργησε την εργασία %s', + '%s closed the task %s' => 'Ο/Η %s έκλεισε την εργασία %s', + '%s created a subtask for the task %s' => 'Ο/Η %s δημιούργησε την υπο-εργασία στην εργασία %s', + '%s updated a subtask for the task %s' => 'Ο/Η %s ενημέρωση την υπο-εργασία στην εργασία %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Ανατέθηκε στον %s με μια εκτίμηση του %s/%sh', + 'Not assigned, estimate of %sh' => 'Δεν έχει ανατεθεί, εκτίμηση %sh', + '%s updated a comment on the task %s' => 'Ο/Η %s ενημέρωσε ένα σχόλιο στην εργασία %s', + '%s commented the task %s' => 'Ο/Η %s σχολίασε την εργασία %s', + '%s\'s activity' => 'δραστηριότητα του έργου %s', + 'RSS feed' => 'Ροή RSS', + '%s updated a comment 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', + '%s created the task #%d' => 'Ο/Η %s δημιούργησε την εργασία #%d', + '%s closed the task #%d' => 'Ο/Η %s έκλεισε την εργασία #%d', + '%s opened the task #%d' => 'Ο/Η %s άνοιξε την εργασία #%d', + 'Activity' => 'Δραστηριότητα', + 'Default values are "%s"' => 'Οι προεπιλεγμένες τιμές είναι «%s»', + 'Default columns for new projects (Comma-separated)' => 'Προεπιλεγμένες στήλες για νέα έργα (χωρισμένες με κόμμα)', + 'Task assignee change' => 'Αλλαγή ανατιθέμενου εργασίας', + '%s changed the assignee of the task #%d to %s' => 'Ο/Η %s άλλαξε τον ανατιθέμενο της εργασίας #%d σε %s', + '%s changed the assignee of the task %s to %s' => 'Ο/Η %s ενημέρωσε τον ανατιθέμενο της εργασίας %s σε %s', + 'New password for the user "%s"' => 'Νέος κωδικός πρόσβασης για τον χρήστη «%s»', + 'Choose an event' => 'Επιλογή συμβάντος', + 'Create a task from an external provider' => 'Δημιουργία εργασίας από ένα εξωτερικό πάροχο', + 'Change the assignee based on an external username' => 'Αλλαγή του ανατιθέμενου βάση ενός εξωτερικού ονόματος χρήστη', + 'Change the category based on an external label' => 'Αλλαγή του ανατιθέμενου βάση μιας εξωτερικής ετικέτας', + 'Reference' => 'Πηγή', + 'Label' => 'Ετικέτα', + 'Database' => 'Βάση δεδομένων', + 'About' => 'Σχετικά', + 'Database driver:' => 'Οδηγός βάσης δεδομένων:', + 'Board settings' => 'Ρυθμίσεις ταμπλό', + 'Webhook settings' => 'Ρυθμίσεις webhook', + 'Reset token' => 'Επαναφορά token', + 'API endpoint:' => 'Διεύθυνση URL API endpoint:', + 'Refresh interval for personal board' => 'Χρονικό διάστημα ανανέωσης του προσωπικού ταμπλό', + 'Refresh interval for public board' => 'Χρονικό διάστημα ανανέωσης του δημόσιου ταμπλό', + 'Task highlight period' => 'Χρονικό διάστημα επισήμανσης εργασίας', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Χρονικό διάστημα (σε δευτερόλεπτα) προκειμένου να διαπιστωθεί αν ένα έργο τροποποιήθηκε πρόσφατα (0 για απενεργοποίηση, 2 ημέρες από προεπιλογή)', + 'Frequency in second (60 seconds by default)' => 'Συχνότητα σε δευτερόλεπτα (60 δευτερόλεπτα από προεπιλογή)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Συχνότητα σε δευτερόλεπτα (0 για απενεργοποίηση της λειτουργίας, 10 δευτερόλεπτα από προεπιλογή)', + 'Application URL' => 'Διεύθυνση URL εφαρμογής', + 'Token regenerated.' => 'Το token δημιουργήθηκε εκ νέου.', + 'Date format' => 'Μορφή ημερομηνίας', + 'ISO format is always accepted, example: "%s" and "%s"' => 'Η μορφή ISO είναι πάντα αποδεκτή, πχ.: «%s» και «%s»', + 'New personal project' => 'Νέο ιδιωτικό έργο', + 'This project is personal' => 'Αυτό το έργο είναι ιδιωτικό', + 'Add' => 'Προσθήκη', + 'Start date' => 'Ημερομηνία έναρξης', + 'Time estimated' => 'Εκτιμώμενος χρόνος', + 'There is nothing assigned to you.' => 'Δεν έχετε κάτι ανατεθειμένο.', + 'My tasks' => 'Οι εργασίες μου', + 'Activity stream' => 'Ροή δραστηριότητας', + 'Dashboard' => 'Κεντρικό ταμπλό', + 'Confirmation' => 'Επιβεβαίωση', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Create a comment from an external provider' => 'Δημιουργία σχολίου από εξωτερικό πάροχο', + 'Project management' => 'Διαχείριση έργων', + 'Columns' => 'Στήλες', + 'Task' => 'Εργασία', + 'Percentage' => 'Ποσοστό', + 'Number of tasks' => 'Αριθμός εργασιών', + 'Task distribution' => 'Κατανομή εργασιών', + 'Analytics' => 'Αναλύσεις', + 'Subtask' => 'Υπο-εργασία', + 'User repartition' => 'Επαναλήψεις χρηστών', + 'Clone this project' => 'Κλωνοποίηση έργου', + 'Column removed successfully.' => 'Η στήλη αφαιρέθηκε με επιτυχία.', + 'Not enough data to show the graph.' => 'Δεν υπάρχουν δεδομένα για να εμφανιστεί το γράφημα.', + 'Previous' => 'Προηγούμενο', + 'The id must be an integer' => 'Το αναγνωριστικό πρέπει να είναι ακέραιος', + 'The project id must be an integer' => 'Το αναγνωριστικό έργου πρέπει να είναι ακέραιος', + 'The status must be an integer' => 'Η κατάσταση πρέπει να είναι ακέραιος', + 'The subtask id is required' => 'Το αναγνωριστικό της υπο-εργασίας είναι υποχρεωτικό', + 'The subtask id must be an integer' => 'Το αναγνωριστικό της υπο-εργασίας πρέπει να είναι ακέραιος', + 'The task id is required' => 'Το αναγνωριστικό της εργασίας είναι υποχρεωτικό', + 'The task id must be an integer' => 'Το αναγνωριστικό της εργασίας πρέπει να είναι ακέραιος', + 'The user id must be an integer' => 'Το αναγνωριστικό χρήστη πρέπει να είναι ακέραιος', + 'This value is required' => 'Η τιμή είναι υποχρεωτική', + 'This value must be numeric' => 'Η τιμή πρέπει να είναι αριθμός', + 'Unable to create this task.' => 'Δεν ήταν δυνατή η δημιουργία αυτής της εργασίας.', + 'Cumulative flow diagram' => 'Συγκεντρωτικό διάγραμμα ροής', + 'Daily project summary' => 'Καθημερινή περίληψη του έργου', + 'Daily project summary export' => 'Εξαγωγή της καθημερινής περίληψης του έργου', + 'Exports' => 'Εξαγωγές', + 'This export contains the number of tasks per column grouped per day.' => 'Αυτή η εξαγωγή περιέχει τον αριθμό των εργασιών ανά στήλη ομαδοποιημένες ανά ημέρα.', + 'Active swimlanes' => 'Ενεργές λωρίδες', + 'Add a new swimlane' => 'Προσθήκη λωρίδας', + 'Default swimlane' => 'Προκαθορισμένη λωρίδα', + 'Do you really want to remove this swimlane: "%s"?' => 'Είστε σίγουροι για την αφαίρεση της λωρίδας: «%s»;', + 'Inactive swimlanes' => 'Ανενεργές λωρίδες', + 'Remove a swimlane' => 'Αφαίρεση λωρίδας', + 'Swimlane modification for the project "%s"' => 'Τροποποίηση λωρίδας για το έργο «%s»', + 'Swimlane removed successfully.' => 'Η λωρίδα αφαιρέθηκε με επιτυχία.', + 'Swimlanes' => 'Λωρίδες', + 'Swimlane updated successfully.' => 'Η λωρίδα ενημερώθηκε με επιτυχία.', + 'Unable to remove this swimlane.' => 'Δεν ήταν δυνατή η αφαίρεση της λωρίδας.', + 'Unable to update this swimlane.' => 'Δεν ήταν δυνατή η ενημέρωση της λωρίδας.', + 'Your swimlane has been created successfully.' => 'Η λωρίδα σας δημιουργήθηκε με επιτυχία.', + 'Example: "Bug, Feature Request, Improvement"' => 'Παράδειγμα: «Σφάλμα, Αίτημα για νέο χαρακτηριστικό, Βελτίωση»', + 'Default categories for new projects (Comma-separated)' => 'Προκαθορισμένες κατηγορίες για νέα έργα (χωρισμένες με κόμμα)', + 'Integrations' => 'Ενσωματώσεις', + 'Integration with third-party services' => 'Ενσωμάτωση με υπηρεσίες τρίτων', + 'Subtask Id' => 'Αναγνωριστικό υπο-εργασίας', + 'Subtasks' => 'Υπο-Εργασίες', + 'Subtasks Export' => 'Εξαγωγή υπο-εργασιών', + 'Task Title' => 'Τίτλος εργασίας', + 'Untitled' => 'Χωρίς τίτλο', + 'Application default' => 'Προεπιλογή από την εφαρμογή', + 'Language:' => 'Γλώσσα:', + 'Timezone:' => 'Ώρα ζώνης:', + 'All columns' => 'Όλες οι στήλες', + 'Next' => 'Επόμενο', + '#%d' => '#%d', + 'All swimlanes' => 'Όλες οι λωρίδες', + 'All colors' => 'Όλα τα χρώματα', + 'Moved to column %s' => 'Μεταφέρθηκε στη στήλη %s', + 'User dashboard' => 'Κεντρικό ταμπλό χρήστη', + 'Allow only one subtask in progress at the same time for a user' => 'Να επιτρέπεται μόνο μία υπο-εργασία σε εξέλιξη ταυτόχρονα από ένα χρήστη', + 'Edit column "%s"' => 'Επεξεργασία στήλης «%s»', + '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?' => 'Ποιά τμήματα του έργου θέλετε να αντιγράψετε;', + 'Disallow login form' => 'Απαγόρευση φόρμας σύνδεσης', + 'Start' => 'Εκκίνηση', + 'End' => 'Τέλος', + 'Task age in days' => 'Χρόνος εργασίας σε ημέρες', + 'Days in this column' => 'Ημέρες σε αυτή την στήλη', + '%dd' => '%d ημ', + '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' => 'Τροποποίηση συνδέσμου', + 'Opposite label' => 'Αντίστροφη ετικέτα', + 'Remove a link' => 'Αφαίρεση συνδέσμου', + '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' => '<1 ωρ', + '%dh' => '%d ωρ', + 'Expand tasks' => 'Ανάπτυξη εργασιών', + 'Collapse tasks' => 'Σύμπτυξη εργασιών', + 'Expand/collapse tasks' => 'Ανάπτυξη/σύμπτυξη εργασιών', + 'Close dialog box' => 'Κλείσιμο του παραθύρου διαλόγου', + 'Submit a form' => 'Αποστολή φόρμας', + 'Board view' => 'Προβολή κεντρικού πίνακα', + 'Keyboard shortcuts' => 'Συντομεύσεις πληκτρολογίου', + 'Open board switcher' => 'Άνοιγμα μεταγωγέα κεντρικού πίνακα', + 'Application' => 'Εφαρμογή', + 'Compact view' => 'Συμπυκνωμένη προβολή', + 'Horizontal scrolling' => 'Οριζόντια ολίσθηση', + 'Compact/wide view' => 'Συμπυκνωμένη/Ευρεία Προβολή', + 'Currency' => 'Νόμισμα', + 'Personal project' => 'Ιδιωτικό έργο', + 'AUD - Australian Dollar' => 'AUD - Australian Dollar', + 'CAD - Canadian Dollar' => 'CAD - Canadian Dollar', + 'CHF - Swiss Francs' => 'CHF - Swiss Francs', + 'Custom Stylesheet' => 'Προσαρμοσμένο CSS stylesheet', + 'EUR - Euro' => 'EUR - Euro', + 'GBP - British Pound' => 'GBP - British Pound', + 'INR - Indian Rupee' => 'INR - Indian Rupee', + 'JPY - Japanese Yen' => 'JPY - Japanese Yen', + 'NZD - New Zealand Dollar' => 'NZD - New Zealand Dollar', + 'PEN - Peruvian Sol' => 'PEN - Περού Σολ', + 'RSD - Serbian dinar' => 'RSD - Serbian dinar', + 'CNY - Chinese Yuan' => 'CNY - Chinese Yuan', + 'USD - US Dollar' => 'USD - US Dollar', + 'VES - Venezuelan Bolívar' => 'VES - Venezuelan Bolívar', + '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' => 'Στήλη προέλευσης', + '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' => 'Αλλαγή ισοτιμίας', + 'Reference currency' => 'Αναφορά ισοτιμίας', + 'The currency rate has been added successfully.' => 'Η ισοτιμία προστέθηκε με επιτυχία.', + 'Unable to add this currency rate.' => 'Δεν ήταν δυνατή η προσθήκη της ισοτιμίας.', + 'Webhook URL' => 'Διεύθυνση webhook URL', + '%s removed the assignee of the task %s' => 'Ο/Η %s αφαίρεσε τον ανατιθέμενο της εργασίας %s', + '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' => 'Αυθεντικοποίηση δύο παραγόντων', + 'This QR code contains the key URI: ' => 'Το QR code περιέχει το URI: ', + 'Check my code' => 'Έλεγχος του κωδικού', + 'Secret key: ' => 'Μυστικό κλειδί: ', + 'Test your device' => 'Ελέγξτε τη συσκευή σας', + 'Assign a color when the task is moved to a specific column' => 'Εκχώρηση χρώματος όταν η εργασία κινείται σε μια συγκεκριμένη στήλη', + '%s via Kanboard' => '%s μέσω του Kanboard', + 'Burndown chart' => 'Δημιουργία διαγράμματος', + 'This chart show the task complexity over the time (Work Remaining).' => 'Αυτό το γράφημα δείχνει την πολυπλοκότητα του έργου κατά την πάροδο του χρόνου (Εργασία που απομένει).', + 'Screenshot taken %s' => 'Το στιγμιότυπο οθόνης αποθηκεύτηκε στις %s', + 'Add a screenshot' => 'Προσθήκη στιγμιότυπου οθόνης', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Λήψη του στιγμιότυπου οθόνης και πάτημα CTRL+V ή ⌘+V για επικόλληση εδώ.', + 'Screenshot uploaded successfully.' => 'Το στιγμιότυπο οθόνης ανέβηκε με επιτυχία.', + 'SEK - Swedish Krona' => 'SEK - Swedish Krona', + 'Identifier' => 'Αναγνωριστικό', + 'Disable two factor authentication' => 'Απενεργοποίηση αυθεντικοποίησης δύο παραγόντων', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Είστε σίγουροι για την απενεργοποίηση της αυθεντικοποίησης δύο παραγόντων για το χρήστη: «%s»;', + 'Edit link' => 'Επεξεργασία συνδέσμου', + 'Start to type task title...' => 'Ξεκινήστε να πληκτρολογείτε τον τίτλο της εργασίας...', + 'A task cannot be linked to itself' => 'Μια εργασία δεν μπορεί να συνδεθεί με τον εαυτό της', + 'The exact same link already exists' => 'Ο σύνδεσμος υπάρχει ήδη', + 'Recurrent task is scheduled to be generated' => 'Έχει προγραμματιστεί να δημιουργηθεί επαναλαμβανόμενη εργασία', + 'Score' => 'Σκορ', + 'The identifier must be unique' => 'Το αναγνωριστικό πρέπει να είναι μοναδικό', + 'This linked task id doesn\'t exists' => 'Αυτό το συνδεδεμένο αναγνωριστικό εργασίας δεν υπάρχει', + 'This value must be alphanumeric' => 'Η τιμή πρέπει να είναι αλφαριθμητική', + 'Edit recurrence' => 'Επεξεργασία επανάληψης', + 'Generate recurrent task' => 'Δημιουργία επαναλαμβανόμενης εργασίας', + 'Trigger to generate recurrent task' => 'Έναυσμα για τη δημιουργία επαναλαμβανόμενης εργασίας', + 'Factor to calculate new due date' => 'Συντελεστής για τον υπολογισμό νέας ημερομηνίας προθεσμίας', + 'Timeframe to calculate new due date' => 'Χρονικό πλαίσιο για τον υπολογισμό νέας ημερομηνίας προθεσμίας', + 'Base date to calculate new due date' => 'Ημερομηνία βάσης για τον υπολογισμό νέας ημερομηνίας προθεσμίας', + 'Action date' => 'Ημερομηνία ενέργειας', + 'Base date to calculate new due date: ' => 'Ημερομηνία βάσης για τον υπολογισμό νέας ημερομηνίας προθεσμίας: ', + 'This task has created this child task: ' => 'Αυτή η εργασία δημιούργησε αυτή την εργασία-παιδί: ', + 'Day(s)' => 'Ημέρα(ες)', + 'Existing due date' => 'Υπάρχουσα ημερομηνία προθεσμίας', + 'Factor to calculate new due date: ' => 'Συντελεστής για τον υπολογισμό νέας ημερομηνίας προθεσμίας: ', + 'Month(s)' => 'Μήνας(ες)', + 'This task has been created by: ' => 'Αυτή η εργασία δημιουργήθηκε από τον χρήστη: ', + 'Recurrent task has been generated:' => 'Παράχθηκε η επαναλαμβανόμενη εργασία:', + 'Timeframe to calculate new due date: ' => 'Χρονοδιάγραμμα υπολογισμού νέας ημερομηνίας προθεσμίας: ', + 'Trigger to generate recurrent task: ' => 'Έναυσμα για τη δημιουργία επαναλαμβανόμενης εργασίας: ', + 'When task is closed' => 'Όταν η εργασία έχει τελειώσει', + 'When task is moved from first column' => 'Όταν η εργασία μετακινηθεί στην πρώτη στήλη', + 'When task is moved to last column' => 'Όταν η εργασία μετακινηθεί στην τελευταία στήλη', + 'Year(s)' => 'Έτος(η)', + 'Project settings' => 'Ρυθμίσεις έργου', + 'Automatically update the start date' => 'Αυτόματη ενημέρωση της ημερομηνίας έναρξης', + 'iCal feed' => 'Ροή iCal', + 'Preferences' => 'Προτιμήσεις', + 'Security' => 'Ασφάλεια', + 'Two factor authentication disabled' => 'Ανενεργή αυθεντικοποίηση δύο παραγόντων', + 'Two factor authentication enabled' => 'Ενεργή αυθεντικοποίηση δύο παραγόντων', + 'Unable to update this user.' => 'Δεν ήταν δυνατή η ενημέρωση του χρήστη.', + 'There is no user management for personal projects.' => 'Δεν υπάρχει διαχείριση χρηστών για ιδιωτικά έργα.', + 'User that will receive the email' => 'Ο χρήστης που θα λάβει το μήνυμα email', + 'Email subject' => 'Θέμα email', + 'Date' => 'Ημερομηνία', + 'Add a comment log when moving the task between columns' => 'Προσθήκη σχολίου καταγραφής κατά την μετακίνηση της εργασίας μεταξύ των στηλών', + 'Move the task to another column when the category is changed' => 'Μετακίνηση της εργασίας σε άλλη στήλη, όταν η κατηγορία αλλάξει', + 'Send a task by email to someone' => 'Αποστολή εργασίας μέσω email σε κάποιον', + 'Reopen a task' => 'Ξανα-άνοιγμα εργασίας', + 'Notification' => 'Ειδοποίηση', + '%s moved the task #%d to the first swimlane' => 'Ο/Η %s μετέφερε την εργασία #%d στην πρώτη λωρίδα', + 'Swimlane' => 'Λωρίδα', + '%s moved the task %s to the first swimlane' => 'Ο/Η %s μετέφερε την εργασία %s στην πρώτη λωρίδα', + '%s moved the task %s to the swimlane "%s"' => 'Ο/Η %s μετέφερε την εργασία %s στη λωρίδα «%s»', + 'This report contains all subtasks information for the given date range.' => 'Η έκθεση αυτή περιέχει όλες τις υπο-εργασίες για το συγκεκριμένο εύρος ημερομηνιών.', + 'This report contains all tasks information for the given date range.' => 'Η έκθεση αυτή περιέχει όλες τις πληροφορίες για το συγκεκριμένο εύρος ημερομηνιών.', + 'Project activities for %s' => 'Δραστηριότητες έργου «%s»', + 'view the board on Kanboard' => 'προβολή του πίνακα στο Kanboard', + 'The task has been moved to the first swimlane' => 'Η εργασία αυτή έχει μετακινηθεί στην πρώτη λωρίδα', + 'The task has been moved to another swimlane:' => 'Η εργασία αυτή έχει μετακινηθεί σε άλλη λωρίδα:', + 'New title: %s' => 'Νέος τίτλος: %s', + 'The task is not assigned anymore' => 'Η εργασία δεν είναι πλέον ανατεθειμένη', + 'New assignee: %s' => 'Νέος ανατιθέμενος: %s', + 'There is no category now' => 'Δεν υπάρχει τώρα κατηγορία', + 'New category: %s' => 'Νέα κατηγορία: %s', + 'New color: %s' => 'Νέο χρώμα: %s', + 'New complexity: %d' => 'Νέα πολυπλοκότητα: %d', + 'The due date has been removed' => 'Η ημερομηνία προθεσμίας έχει αφαιρεθεί', + 'There is no description anymore' => 'Δεν υπάρχει πλέον περιγραφή', + 'Recurrence settings has been modified' => 'Οι ρυθμίσεις επανάληψης έχουν τροποποιηθεί', + 'Time spent changed: %sh' => 'Ο χρόνος που σπαταλήθηκε έχει αλλάξει: %sh', + 'Time estimated changed: %sh' => 'Ο εκτιμώμενος χρόνος άλλαξε: %sh', + 'The field "%s" has been updated' => 'Το πεδίο «%s» έχει ενημερωθεί', + 'The description has been modified:' => 'Η περιγραφή έχει ενημερωθεί:', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Είστε σίγουροι για το κλείσιμο της εργασίας «%s» καθώς και όλων των υπο-εργασιών της;', + 'I want to receive notifications for:' => 'Επιθυμώ να λαμβάνω ειδοποιήσεις για:', + 'All tasks' => 'Όλες τις εργασίες', + 'Only for tasks assigned to me' => 'Μόνο για εργασίες που μου έχουν ανατεθεί', + 'Only for tasks created by me' => 'Μόνο για εργασίες που έχουν δημιουργηθεί από εμένα', + 'Only for tasks created by me and tasks assigned to me' => 'Μόνο για εργασίες που έχουν δημιουργηθεί από εμένα και μου έχουν ανατεθεί', + '%%Y-%%m-%%d' => '%%d/%%m/%%Y', + 'Total for all columns' => 'Σύνολο για όλες τις στήλες', + 'You need at least 2 days of data to show the chart.' => 'Χρειάζονται τουλάχιστον δεδομένα δύο ημερών για να εμφανιστεί το γράφημα.', + '<15m' => '<15 λ', + '<30m' => '<30 λ', + 'Stop timer' => 'Διακοπή ρολογιού', + 'Start timer' => 'Έναρξη ρολογιού', + 'My activity stream' => 'Η ροή δραστηριοτήτων μου', + 'Search tasks' => 'Αναζήτηση εργασιών', + 'Reset filters' => 'Επαναφορά φίλτρων', + 'My tasks due tomorrow' => 'Οι εργασίες μου με αυριανή προθεσμία', + 'Tasks due today' => 'Εργασίες με σημερινή προθεσμία', + 'Tasks due tomorrow' => 'Εργασίες με αυριανή προθεσμία', + 'Tasks due yesterday' => 'Εργασίες με χθεσινή προθεσμία', + 'Closed tasks' => 'Κλειστές εργασίες', + 'Open tasks' => 'Ανοιχτές εργασίες', + 'Not assigned' => 'Χωρίς ανάθεση', + 'View advanced search syntax' => 'Δείτε τη σύνταξη αναζήτησης για προχωρημένους', + 'Overview' => 'Επισκόπηση', + 'Board/Calendar/List view' => 'Πίνακας/Ημερολόγιο/Προβολή λίστας', + 'Switch to the board view' => 'Εναλλαγή στην προβολή πίνακα', + 'Switch to the list view' => 'Εναλλαγή στην προβολή λίστας', + 'Go to the search/filter box' => 'Μετάβαση στο πλαίσιο αναζήτησης/φίλτρο', + 'There is no activity yet.' => 'Δεν υπάρχει ακόμη κάποια δραστηριότητα.', + 'No tasks found.' => 'Δεν βρέθηκαν εργασίες.', + 'Keyboard shortcut: "%s"' => 'Συντόμευση πληκτρολογίου: «%s»', + 'List' => 'Λίστα', + 'Filter' => 'Φίλτρο', + 'Advanced search' => 'Προχωρημένη Αναζήτηση', + 'Example of query: ' => 'Παράδειγμα ερωτήματος: ', + 'Search by project: ' => 'Αναζήτηση με βάση το έργο: ', + 'Search by column: ' => 'Αναζήτηση με βάση την στήλη: ', + 'Search by assignee: ' => 'Αναζήτηση με βάση τον ανατιθέμενο: ', + 'Search by color: ' => 'Αναζήτηση με βάση το χρώμα: ', + 'Search by category: ' => 'Αναζήτηση με βάση την κατηγορία: ', + 'Search by description: ' => 'Αναζήτηση με βάση την περιγραφή: ', + 'Search by due date: ' => 'Αναζήτηση με βάση την ημέρα προθεσμίας: ', + 'Average time spent in each column' => 'Μέσος χρόνος παραμονής σε κάθε στήλη', + 'Average time spent' => 'Μέσος χρόνος που δαπανήθηκε', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Αυτό το γράφημα δείχνει το μέσο χρόνο που δαπανάται σε κάθε στήλη για τις τελευταίες %d εργασίες', + 'Average Lead and Cycle time' => 'Μέσοι χρόνοι Lead & Cycle', + 'Average lead time: ' => 'Μέσος χρόνος lead: ', + 'Average cycle time: ' => 'Μέσος χρόνος cycle: ', + 'Cycle Time' => 'Χρόνος cycle', + 'Lead Time' => 'Χρόνος lead', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Αυτό το γράφημα δείχνει το average lead and cycle time για τις τελευταίες %d εργασίες κατά τη διάρκεια του χρόνου.', + 'Average time into each column' => 'Μέσος χρόνος σε κάθε στήλη', + 'Lead and cycle time' => 'Χρόνος lead και cycle', + 'Lead time: ' => 'Χρόνος lead: ', + 'Cycle time: ' => 'Χρόνος cycle: ', + 'Time spent in each column' => 'Ο χρόνος που δαπανήθηκε σε κάθε στήλη', + 'The lead time is the duration between the task creation and the completion.' => 'Το <lead time> είναι η διάρκεια μεταξύ της δημιουργίας του έργου και της ολοκλήρωσής του.', + 'The cycle time is the duration between the start date and the completion.' => 'Το <cycle time> είναι η διάρκεια μεταξύ της ημερομηνίας εκκίνησης και της ολοκλήρωσής του.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Εάν η εργασία δεν έχει κλείσει η τρέχουσα ώρα χρησιμοποιείται αντί της ημερομηνίας ολοκλήρωσης.', + 'Set the start date automatically' => 'Ρυθμίστε αυτόματα την ημερομηνία έναρξης', + 'Edit Authentication' => 'Επεξεργασία Αυθεντικοποίησης', + 'Remote user' => 'Απομακρυσμένος χρήστης', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Στους απομακρυσμένους χρήστες δεν αποθηκεύονται οι κωδικοί πρόσβασης εντός της βάσης δεδομένων της τρέχουσας εφαρμογής, Παραδείγματα: LDAP, Google και λογαριασμοί Github.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Αν ενεργοποιήσετε την επιλογή "Απαγόρευση φόρμας σύνδεσης", τα στοιχεία που εισάγονται στη φόρμα σύνδεσης αγνοούνται.', + 'Default task color' => 'Προκαθορισμένο χρώμα εργασίας', + 'This feature does not work with all browsers.' => 'Αυτή η δυνατότητα δεν λειτουργεί σε όλα τα προγράμματα πλοήγησης.', + 'There is no destination project available.' => 'Δεν υπάρχει διαθέσιμο κανένα έργο προορισμού.', + 'Trigger automatically subtask time tracking' => 'Αυτόματη ενεργοποίηση της παρακολούθησης χρόνου των υπο-εργασιών', + 'Include closed tasks in the cumulative flow diagram' => 'Να συμπεριλαμβάνονται οι κλειστές εργασίες στο σωρευτικό διάγραμμα ροής', + 'Current swimlane: %s' => 'Τρέχουσα λωρίδα: %s', + 'Current column: %s' => 'Τρέχουσα στήλη: %s', + 'Current category: %s' => 'Τρέχουσα κατηγορία: %s', + 'no category' => 'Καμία κατηγορία', + 'Current assignee: %s' => 'Τρέχων ανατιθέμενος: %s', + 'not assigned' => 'δεν έχει ανατεθεί', + 'Author:' => 'Συγγραφέας:', + 'contributors' => 'συνεισφέροντες', + 'License:' => 'Άδεια:', + 'License' => 'Άδεια', + 'Enter the text below' => 'Πληκτρολογήστε το κείμενο παρακάτω', + 'Start date:' => 'Ημερομηνία εκκίνησης:', + 'Due date:' => 'Ημερομηνία προθεσμίας:', + 'People who are project managers' => 'Οι άνθρωποι που είναι συντονιστές έργων', + 'People who are project members' => 'Οι άνθρωποι που είναι μέλη έργων', + 'NOK - Norwegian Krone' => 'NOK - Norwegian Krone', + 'Show this column' => 'Εμφάνιση αυτής της στήλης', + 'Hide this column' => 'Απόκρυψη αυτής της στήλης', + 'End date' => 'Ημερομηνία λήξης', + 'Users overview' => 'Επισκόπηση χρηστών', + 'Members' => 'Μέλη', + 'Shared project' => 'Κοινόχρηστο έργο', + 'Project managers' => 'Συντονιστές έργου', + 'Projects list' => 'Λίστα έργων', + 'End date:' => 'Ημερομηνία λήξης:', + 'Change task color when using a specific task link' => 'Αλλαγή χρώματος εργασίας χρησιμοποιώντας συγκεκριμένο σύνδεσμο εργασίας', + 'Task link creation or modification' => 'Σύνδεσμος δημιουργίας ή τροποποίησης εργασίας', + 'Milestone' => 'Ορόσημο', + 'Reset the search/filter box' => 'Αρχικοποίηση του πεδίου αναζήτησης/φιλτραρίσματος', + 'Documentation' => 'Τεκμηρίωση', + 'Author' => 'Δημιουργός', + 'Version' => 'Έκδοση', + 'Plugins' => 'Πρόσθετα', + 'There is no plugin loaded.' => 'Δεν έχει φορτωθεί πρόσθετο.', + 'My notifications' => 'Οι ειδοποιήσεις μου', + 'Custom filters' => 'Προσαρμοσμένα φίλτρα', + 'Your custom filter has been created successfully.' => 'Το προσαρμοσμένο φίλτρο δημιουργήθηκε με επιτυχία.', + 'Unable to create your custom filter.' => 'Δεν ήταν δυνατή η δημιουργία του προσαρμοσμένου φίλτρου.', + 'Custom filter removed successfully.' => 'Το προσαρμοσμένου φίλτρο αφαιρέθηκε με επιτυχία.', + 'Unable to remove this custom filter.' => 'Δεν ήταν δυνατή η αφαίρεση του προσαρμοσμένου φίλτρου.', + 'Edit custom filter' => 'Επεξεργασία προσαρμοσμένου φίλτρου', + 'Your custom filter has been updated successfully.' => 'Το προσαρμοσμένο φίλτρο ενημερώθηκε με επιτυχία.', + 'Unable to update custom filter.' => 'Δεν ήταν δυνατή η ενημέρωση του προσαρμοσμένου φίλτρου.', + 'Web' => 'Ιστός', + 'New attachment on task #%d: %s' => 'Νέο συνημμένο για την εργασία #%d: %s', + 'New comment on task #%d' => 'Νέο σχόλιο για την εργασία #%d', + 'Comment updated on task #%d' => 'Ενημέρωση σχολίου για την εργασία #%d', + 'New subtask on task #%d' => 'Νέα υπο-εργασία για την εργασία #%d', + 'Subtask updated on task #%d' => 'Ενημέρωση υπό-εργασίας για την εργασία #%d', + 'New task #%d: %s' => 'Νέα εργασία #%d: %s', + 'Task updated #%d' => 'Η εργασία #%d ενημερώθηκε με επιτυχία', + 'Task #%d closed' => 'Η εργασία #%d έκλεισε', + 'Task #%d opened' => 'Η εργασία #%d άνοιξε', + 'Column changed for task #%d' => 'Η στήλη άλλαξε για την εργασία #%d', + 'New position for task #%d' => 'Νέα θέση για την εργασία #%d', + 'Swimlane changed for task #%d' => 'Η λωρίδα άλλαξε για την εργασία #%d', + 'Assignee changed on task #%d' => 'Η ανάθεση άλλαξε για την εργασία #%d', + '%d overdue tasks' => '%d εκπρόθεσμες εργασίες', + 'No notification.' => 'Χωρίς νέες ειδοποιήσεις.', + 'Mark all as read' => 'Μαρκάρισμα όλων ως διαβασμένα', + 'Mark as read' => 'Μαρκάρισμα ως διαβασμένο', + 'Total number of tasks in this column across all swimlanes' => 'Συνολικός αριθμός εργασιών σε αυτήν τη στήλη σε όλες τις λωρίδες', + 'Collapse swimlane' => 'Συρρίκνωση λωρίδας', + 'Expand swimlane' => 'Ανάπτυξη λωρίδας', + 'Add a new filter' => 'Προσθήκη νέου φίλτρου', + 'Share with all project members' => 'Διαμοίραση με όλα τα μέλη του έργου', + 'Shared' => 'Διαμοιρασμένα', + 'Owner' => 'Ιδιοκτήτης', + 'Unread notifications' => 'Μη αναγνωσμένες ειδοποιήσεις', + 'Notification methods:' => 'Μέθοδοι ειδοποίησης:', + 'Unable to read your file' => 'Δεν ήταν δυνατή η ανάγνωση του αρχείου', + '%d task(s) have been imported successfully.' => '%d η(οι) εργασία(ες) εισήχθησαν με επιτυχία.', + 'Nothing has been imported!' => 'Τίποτα δεν εισήχθη!', + 'Import users from CSV file' => 'Εισαγωγή χρηστών μέσω αρχείου CSV', + '%d user(s) have been imported successfully.' => '%d ο(οι) χρήστης(ες) εισήχθησαν με επιτυχία.', + 'Comma' => 'Κόμμα', + 'Semi-colon' => 'Ερωτηματικό', + 'Tab' => 'Στηλοθέτης', + 'Vertical bar' => 'Κατακόρυφη μπάρα', + 'Double Quote' => 'Διπλά εισαγωγικά', + 'Single Quote' => 'Μονά εισαγωγικά', + '%s attached a file to the task #%d' => 'Ο/Η %s επισύναψε ένα αρχείο στην εργασία #%d', + 'There is no column or swimlane activated in your project!' => 'Δεν υπάρχει στήλη ή λωρίδα ενεργοποιημένη στο έργο σας!', + 'Append filter (instead of replacement)' => 'Προσθήκη στο φίλτρο (αντί για αντικατάσταση)', + 'Append/Replace' => 'Προσθήκη/Αντικατάσταση', + 'Append' => 'Προσθήκη', + 'Replace' => 'Αντικατάσταση', + 'Import' => 'Εισαγωγή', + 'Change sorting' => 'Αλλαγή ταξινόμησης', + 'Tasks Importation' => 'Εισαγωγή εργασιών', + 'Delimiter' => 'Διαχωριστής', + 'Enclosure' => 'Enclosure', + 'CSV File' => 'Αρχείο CSV', + 'Instructions' => 'Οδηγίες', + 'Your file must use the predefined CSV format' => 'Το αρχείο σας πρέπει να χρησιμοποιεί την προκαθορισμένη μορφοποίηση CSV', + 'Your file must be encoded in UTF-8' => 'Το αρχείο σας πρέπει να έχει κωδικοποίηση χαρακτήρων UTF-8', + 'The first row must be the header' => 'Η πρώτη γραμμή πρέπει να είναι η κεφαλίδα', + 'Duplicates are not verified for you' => 'Δεν γίνεται έλεγχος των διπλοεγγραφών', + 'The due date must use the ISO format: YYYY-MM-DD' => 'Η ημερομηνία προθεσμίας πρέπει να χρησιμοποιεί τη μορφοποίηση ISO: EEEE-MM-HH ή στα αγγλικά YYYY-MM-DD', + 'Download CSV template' => 'Κατέβασμα πρότυπου αρχείου CSV', + 'No external integration registered.' => 'Δεν υπάρχει καταγεγραμμένη εξωτερική ενσωμάτωση.', + 'Duplicates are not imported' => 'Δεν εισήχθησαν διπλοεγγραφές', + 'Usernames must be lowercase and unique' => 'Οι ονομασίες χρηστών πρέπει να είναι σε μικρά γράμματα (lowercase) και μοναδικά', + 'Passwords will be encrypted if present' => 'Οι κωδικοί πρόσβασης κρυπτογραφούνται, αν υπάρχουν', + '%s attached a new file to the task %s' => 'Ο/Η %s επισύναψε νέο αρχείο στην εργασία %s', + 'Link type' => 'Τύπος συνδέσμου', + 'Assign automatically a category based on a link' => 'Αυτόματη εκχώρηση κατηγορίας, βασισμένη σε σύνδεσμο', + 'BAM - Konvertible Mark' => 'BAM - Konvertible Mark', + 'Assignee Username' => 'Όνομα χρήστη ανατιθέμενου', + 'Assignee Name' => 'Όνομα ανατιθέμενου', + 'Groups' => 'Ομάδες', + 'Members of %s' => 'Μέλη της %s', + 'New group' => 'Νέα ομάδα', + 'Group created successfully.' => 'Η ομάδα δημιουργήθηκε με επιτυχία.', + 'Unable to create your group.' => 'Δεν ήταν δυνατή η δημιουργία της ομάδας.', + 'Edit group' => 'Επεξεργασία ομάδας', + 'Group updated successfully.' => 'Η ομάδα ενημερώθηκε με επιτυχία.', + 'Unable to update your group.' => 'Δεν ήταν δυνατή η ενημέρωση της ομάδας.', + 'Add group member to "%s"' => 'Προσθήκη του μέλους στην ομάδα «%s»', + 'Group member added successfully.' => 'Το μέλος προστέθηκε στην ομάδα με επιτυχία.', + 'Unable to add group member.' => 'Δεν ήταν δυνατή η προσθήκη μέλους στην ομάδα.', + 'Remove user from group "%s"' => 'Αφαίρεση μέλους από την ομάδα «%s»', + 'User removed successfully from this group.' => 'Ο χρήστης αφαιρέθηκε με επιτυχία από την ομάδα.', + 'Unable to remove this user from the group.' => 'Δεν ήταν δυνατή η αφαίρεση του χρήστη από την ομάδα.', + 'Remove group' => 'Αφαίρεση ομάδας', + 'Group removed successfully.' => 'Η ομάδα αφαιρέθηκε με επιτυχία.', + 'Unable to remove this group.' => 'Δεν ήταν δυνατή η αφαίρεση της ομάδας.', + 'Project Permissions' => 'Δικαιώματα έργου', + 'Manager' => 'Συντονιστής', + 'Project Manager' => 'Συντονιστής έργου', + 'Project Member' => 'Μέλος έργου', + 'Project Viewer' => 'Μέλος έργου μόνο για προβολή', + 'Your account is locked for %d minutes' => 'Ο λογαριασμός σας κλειδώθηκε για %d λεπτά', + 'Invalid captcha' => 'Μη αποδεκτός κωδικός captcha', + 'The name must be unique' => 'Το όνομα πρέπει να είναι μοναδικό', + 'View all groups' => 'Προβολή όλων των ομάδων', + 'There is no user available.' => 'Δεν υπάρχει διαθέσιμος χρήστης.', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Είστε σίγουροι για την αφαίρεση του χρήστη «%s» από την ομάδα «%s»;', + 'There is no group.' => 'Δεν υπάρχει ομάδα.', + 'Add group member' => 'Προσθήκη μέλους ομάδας', + 'Do you really want to remove this group: "%s"?' => 'Είστε σίγουροι για την αφαίρεση της ομάδας: «%s»;', + 'There is no user in this group.' => 'Δεν υπάρχει χρήστης σε αυτήν την ομάδα.', + 'Permissions' => 'Δικαιώματα', + 'Allowed Users' => 'Χρήστες που επιτρέπονται', + 'No specific user has been allowed.' => 'Δεν υπάρχει συγκεκριμένος χρήστης που να επιτρέπεται.', + 'Role' => 'Ρόλος', + 'Enter user name...' => 'Εισαγωγή ονόματος χρήστη...', + 'Allowed Groups' => 'Ομάδες που επιτρέπονται', + 'No group has been allowed.' => 'Δεν υπάρχει συγκεκριμένη ομάδα που να επιτρέπεται.', + 'Group' => 'Ομάδα', + 'Group Name' => 'Ονομασία ομάδας', + 'Enter group name...' => 'Εισαγωγή ονομασίας ομάδας...', + 'Role:' => 'Ρόλος:', + 'Project members' => 'Μέλη έργου', + '%s mentioned you in the task #%d' => 'Ο/Η %s σας ανέφερε στην εργασία #%d', + '%s mentioned you in a comment on the task #%d' => 'Ο/Η %s σας ανέφερε σε σχόλιο στην εργασία #%d', + 'You were mentioned in the task #%d' => 'Σας ανέφεραν στην εργασία #%d', + 'You were mentioned in a comment on the task #%d' => 'Σας ανέφεραν σε σχόλιο στην εργασία #%d', + 'Estimated hours: ' => 'Προβλεπόμενες ώρες: ', + 'Actual hours: ' => 'Πραγματικές ώρες: ', + 'Hours Spent' => 'Δαπανόμενες ώρες', + 'Hours Estimated' => 'Προβλεπόμενες ώρες', + 'Estimated Time' => 'Προβλεπόμενος χρόνος', + 'Actual Time' => 'Πραγματικός χρόνος', + 'Estimated vs actual time' => 'Προβλεπόμενος vs πραγματικός χρόνος', + 'RUB - Russian Ruble' => 'RUB - Russian Ruble', + 'Assign the task to the person who does the action when the column is changed' => 'Ανάθεση της εργασίας στο άτομο που εκτελεί την ενέργεια όταν η στήλη αλλάζει', + 'Close a task in a specific column' => 'Κλείσιμο εργασίας σε συγκεκριμένη στήλη', + 'Time-based One-time Password Algorithm' => 'Αλγόριθμος Κωδικού Πρόσβασης Μιας Χρήσης Βάσει Χρόνου', + 'Two-Factor Provider: ' => 'Πάροχος Αυθεντικοποίησης Δύο Παραγόντων: ', + 'Disable two-factor authentication' => 'Απενεργοποίηση αυθεντικοποίησης δύο παραγόντων', + 'Enable two-factor authentication' => 'Ενεργοποίηση αυθεντικοποίησης δύο παραγόντων', + 'There is no integration registered at the moment.' => 'Δεν υπάρχει αυτή τη στιγμή καταχωρημένη ενσωμάτωση.', + 'Password Reset for Kanboard' => 'Αρχικοποίηση κωδικών πρόσβασης για την εφαρμογή Kanboard', + 'Forgot password?' => 'Ξεχάσατε τον κωδικό πρόσβασης;', + 'Enable "Forget Password"' => 'Ενεργοποίηση του «Ξέχασα τον κωδικό πρόσβασης»', + 'Password Reset' => 'Αρχικοποίηση κωδικού πρόσβασης', + 'New password' => 'Νέος κωδικός πρόσβασης', + 'Change Password' => 'Αλλαγή κωδικού πρόσβασης', + 'To reset your password click on this link:' => 'Για να αρχικοποιηθεί ο κωδικός σας πρόσβασης πατήστε σε αυτόν τον σύνδεσμο:', + 'Last Password Reset' => 'Αρχικοποίηση τελευταίου κωδικού πρόσβασης', + 'The password has never been reinitialized.' => 'Ο κωδικός πρόσβασης δεν μπορεί να αρχικοποιηθεί για δεύτερη φορά.', + 'Creation' => 'Δημιουργία', + 'Expiration' => 'Λήξη', + 'Password reset history' => 'Ιστορικό αρχικοποίησης κωδικών πρόσβασης', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Όλες οι εργασίας της στήλης «%s» και η λωρίδα «%s» έκλεισαν με επιτυχία.', + 'Do you really want to close all tasks of this column?' => 'Είστε σίγουροι για το κλείσιμο όλων των εργασιών αυτής της στήλης;', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d εργασία(ες) στη στήλη «%s» και στη λωρίδα «%s» θα κλείσουν.', + 'Close all tasks in this column and this swimlane' => 'Κλείσιμο όλων των εργασιών αυτής της στήλης', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Δεν έχει καταχωρηθεί κάποιο πρόσθετο για τη μέθοδο ειδοποιήσεων του έργου. Μπορούν και τώρα να παραμετροποιηθούν ξεχωριστές ειδοποιήσεις στο προφίλ χρήστη.', + 'My dashboard' => 'Το κεντρικό ταμπλό μου', + 'My profile' => 'Το προφίλ μου', + 'Project owner: ' => 'Ιδιοκτήτης έργου: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'Το αναγνωριστικό έργου είναι προαιρετικό και πρέπει να είναι αλφαριθμητικό, για παράδειγμα: MYPROJECT', + 'Project owner' => 'Ιδιοκτήτης έργου', + 'Personal projects do not have users and groups management.' => 'Τα ιδιωτικά έργα δεν έχουν χρήστες και διαχείριση ομάδων', + 'There is no project member.' => 'Δεν υπάρχει μέλος στο έργο', + 'Priority' => 'Προτεραιότητα', + 'Task priority' => 'Προτεραιότητα εργασίας', + 'General' => 'Γενικά', + 'Dates' => 'Ημερομηνίες', + 'Default priority' => 'Εξ ορισμού προτεραιότητα', + 'Lowest priority' => 'Η χαμηλότερη προτεραιότητα', + 'Highest priority' => 'Η υψηλότερη προτεραιότητα', + 'Close a task when there is no activity' => 'Κλείσιμο εργασίας όταν δεν υπάρχει δραστηριότητα', + 'Duration in days' => 'Διάρκεια σε ημέρες', + 'Send email when there is no activity on a task' => 'Αποστολή email όταν δεν υπάρχει δραστηριότητα σε εργασία', + 'Unable to fetch link information.' => 'Δεν ήταν δυνατή η ανάλυση της πληροφορίας συνδεσμου', + 'Daily background job for tasks' => 'Ημερήσια παρασκηνιακή δουλειά για τις εργασίες', + 'Auto' => 'Αυτόματο', + 'Related' => 'Σχετίζεται', + 'Attachment' => 'Συνημμένο', + 'Web Link' => 'Σύνδεσμος web', + 'External links' => 'Εξωτερικοί σύνδεσμοι', + 'Add external link' => 'Προσθήκη εξωτερικού συνδέσμου', + 'Type' => 'Τύπος', + 'Dependency' => 'Εξάρτηση', + 'Add internal link' => 'Προσθήκη εσωτερικού συνδέσμου', + 'Add a new external link' => 'Προσθήκη νέου εξωτερικού συνδέσμου', + 'Edit external link' => 'Επεξεργασία εξωτερικού συνδέσμου', + 'External link' => 'Εξωτερικός σύνδεσμος', + 'Copy and paste your link here...' => 'Κάντε αντιγραφή και επικόλληση εδώ', + 'URL' => 'URL', + 'Internal links' => 'Εσωτερικοί σύνδεσμοι', + 'Assign to me' => 'Ανάθεση σε εμένα', + 'Me' => 'Σε εμένα', + 'Do not duplicate anything' => 'Να μην γίνει κλωνοποίηση από άλλο έργο', + 'Projects management' => 'Διαχείριση έργων', + 'Users management' => 'Διαχείριση χρηστών', + 'Groups management' => 'Διαχείριση ομάδων', + 'Create from another project' => 'Δημιουργία από άλλο έργο', + 'open' => 'Ανοικτό', + 'closed' => 'Κλειστό', + 'Priority:' => 'Προτεραιότητα:', + 'Reference:' => 'Αναφορά:', + 'Complexity:' => 'Πολυπλοκότητα:', + 'Swimlane:' => 'Λωρίδα:', + 'Column:' => 'Στήλη:', + 'Position:' => 'Θέση:', + 'Creator:' => 'Δημιουργός:', + 'Time estimated:' => 'Εκτιμώμενος χρόνος:', + '%s hours' => '%s ώρες', + 'Time spent:' => 'χρόνος που καταναλώθηκε:', + 'Created:' => 'Δημιουργήθηκε:', + 'Modified:' => 'Τροποποιήθηκε:', + 'Completed:' => 'Ολοκληρώθηκε:', + 'Started:' => 'Ξεκίνησε:', + 'Moved:' => 'Μετακινήθηκε:', + 'Task #%d' => 'Εργασία #%d', + 'Time format' => 'Μορφή ώρας', + 'Start date: ' => 'Ημερομηνία έναρξης: ', + 'End date: ' => 'Ημερομηνία λήξης: ', + 'New due date: ' => 'Νέα ημερομηνία προθεσμίας: ', + 'Start date changed: ' => 'Αλλαγμένη ημερομηνία έναρξης: ', + 'Disable personal projects' => 'Απενεργοποίηση ιδιωτικών έργων', + 'Do you really want to remove this custom filter: "%s"?' => 'Είστε σίγουροι για την αφαίρεση του προσαρμοσμένου φίλτρου: «%s»;', + 'Remove a custom filter' => 'Αφαίρεση του προσαρμοσμένου φίλτρου', + 'User activated successfully.' => 'Ο χρήστης ενεργοποιήθηκε με επιτυχία.', + 'Unable to enable this user.' => 'Δεν ήταν δυνατή η ενεργοποίηση του χρήστη.', + 'User disabled successfully.' => 'Η απενεργοποίηση του χρήστη έγινε με επιτυχία.', + 'Unable to disable this user.' => 'Δεν ήταν δυνατή η απενεργοποίηση του χρήστη.', + 'All files have been uploaded successfully.' => 'Όλα τα αρχεία ανέβηκαν με επιτυχία.', + 'The maximum allowed file size is %sB.' => 'Το μέγιστο μέγεθος αρχείου που επιτρέπεται είναι %sB.', + 'Drag and drop your files here' => 'Σύρετε τα αρχεία σας εδώ', + 'choose files' => 'επιλέξτε αρχεία', + 'View profile' => 'Προβολή προφίλ', + 'Two Factor' => 'Αυθεντικοποίηση δύο παραγόντων', + 'Disable user' => 'Απενεργοποίηση χρήστη', + 'Do you really want to disable this user: "%s"?' => 'Είστε σίγουροι για την απενεργοποίηση του χρήστη: «%s»;', + 'Enable user' => 'Ενεργοποίηση χρήστη', + 'Do you really want to enable this user: "%s"?' => 'Είστε σίγουροι για την ενεργοποίηση του χρήστη «%s»;', + 'Download' => 'Κατέβασμα', + 'Uploaded: %s' => 'Ανέβηκε το αρχείο: %s', + 'Size: %s' => 'Μέγεθος: %s', + 'Uploaded by %s' => 'Ανέβηκε από το χρήστη %s', + 'Filename' => 'Όνομα αρχείου', + 'Size' => 'Μέγεθος', + 'Column created successfully.' => 'Η στήλη δημιουργήθηκε με επιτυχία.', + 'Another column with the same name exists in the project' => 'Μια άλλη στήλη με το ίδιο όνομα υπάρχει στο έργο', + 'Default filters' => 'Προκαθορισμένα φίλτρα', + 'Your board doesn\'t have any columns!' => 'Το ταμπλό δεν έχει καμία στήλη!', + 'Change column position' => 'Αλλαγή θέσης στήλης', + 'Switch to the project overview' => 'Αλλαγή προβολής σε επισκόπηση έργου', + 'User filters' => 'Φίλτρα οριζόμενα από τον χρήστη', + 'Category filters' => 'Κατηγορία φίλτρων', + 'Upload a file' => 'Ανέβασμα αρχείου', + 'View file' => 'Προβολή αρχείου', + 'Last activity' => 'Τελευταία δραστηριότητα', + 'Change subtask position' => 'Αλλαγή θέσης υπο-εργασίας', + 'This value must be greater than %d' => 'Η τιμή πρέπει να είναι μεγαλύτερη από %d', + 'Another swimlane with the same name exists in the project' => 'Μια άλλη λωρίδα, με το ίδιο όνομα υπάρχει στο έργο', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Παράδειγμα: https://example.kanboard.org/ (χρησιμοποιείται για τη δημιουργία απόλυτων URLs)', + 'Actions duplicated successfully.' => 'Οι ενέργειες αντιγράφηκαν με επιτυχία.', + 'Unable to duplicate actions.' => 'Δεν ήταν δυνατή η αντιγραφή των ενεργειών.', + 'Add a new action' => 'Προσθήκη νέας ενέργειας', + 'Import from another project' => 'Εισαγωγή από άλλο έργο', + 'There is no action at the moment.' => 'Δεν υπάρχουν ενέργειες αυτή τη στιγμή.', + 'Import actions from another project' => 'Εισαγωγή ενεργειών από άλλο έργο', + 'There is no available project.' => 'Δεν υπάρχουν διαθέσιμα έργα.', + 'Local File' => 'Τοπικό αρχείο', + 'Configuration' => 'Παραμετροποίηση', + 'PHP version:' => 'Έκδοση PHP:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'Έκδοση λειτουργικού συστήματος:', + 'Database version:' => 'Έκδοση βάσης δεδομένων:', + 'Browser:' => 'Πρόγραμμα πλοήγησης:', + 'Task view' => 'Προβολή εργασίας', + 'Edit task' => 'Επεξεργασία εργασίας', + 'Edit description' => 'Επεξεργασία περιγραφής', + 'New internal link' => 'Νέος εσωτερικός σύνδεσμος', + 'Display list of keyboard shortcuts' => 'Προβολή λίστας συντομεύσεων πληκτρολογίου', + 'Avatar' => 'Άβαταρ', + 'Upload my avatar image' => 'Ανέβασμα της εικόνας μου άβαταρ', + 'Remove my image' => 'Αφαίρεση της εικόνας μου', + 'The OAuth2 state parameter is invalid' => 'Η παράμετρος κατάστασης OAuth2 δεν είναι έγκυρη', + 'User not found.' => 'Δε βρέθηκε ο χρήστης.', + 'Search in activity stream' => 'Αναζήτηση στη ροή δραστηριοτήτων', + 'My activities' => 'Οι δραστηριότητές μου', + 'Activity until yesterday' => 'Δραστηριότητα μέχρι χθες', + 'Activity until today' => 'Δραστηριότητα μέχρι σήμερα', + 'Search by creator: ' => 'Αναζήτηση με το δημιουργό: ', + 'Search by creation date: ' => 'Αναζήτηση με την ημερομηνία δημιουργίας: ', + 'Search by task status: ' => 'Αναζήτηση με την κατάσταση εργασίας: ', + 'Search by task title: ' => 'Αναζήτηση με τον τίλο εργασίας: ', + 'Activity stream search' => 'Αναζήτηση στη ροή δραστηριοτήτων', + 'Projects where "%s" is manager' => 'Έργα όπου ο «%s» είναι συντονιστής', + 'Projects where "%s" is member' => 'Έργα όπου ο «%s» είναι μέλος', + 'Open tasks assigned to "%s"' => 'Ανοιχτές εργασίες ανατεθειμένες στον/στην «%s»', + 'Closed tasks assigned to "%s"' => 'Κλειστές εργασίες ανατεθειμένες στον/στην «%s»', + 'Assign automatically a color based on a priority' => 'Αυτόματη εκχώρηση χρώματος βάση προτεραιότητας', + 'Overdue tasks for the project(s) "%s"' => 'Εκπρόθεσμες εργασίες για το έργο(α) «%s»', + 'Upload files' => 'Ανέβασμα αρχείων', + 'Installed Plugins' => 'Εγκατεστημένα Πρόσθετα', + 'Plugin Directory' => 'Κατάλογος Πρόσθετων', + 'Plugin installed successfully.' => 'Το πρόσθετο εγκαταστάθηκε με επιτυχία.', + 'Plugin updated successfully.' => 'Το πρόσθετο ενημερώθηκε με επιτυχία.', + 'Plugin removed successfully.' => 'Το πρόσθετο αφαιρέθηκε με επιτυχία.', + 'Subtask converted to task successfully.' => 'Η υπο-εργασία μετατράπηκε σε εργασία με επιτυχία.', + 'Unable to convert the subtask.' => 'Δεν ήταν δυνατή η μετατροπή της υπο-εργασίας.', + 'Unable to extract plugin archive.' => 'Δεν ήταν δυνατή η αποσυμπίεση του αρχείου του πρόσθετου.', + 'Plugin not found.' => 'Δε βρέθηκε το πρόσθετο.', + 'You don\'t have the permission to remove this plugin.' => 'Δεν έχετε το δικαίωμα να αφαιρέσετε το πρόσθετο.', + 'Unable to download plugin archive.' => 'Δεν ήταν δυνατό το κατέβασμα του αρχείου του πρόσθετου.', + 'Unable to write temporary file for plugin.' => 'Δεν ήταν δυνατή η εγγραφή προσωρινού αρχείου για το πρόσθετο.', + 'Unable to open plugin archive.' => 'Δεν ήταν δυνατή η εξαγωγή του αρχείου του πρόσθετου.', + 'There is no file in the plugin archive.' => 'Δεν υπάρχει αρχείο μέσα στο πακέτο του πρόσθετου.', + 'Create tasks in bulk' => 'Δημιουργία εργασιών μαζικά', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Η εγκατάσταση του Kanboard σας δεν έχει παραμετροποιηθεί για την εγκατάσταση πρόσθετων από τη διεπαφή χρήστη.', + 'There is no plugin available.' => 'Δεν υπάρχει διαθέσιμο πρόσθετο.', + 'Install' => 'Εγκατάσταση', + 'Update' => 'Ενημέρωση', + 'Up to date' => 'Ενημερωμένο', + 'Not available' => 'Μη διαθέσιμο', + 'Remove plugin' => 'Αφαίρεση πρόσθετου', + 'Do you really want to remove this plugin: "%s"?' => 'Είστε σίγουροι για την αφαίρεση του πρόσθετου: «%s»;', + 'Uninstall' => 'Απεγκατάσταση', + 'Listing' => 'Λίστα', + 'Metadata' => 'Μεταδεδομένα', + 'Manage projects' => 'Διαχείριση έργων', + 'Convert to task' => 'Μετατροπή σε εργασία', + 'Convert sub-task to task' => 'Μετατροπή υπο-εργασίας σε εργασία', + 'Do you really want to convert this sub-task to a task?' => 'Είστε σίγουροι για την μετατροπή της υπο-εργασίας σε εργασία;', + 'My task title' => 'Ο τίτλος της εργασίας μου', + 'Enter one task by line.' => 'Εισαγάγετε μια εργασία ανά γραμμή.', + 'Number of failed login:' => 'Αριθμός αποτυχημένων προσπαθειών σύνδεσης:', + 'Account locked until:' => 'Λογαριασμός κλειδωμένος μέχρι:', + 'Email settings' => 'Ρυθμίσεις email', + 'Email sender address' => 'Διεύθυνση αποστολέα email', + 'Email transport' => 'Μέθοδος αποστολής email', + 'Webhook token' => 'Webhook token', + 'Project tags management' => 'Διαχείριση σημάνσεων έργων', + 'Tag created successfully.' => 'Η σήμανση δημιουργήθηκε με επιτυχία.', + 'Unable to create this tag.' => 'Δεν ήταν δυνατή η δημιουργία της σήμανσης.', + 'Tag updated successfully.' => 'Η σήμανση ενημερώθηκε με επιτυχία.', + 'Unable to update this tag.' => 'Δεν ήταν δυνατή η επεξεργασία της σήμανσης.', + 'Tag removed successfully.' => 'Η σήμανση αφαιρέθηκε με επιτυχία.', + 'Unable to remove this tag.' => 'Δεν ήταν δυνατή η αφαίρεση της σήμανσης.', + 'Global tags management' => 'Διαχείριση καθολικών σημάνσεων', + 'Tags' => 'Σημάνσεις', + 'Tags management' => 'Διαχείριση σημάνσεων', + 'Add new tag' => 'Προσθήκη νέας σήμανσης', + 'Edit a tag' => 'Επεξεργασία σήμανσης', + 'Project tags' => 'Σημάνσεις έργων', + 'There is no specific tag for this project at the moment.' => 'Δεν υπάρχει αυτή τη στιγμή συγκεκριμένη σήμανση για το έργο.', + 'Tag' => 'Σήμανση', + 'Remove a tag' => 'Αφαίρεση σήμανσης', + 'Do you really want to remove this tag: "%s"?' => 'Είστε σίγουροι για την αφαίρεση της σήμανσης: «%s»;', + 'Global tags' => 'Καθολικές σημάνσεις', + 'There is no global tag at the moment.' => 'Δεν υπάρχει αυτή τη στιγμή καθολική σήμανση.', + 'This field cannot be empty' => 'Το πεδίο δεν μπορεί να είναι κενό', + 'Close a task when there is no activity in a specific column' => 'Να κλείνει μια εργασία όταν δεν υπάρχει δραστηριότητα σε μια συγκεκριμένη στήλη', + '%s removed a subtask for the task #%d' => 'Ο/Η %s αφαίρεσε μια υπο-εργασία για την εργασία #%d', + '%s removed a comment on the task #%d' => 'Ο/Η %s αφαίρεσε ένα σχόλιο στην εργασία #%d', + 'Comment removed on task #%d' => 'Αφαιρέθηκε σχόλιο στην εργασία #%d', + 'Subtask removed on task #%d' => 'Αφαιρέθηκε υπο-εργασία στην εργασία #%d', + 'Hide tasks in this column in the dashboard' => 'Απόκρυψη των εργασιών της στήλης στο κεντρικό ταμπλό', + '%s removed a comment on the task %s' => 'Ο/Η %s αφαίρεσε ένα σχόλιο στην εργασία %s', + '%s removed a subtask for the task %s' => 'Ο/Η %s αφαίρεσε μια υπο-εργασία για την εργασία %s', + 'Comment removed' => 'Αφαιρέθηκε σχόλιο', + 'Subtask removed' => 'Αφαιρέθηκε υπο-εργασία', + '%s set a new internal link for the task #%d' => 'Ο/Η %s όρισε ένα νέο εσωτερικό σύνδεσμο για την εργασία #%d', + '%s removed an internal link for the task #%d' => 'Ο/Η %s αφαίρεσε ένα εσωτερικό σύνδεσμο για την εργασία #%d', + 'A new internal link for the task #%d has been defined' => 'Ορίστηκε ένας νέος εσωτερικός σύνδεσμος για την εργασία #%d', + 'Internal link removed for the task #%d' => 'Αφαιρέθηκε ένας εσωτερικός σύνδεσμος για την εργασία #%d', + '%s set a new internal link for the task %s' => 'Ο/Η %s όρισε ένα νέο εσωτερικό σύνδεσμο για την εργασία %s', + '%s removed an internal link for the task %s' => 'Ο/Η %s αφαίρεσε ένα εσωτερικό σύνδεσμο για την εργασία %s', + 'Automatically set the due date on task creation' => 'Αυτόματος ορισμός της ημερομηνίας προθεσμίας στη δημιουργία της εργασίας', + 'Move the task to another column when closed' => 'Μετακίνηση της εργασίας σε άλλη στήλη όταν κλείσει', + 'Move the task to another column when not moved during a given period' => 'Μετακίνηση της εργασίας σε άλλη στήλη όταν δεν μετακινείται μέσα σε καθορισμένο διάστημα', + 'Dashboard for %s' => 'Κεντρικό ταμπλό για τον/την «%s»', + 'Tasks overview for %s' => 'Σύνοψη εργασιών για τον %s', + 'Subtasks overview for %s' => 'Σύνοψη υπο-εργασιών για τον %s', + 'Projects overview for %s' => 'Σύνοψη έργων για τον %s', + 'Activity stream for %s' => 'Ροή δραστηριοτήτων για τον %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Ορισμός χρώματος όταν η εργασία μετακινείται σε συγκεκριμένη λωρίδα', + 'Assign a priority when the task is moved to a specific swimlane' => 'Ορισμός προτεραιότητας όταν η εργασία μετακινείται σε συγκεκριμένη λωρίδα', + 'User unlocked successfully.' => 'Ο χρήστης ξεκλειθώθηκε με επιτυχία.', + 'Unable to unlock the user.' => 'Δεν ήταν δυνατό το ξεκλείδωμα του χρήστη.', + 'Move a task to another swimlane' => 'Μετακίνηση εργασίας σε άλλη λωρίδα', + 'Creator Name' => 'Όνομα Δημιουργού', + 'Time spent and estimated' => 'Χρόνος που δαπανήθηκε και εκτιμήθηκε', + 'Move position' => 'Θέση μετακίνησης', + 'Move task to another position on the board' => 'Μετακίνηση εργασίας σε άλλη θέση στον πίνακα', + 'Insert before this task' => 'Εισαγωγή πριν την εργασία', + 'Insert after this task' => 'Εισαγωγή μετά την εργασία', + 'Unlock this user' => 'Ξεκλείδωμα του χρήστη', + 'Custom Project Roles' => 'Προσαρμοσμένοι Ρόλοι Έργου', + 'Add a new custom role' => 'Προσθήκη νέου προσαρμοσμένου ρόλου', + 'Restrictions for the role "%s"' => 'Περιορισμοί για το ρόλο «%s»', + 'Add a new project restriction' => 'Προσθήκη περιορισμού για νέο έργο', + 'Add a new drag and drop restriction' => 'Προσθήκη περιορισμού για σύρε και άσε', + 'Add a new column restriction' => 'Προσθήκη περιορισμού για νέα στήλη', + 'Edit this role' => 'Επεξεργασία του ρόλου', + 'Remove this role' => 'Αφαίρεση του ρόλου', + 'There is no restriction for this role.' => 'Δεν υπάρχουν περιορισμοί για το ρόλο.', + 'Only moving task between those columns is permitted' => 'Επιτρέπεται μόνο η μετακίνηση εργασιών μεταξύ των στηλών', + 'Close a task in a specific column when not moved during a given period' => 'Κλείσιμο εργασίας σε μια συγκεκριμένη στήλη αν δεν μετακινηθεί μέσα σε καθορισμένο διάστημα', + 'Edit columns' => 'Επεξεργασία στηλών', + 'The column restriction has been created successfully.' => 'Ο περιορισμός στήλης δημιουργήθηκε με επιτυχία.', + 'Unable to create this column restriction.' => 'Δεν ήταν δυνατή η δημιουργία του περιορισμού στήλης.', + 'Column restriction removed successfully.' => 'Ο περιορισμός στήλης αφαιρέθηκε με επιτυχία.', + 'Unable to remove this restriction.' => 'Δεν ήταν δυνατή η αφαίρεση του περιορισμού στήλης.', + 'Your custom project role has been created successfully.' => 'Ο προσαρμοσμένος ρόλος έργου δημιουργήθηκε με επιτυχία.', + 'Unable to create custom project role.' => 'Δεν ήταν δυνατή η δημιουργία του προσαρμοσμένου ρόλου έργου.', + 'Your custom project role has been updated successfully.' => 'Ο προσαρμοσμένος ρόλος έργου ενημερώθηκε με επιτυχία.', + 'Unable to update custom project role.' => 'Δεν ήταν δυνατή η επεξεργασία του προσαρμοσμένου ρόλου έργου.', + 'Custom project role removed successfully.' => 'Ο προσαρμοσμένος ρόλος έργου αφαιρέθηκε με επιτυχία.', + 'Unable to remove this project role.' => 'Δεν ήταν δυνατή η αφαίρεση του προσαρμοσμένου ρόλου έργου.', + 'The project restriction has been created successfully.' => 'Ο περιορισμός έργου δημιουργήθηκε με επιτυχία.', + 'Unable to create this project restriction.' => 'Δεν ήταν δυνατή η δημιουργία του περιορισμού έργου.', + 'Project restriction removed successfully.' => 'Ο περιορισμός έργου αφαιρέθηκε με επιτυχία.', + 'You cannot create tasks in this column.' => 'Δεν μπορείτε να δημιουργήσετε εργασίες στη στήλη.', + 'Task creation is permitted for this column' => 'Η δημιουργία εργασίας επιτρέπεται για τη στήλη', + 'Closing or opening a task is permitted for this column' => 'Το κλείσιμο ή άνοιγμα εργασίας επιτρέπονται για τη στήλη', + 'Task creation is blocked for this column' => 'Η δημιουργία εργασίας είναι μπλοκαρισμένη για τη στήλη', + 'Closing or opening a task is blocked for this column' => 'Το κλείσιμο ή άνοιγμα εργασίας είναι μπλοκαρισμένα για τη στήλη', + 'Task creation is not permitted' => 'Η δημιουργία εργασίας δεν επιτρέπεται', + 'Closing or opening a task is not permitted' => 'Το κλείσιμο ή άνοιγμα εργασίας δεν επιτρέπεται', + 'New drag and drop restriction for the role "%s"' => 'Νέος περιορισμός σύρε και άσε για το ρόλο «%s»', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Οι άνθρωποι που θα ανήκουν σε αυτό το ρόλο θα μπορούν να μετακινούν εργασίες μόνο μεταξύ των στηλών προέλευσης και προορισμού.', + 'Remove a column restriction' => 'Αφαίρεση περιορισμού στήλης', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Είστε σίγουροι για την αφαίρεση του περιορισμού στήλης: «%s» σε «%s»;', + 'New column restriction for the role "%s"' => 'Νέος περιορισμός στήλης για το ρόλο «%s»', + 'Rule' => 'Κανόνας', + 'Do you really want to remove this column restriction?' => 'Είστε σίγουροι για την αφαίρεση του περιορισμού στήλης;', + 'Custom roles' => 'Προσαρμοσμένοι ρόλοι', + 'New custom project role' => 'Νέος προσαρμοσμένος ρόλος έργου', + 'Edit custom project role' => 'Επεξεργασία προσαρμοσμένου ρόλου έργου', + 'Remove a custom role' => 'Αφαίρεση προσαρμοσμένου ρόλου', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Είστε σίγουροι για την αφαίρεση του προσαρμοσμένου ρόλου: «%s»; Όλοι οι άνθρωποι που είναι ανήκουν στο ρόλο θα γίνουν μέλη του έργου.', + 'There is no custom role for this project.' => 'Δεν υπάρχει προσαρμοσμένος ρόλος για το έργο.', + 'New project restriction for the role "%s"' => 'Νέος περιορισμός έργου για το ρόλο «%s»', + 'Restriction' => 'Περιορισμός', + 'Remove a project restriction' => 'Αφαίρεση περιορισμού ρόλου', + 'Do you really want to remove this project restriction: "%s"?' => 'Είστε σίγουροι για την αφαίρεση του περιορισμού έργου: «%s»;', + 'Duplicate to multiple projects' => 'Αντιγραφή σε πολλαπλά έργα', + 'This field is required' => 'Το πεδίο είναι απαιτούμενο', + 'Moving a task is not permitted' => 'Η μετακίνηση εργασίας δεν επιτρέπεται', + 'This value must be in the range %d to %d' => 'Η τιμή πρέπει να είναι εντός του εύρους %d και %d', + 'You are not allowed to move this task.' => 'Δεν επιτρέπεται να μετακινήσετε την εργασία.', + 'API User Access' => 'API Πρόσβασης Χρήστη', + 'Preview' => 'Προεπισκόπηση', + 'Write' => 'Σύνταξη', + 'Write your text in Markdown' => 'Γράψτε το κείμενό σας σε Markdown', + 'No personal API access token registered.' => 'Δεν υπάρχει καταχωρημένο προσωπικό token πρόσβασης στο API.', + 'Your personal API access token is "%s"' => 'Το προσωπικό σας token πρόσβασης στο API είναι «%s»', + 'Remove your token' => 'Αφαίρεση του token σας', + 'Generate a new token' => 'Δημιουργία νέου token', + 'Showing %d-%d of %d' => 'Προβολή %d-%d από %d', + 'Outgoing Emails' => 'Εξερχόμενα email', + 'Add or change currency rate' => 'Προσθήκη ή αλλαγή ισοτιμίας νομίσματος', + 'Reference currency: %s' => 'Νόμισμα αναφοράς: %s', + 'Add custom filters' => 'Προσθήκη προσαρμοσμένων φίλτρων', + 'Export' => 'Εξαγωγή', + 'Add link label' => 'Προσθήκη ετικέτας συνδέσμου', + 'Incompatible Plugins' => 'Μη συμβατά πρόσθετα', + 'Compatibility' => 'Συμβατότητα', + 'Permissions and ownership' => 'Άδειες και ιδιοκτησία', + 'Priorities' => 'Προτεραιότητες', + 'Close this window' => 'Κλείσιμο του παραθύρου', + 'Unable to upload this file.' => 'Δεν ήταν δυνατό το ανέβασμα του αρχείου.', + 'Import tasks' => 'Εισαγωγή εργασιών', + 'Choose a project' => 'Επιλογή έργου', + 'Profile' => 'Προφίλ', + 'Application role' => 'Ρόλος εφαρμογής', + '%d invitations were sent.' => 'Στάλθηκαν %d προσκλήσεις.', + '%d invitation was sent.' => 'Στάλθηκε %d πρόσκληση.', + 'Unable to create this user.' => 'Δεν ήταν δυνατή η δημιουργία του χρήστη.', + 'Kanboard Invitation' => 'Πρόσκληση Kanboard', + 'Visible on dashboard' => 'Ορατό στο κεντρικό ταμπλό', + 'Created at:' => 'Δημιουργήθηκε στις:', + 'Updated at:' => 'Ενημερώθηκε στις:', + 'There is no custom filter.' => 'Δεν υπάρχει προσαρμοσμένο φίλτρο.', + 'New User' => 'Νέος Χρήστης', + 'Authentication' => 'Αυθεντικοποίηση', + 'If checked, this user will use a third-party system for authentication.' => 'Αν είναι τσεκαρισμένο, ο χρήστης θα χρησιμοποιεί ένα τρίτο σύστημα για αυθεντικοποίηση.', + 'The password is necessary only for local users.' => 'Ο κωδικός πρόσβασης είναι απαραίτητος μόνο για τοπικούς λογαριασμούς.', + 'You have been invited to register on Kanboard.' => 'Έχετε προσκληθεί για να εγγραφείτε στο Kanboard.', + 'Click here to join your team' => 'Κάντε κλικ εδώ για να συμμετέχετε στην ομάδα σας', + 'Invite people' => 'Πρόσκληση ανθρώπων', + 'Emails' => 'Email', + 'Enter one email address by line.' => 'Εισαγάγετε μια διεύθυνση email ανά σειρά.', + 'Add these people to this project' => 'Προσθήκη των ανθρώπων στο έργο', + 'Add this person to this project' => 'Προσθήκη του ανθρώπου στο έργο', + 'Sign-up' => 'Εγγραφή', + 'Credentials' => 'Διαπιστευτήρια', + 'New user' => 'Νέος χρήστης', + 'This username is already taken' => 'Το όνομα χρήστη χρησιμοποείται ήδη', + 'Your profile must have a valid email address.' => 'Το προφίλ σας πρέπει να έχει μια έγκυρη διεύθυνση email.', + 'TRL - Turkish Lira' => 'TRL - Turkish Lira', + 'The project email is optional and could be used by several plugins.' => 'Η διεύθυνση email του έργου είναι προαιρετική και μπορεί να χρησιμοποιηθεί από διάφορα πρόσθετα.', + 'The project email must be unique across all projects' => 'Η διεύθυνση email του έργου πρέπει να είναι μοναδική σε όλα τα έργα', + 'The email configuration has been disabled by the administrator.' => 'Η παραμετροποίηση email έχει απενεργοποιηθεί από τον διαχειριστή σας.', + 'Close this project' => 'Κλείσιμο του έργου', + 'Open this project' => 'Άνοιγμα του έργου', + 'Close a project' => 'Κλείσιμο ενός έργου', + 'Do you really want to close this project: "%s"?' => 'Είστε σίγουροι για το κλείσιμο του έργου: «%s»;', + 'Reopen a project' => 'Επανα-άνοιγμα ενός έργου', + 'Do you really want to reopen this project: "%s"?' => 'Είστε σίγουροι για το επανα-άνοιγμα του έργου: «%s»;', + 'This project is open' => 'Το έργο είναι ανοικτό', + 'This project is closed' => 'Το έργο είναι κλειστό', + 'Unable to upload files, check the permissions of your data folder.' => 'Αδύνατο το ανέβασμα αρχείων, ελέγξτε τα δικαιώματα του φακέλου αρχείων σας.', + 'Another category with the same name exists in this project' => 'Υπάρχει ήδη άλλη κατηγορία με το ίδιο όνομα σε αυτό το έργο', + 'Comment sent by email successfully.' => 'Το σχόλιο στάλθηκε μέσω email με επιτυχία.', + 'Sent by email to "%s" (%s)' => 'Στάλθηκε μέσω email στον/στην «%s» (%s)', + 'Unable to read uploaded file.' => 'Δεν ήταν δυνατή η ανάγνωση του ανεβασμένου αρχείου.', + 'Database uploaded successfully.' => 'Η βάση δεδομένων ανέβηκε με επιτυχία.', + 'Task sent by email successfully.' => 'Η εργασία στάλθηκε μέσω email με επιτυχία.', + 'There is no category in this project.' => 'Δεν υπάρχει κατηγορία στο έργο.', + 'Send by email' => 'Αποστολή μέσω email', + 'Create and send a comment by email' => 'Δημιουργία και αποστολή σχολίου μέσω email', + 'Subject' => 'Θέμα', + 'Upload the database' => 'Ανέβασμα της βάσης δεδομένων', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Μπορείτε να ανεβάσετε την κατεβασμένη από προηγούμενη στιγμή βάση δεδομένων Sqlite (σε μορφή Gzip).', + 'Database file' => 'Αρχείο βάσης δεδομένων', + 'Upload' => 'Ανέβασμα', + 'Your project must have at least one active swimlane.' => 'Το έργο σας πρέπει να διαθέτει τουλάχιστον μία λωρίδα.', + 'Project: %s' => 'Έργο: %s', + 'Automatic action not found: "%s"' => 'Δε βρέθηκε η αυτόματη ενέργεια: «%s»', + '%d projects' => '%d έργα', + '%d project' => '%d έργο', + 'There is no project.' => 'Δεν υπάρχει κάποιο έργο.', + 'Sort' => 'Ταξινόμηση', + 'Project ID' => 'Αναγνωριστικό έργου', + 'Project name' => 'Όνομα έργου', + 'Public' => 'Δημόσιο', + 'Personal' => 'Ιδιωτικό', + '%d tasks' => '%d εργασίες', + '%d task' => '%d εργασία', + 'Task ID' => 'Αναγνωριστικό εργασίας', + 'Assign automatically a color when due date is expired' => 'Αυτόματη εκχώρηση χρώματος όταν περάσει η προθεσμία της ημερομηνίας', + 'Total score in this column across all swimlanes' => 'Συνολικό σκορ της στήλης μεταξύ όλων των λωρίδων', + 'HRK - Kuna' => 'HRK - Kuna', + 'ARS - Argentine Peso' => 'ARS - Argentine Peso', + 'COP - Colombian Peso' => 'COP - Colombian Peso', + '%d groups' => '%d ομάδες', + '%d group' => '%d ομάδα', + 'Group ID' => 'Αναγνωριστικό ομάδας', + 'External ID' => 'Εξωτερικό αναγνωριστικό', + '%d users' => '%d χρήστες', + '%d user' => '%d χρήστης', + 'Hide subtasks' => 'Απόκρυψη υπο-εργασιών', + 'Show subtasks' => 'Προβολή υπο-εργασιών', + 'Authentication Parameters' => 'Παράμετροι αυθεντικοποίησης', + 'API Access' => 'Πρόσβαση στο API', + 'No users found.' => 'Δε βρέθηκαν χρήστες.', + 'User ID' => 'Αναγνωριστικό χρήστη', + 'Notifications are activated' => 'Οι ειδοποιήσεις είναι ενεργοποιημένες', + 'Notifications are disabled' => 'Οι ειδοποιήσεις είναι απενεργοποιημένες', + 'User disabled' => 'Χρήστης απενεργοποιημένος', + '%d notifications' => '%d ειδοποιήσεις', + '%d notification' => '%d ειδοποίηση', + 'There is no external integration installed.' => 'Δεν υπάρχει εγκατεστημένη εξωτερική ενσωμάτωση.', + 'You are not allowed to update tasks assigned to someone else.' => 'Δεν επιτρέπεται να ενημερώνετε εργασίες που έχουν ανατεθεί σε κάποιον άλλον.', + 'You are not allowed to change the assignee.' => 'Δεν επιτρέπεται να αλλάξετε τον ανατιθέμενο.', + 'Task suppression is not permitted' => 'Δεν επιτρέπεται η καταστολή εργασίας', + 'Changing assignee is not permitted' => 'Δεν επιτρέπεται η αλλαγή ανατιθέμενου', + 'Update only assigned tasks is permitted' => 'Επιτρέπεται μόνο η επεξεργασία ανατεθειμένων εργασιών', + 'Only for tasks assigned to the current user' => 'Μόνο για εργασίες που έχουν ανατεθεί στον τρέχων χρήστη', + 'My projects' => 'Τα έργα μου', + 'You are not a member of any project.' => 'Δεν είστε μέλος σε οποιοδήποτε έργο.', + 'My subtasks' => 'Οι υπο-εργασίες μου', + '%d subtasks' => '%d υπο-εργασίες', + '%d subtask' => '%d υπο-εργασία', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Επιτρέπεται μόνο η μετακίνηση εργασιών μεταξύ των στηλών που είναι ανατεθειμένες στον τρέχων χρήστη', + '[DUPLICATE]' => '[ΔΙΠΛΟΤΥΠΟ]', + 'DKK - Danish Krona' => 'DKK - Danish Krona', + 'Remove user from group' => 'Αφαίρεση χρήση από ομάδα', + 'Assign the task to its creator' => 'Ανάθεση της εργασίας στον δημιουργό της', + 'This task was sent by email to "%s" with subject "%s".' => 'Η εργασία στάλθηκε μέσω email στον/στην «%s» με θέμα «%s».', + 'Predefined Email Subjects' => 'Προκαθορισμένα Θέματα Email', + 'Write one subject by line.' => 'Εισαγάγετε ένα θέμα ανά γραμμή.', + 'Create another link' => 'Δημιουργία άλλου συνδέσμου', + 'BRL - Brazilian Real' => 'BRL - Brazilian Real', + 'Add a new Kanboard task' => 'Προσθήκη νέας εργασίας στο Kanboard', + 'Subtask not started' => 'Η υπο-εργασία δεν ξεκίνησε', + 'Subtask currently in progress' => 'Η υπο-εργασία είναι σε εξέλιξη', + 'Subtask completed' => 'Η υπο-εργασία ολοκληρώθηκε', + 'Subtask added successfully.' => 'Η υπο-εργασία προστέθηκε με επιτυχία.', + '%d subtasks added successfully.' => '%d υπο-εργασίες προστέθηκαν με επιτυχία.', + 'Enter one subtask by line.' => 'Εισαγάγετε μια υπο-εργασία ανά γραμμή.', + 'Predefined Contents' => 'Προκαθορισμένα Περιεχόμενα', + 'Predefined contents' => 'Προκαθορισμένα περιεχόμενα', + 'Predefined Task Description' => 'Προκαθορισμένη Περιγραφή Εργασίας', + 'Do you really want to remove this template? "%s"' => 'Είστε σίγουροι για την αφαίρεση του προτύπου; «%s»', + 'Add predefined task description' => 'Προσθήκη προκαθορισμένης περιγραφής εργασίας', + 'Predefined Task Descriptions' => 'Προκαθορισμένες Περιγραφές Εργασιών', + 'Template created successfully.' => 'Το πρότυπο δημιουργήθηκε με επιτυχία.', + 'Unable to create this template.' => 'Δεν ήταν δυνατή η δημιουργία του προτύπου.', + 'Template updated successfully.' => 'Το πρότυπο ενημερώθηκε με επιτυχία.', + 'Unable to update this template.' => 'Δεν ήταν δυνατή η επεξεργασία του προτύπου.', + 'Template removed successfully.' => 'Το πρότυπο αφαιρέθηκε με επιτυχία.', + 'Unable to remove this template.' => 'Δεν ήταν δυνατή η αφαίρεση του προτύπου.', + 'Template for the task description' => 'Πρότυπο για την περιγραφή εργασίας', + 'The start date is greater than the end date' => 'Η ημερομηνία έναρξης είναι μεταγενέστερη της λήξης', + 'Tags must be separated by a comma' => 'Οι σημάνσεις θα πρέπει να χωρίζονται με κόμμα', + 'Only the task title is required' => 'Απαιτείται μόνο ο τίτλος εργασίας', + 'Creator Username' => 'Όνομα Χρήστη Δημιουργού', + 'Color Name' => 'Όνομα Χρώματος', + 'Column Name' => 'Όνομα Στήλης', + 'Swimlane Name' => 'Όνομα Λωρίδας', + 'Time Estimated' => 'Εκτιμώμενος Χρόνος', + 'Time Spent' => 'Χρόνος που Δαπανήθηκε', + 'External Link' => 'Εξωτερικός Σύνδεσμος', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Το χαρακτηριστικό αυτό ενεργοποιεί τις ροές iCal, RSS και την προβολή του δημόσιου πίνακα.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Να σταματά ο χρονομετρητής όλων των εργασιών όταν μετακινείται μια εργασία σε άλλη στήλη', + 'Subtask Title' => 'Τίτλος Υπο-εργασίας', + 'Add a subtask and activate the timer when moving a task to another column' => 'Προσθήκη υπο-εργασίας και ενεργοποίηση του χρονομετρητή όταν μετακινείται μια εργασία σε άλλη στήλη', + 'days' => 'ημέρες', + 'minutes' => 'λεπτά', + 'seconds' => 'δευτερόλεπτα', + 'Assign automatically a color when preset start date is reached' => 'Αυτόματη εκχώρηση χρώματος όταν φτάνει η ημερομηνία έναρξης', + 'Move the task to another column once a predefined start date is reached' => 'Μετακίνηση της εργασίας σε άλλη στήλη όταν φτάσει η προκαθορισμένη ημερομηνία έναρξης', + 'This task is now linked to the task %s with the relation "%s"' => 'Η εργασία είναι τώρα συνδεδεμένη με την εργασία %s με τη συσχέτιση «%s»', + 'The link with the relation "%s" to the task %s has been removed' => 'Ο σύνδεσμος με τη συσχέτιση «%s» με την εργασία %s αφαιρέθηκε', + 'Custom Filter:' => 'Προσαρμοσμένο Φίλτρο:', + 'Unable to find this group.' => 'Δεν ήταν δυνατή η εύρεση της ομάδας.', + '%s moved the task #%d to the column "%s"' => 'Ο/Η %s μετακίνησε την εργασία #%d στην στήλη «%s»', + '%s moved the task #%d to the position %d in the column "%s"' => 'Ο/Η %s μετακίνησε την εργασία #%d στη θέση %d στην στήλη «%s»', + '%s moved the task #%d to the swimlane "%s"' => 'Ο/Η %s μετακίνησε την εργασία #%d στην λωρίδα «%s»', + '%sh spent' => '%sω δαπανήθηκαν', + '%sh estimated' => '%sω εκτιμώμενο', + 'Select All' => 'Επιλογή Όλων', + 'Unselect All' => 'Ακύρωση Επιλογής Όλων', + 'Apply action' => 'Εφαρμογή ενέργειας', + 'Move selected tasks to another column or swimlane' => 'Μετακίνηση επιλεγμένων εργασιών σε άλλη στήλη ή λωρίδα', + 'Edit tasks in bulk' => 'Μαζική επεξεργασία εργασιών', + 'Choose the properties that you would like to change for the selected tasks.' => 'Επιλογή των ιδιοτήτων που θέλετε να αλλάξετε για τις επιλεγμένες εργασίες.', + 'Configure this project' => 'Παραμετροποίηση του έργου', + 'Start now' => 'Εκκίνηση τώρα', + '%s removed a file from the task #%d' => 'Ο/Η %s αφαίρεσε ένα αρχείο από την εργασία #%d', + 'Attachment removed from task #%d: %s' => 'Αφαιρέθηκε συνημμένο από την εργασία #%d: %s', + 'No color' => 'Χωρίς χρώμα', + 'Attachment removed "%s"' => 'Αφαίρεση συνημμένου «%s»', + '%s removed a file from the task %s' => 'Ο/Η %s αφαίρεσε ένα αρχείο από την εργασία %s', + 'Move the task to another swimlane when assigned to a user' => 'Μετακίνηση της εργασίας σε άλλη στήλη όταν ανατίθεται σε ένα χρήστη', + 'Destination swimlane' => 'Λωρίδα προορισμού', + 'Assign a category when the task is moved to a specific swimlane' => 'Εκχώρηση κατηγορίας όταν η εργασία μετακινείται σε συγκεκριμένη λωρίδα', + 'Move the task to another swimlane when the category is changed' => 'Μετακίνηση της εργασίας σε άλλη λωρίδα όταν αλλάζει η κατηγορία', + 'Reorder this column by priority (ASC)' => 'Ταξινόμηση της στήλης με την προτεραιότητα (ΑΥΞ)', + 'Reorder this column by priority (DESC)' => 'Ταξινόμηση της στήλης με την προτεραιότητα (ΦΘΙ)', + 'Reorder this column by assignee and priority (ASC)' => 'Ταξινόμηση της στήλης με τον ανατιθέμενο και προτεραιότητα (ΑΥΞ)', + 'Reorder this column by assignee and priority (DESC)' => 'Ταξινόμηση της στήλης με τον ανατιθέμενο και προτεραιότητα (ΦΘΙ)', + 'Reorder this column by assignee (A-Z)' => 'Ταξινόμηση της στήλης με τον ανατιθέμενο (ΑΥΞ)', + 'Reorder this column by assignee (Z-A)' => 'Ταξινόμηση της στήλης με τον ανατιθέμενο (ΦΘΙ)', + 'Reorder this column by due date (ASC)' => 'Ταξινόμηση της στήλης με την ημερομηνία προθεσμίας (ΑΥΞ)', + 'Reorder this column by due date (DESC)' => 'Ταξινόμηση της στήλης με την ημερομηνία προθεσμίας (ΦΘΙ)', + 'Reorder this column by id (ASC)' => 'Ταξινόμηση της στήλης με το αναγνωριστικό (ΑΥΞ)', + 'Reorder this column by id (DESC)' => 'Ταξινόμηση της στήλης με το αναγνωριστικό (ΦΘΙ)', + '%s moved the task #%d "%s" to the project "%s"' => 'Ο/Η %s μετακίνησε την εργασία #%d «%s» στο έργο «%s»', + 'Task #%d "%s" has been moved to the project "%s"' => 'Η εργασία #%d «%s» μετακινήθηκε στο έργο «%s»', + 'Move the task to another column when the due date is less than a certain number of days' => 'Μετακίνηση της εργασίας σε άλλη στήλη όταν η ημερομηνία προθεσμίας είναι μικρότερη από συγκεκριμένο αριθμό ημερών', + 'Automatically update the start date when the task is moved away from a specific column' => 'Αυτόματη ενημέρωση της ημερομηνίας έναρξης όταν η εργασία μετακινείται εκτός συγκεκριμένης στήλης', + 'HTTP Client:' => 'Πελάτης HTTP:', + 'Assigned' => 'Ανατεθειμένο', + 'Task limits apply to each swimlane individually' => 'Τα όρια εργασιών εφαρμόζονται σε κάθε λωρίδα ξεχωριστά', + 'Column task limits apply to each swimlane individually' => 'Τα όρια των εργασιών στις στήλες αφορούν κάθε λωρίδα ξεχωριστά', + 'Column task limits are applied to each swimlane individually' => 'Τα όρια των εργασιών στις στήλες εφαρμόζονται σε κάθε λωρίδα ξεχωριστά', + 'Column task limits are applied across swimlanes' => 'Τα όρια των εργασιών στις στήλες εφαρμόζονται σε όλες τις λωρίδες', + 'Task limit: ' => 'Όριο εργασιών: ', + 'Change to global tag' => 'Αλλαγή σε καθολική ετικέτα', + 'Do you really want to make the tag "%s" global?' => 'Είστε σίγουροι για την μετατροπή της ετικέτας «%s» σε καθολική;', + 'Enable global tags for this project' => 'Ενεργοποίηση καθολικών σημάνσεων για το έργο', + 'Group membership(s):' => 'Ιδιοκτησία σε ομάδα(ες):', + '%s is a member of the following group(s): %s' => 'Ο/Η %s είναι μέλος των παρακάτω ομάδων: %s', + '%d/%d group(s) shown' => 'Εμφανίζονται %d/%d ομάδες', + 'Subtask creation or modification' => 'Δημιουργία ή τροποποίηση υπο-εργασιών', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Ανάθεση της εργασίας σε συγκεκριμένο χρήστη όταν η εργασία μετακινείται σε συγκεκριμένη λωρίδα', + 'Comment' => 'Σχόλιο', + 'Collapse vertically' => 'Σύμπτυξη κάθετα', + 'Expand vertically' => 'Ανάπτυξη κάθετα', + 'MXN - Mexican Peso' => 'MXN - Mexican Peso', + 'Estimated vs actual time per column' => 'Εκτιμώμενος έναντι πραγματικού χρόνου ανά στήλη', + 'HUF - Hungarian Forint' => 'HUF - Hungarian Forint', + 'XBT - Bitcoin' => 'XBT - Bitcoin', + 'You must select a file to upload as your avatar!' => 'Πρέπει να επιλέξετε ένα αρχείο για ανέβασμα ως το άβατάρ σας!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'Το αρχείο που ανεβάσατε δεν είναι έγκυρη εικόνα! (Επιτρέπονται μόνο *.gif, *.jpg, *.jpeg και *.png!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Αυτόματος ορισμός της ημερομηνίας προθεσμίας όταν η εργασία μετακινείται εκτός συγκεκριμένης στήλης', + 'No other projects found.' => 'Δε βρέθηκαν άλλα έργα', + 'Tasks copied successfully.' => 'Οι εργασίες αντιγράφηκαν με επιτυχία.', + 'Unable to copy tasks.' => 'Δεν ήταν δυνατή η αντιγραφή των εργασιών.', + 'Theme' => 'Θέμα', + 'Theme:' => 'Θέμα:', + 'Light theme' => 'Ανοιχτόχρωμο θέμα', + 'Dark theme' => 'Σκοτεινόχρωμο θέμα', + 'Automatic theme - Sync with system' => 'Αυτόματο θέμα - Συγχρονισμός με το σύστημα', + 'Application managers or more' => 'Συντονιστές εφαρμογής ή περισσότερο', + 'Administrators' => 'Διαχειριστές', + 'Visibility:' => 'Ορατότητα', + 'Standard users' => 'Απλοί χρήστες', + 'Visibility is required' => 'Η ορατότητα είναι απαιτούμενη', + 'The visibility should be an app role' => 'Η ορατότητα πρέπει να είναι ρόλος της εφαρμογής', + 'Reply' => 'Απάντηση', + '%s wrote: ' => 'Ο/Η %s έγραψε: ', + 'Number of visible tasks in this column and swimlane' => 'Αριθμός ορατών εργασιών σε αυτήν τη στήλη και τη λωρίδα κολύμβησης', + 'Number of tasks in this swimlane' => 'Αριθμός εργασιών σε αυτήν τη λωρίδα κολύμβησης', + 'Unable to find another subtask in progress, you can close this window.' => 'Δεν είναι δυνατή η εύρεση άλλης δευτερεύουσας εργασίας σε εξέλιξη, μπορείτε να κλείσετε αυτό το παράθυρο.', + 'This theme is invalid' => 'Αυτό το θέμα είναι μη έγκυρο', + 'This role is invalid' => 'Αυτός ο ρόλος είναι μη έγκυρος', + 'This timezone is invalid' => 'Αυτή η ζώνη ώρας είναι μη έγκυρη', + 'This language is invalid' => 'Αυτή η γλώσσα είναι μη έγκυρη', + 'This URL is invalid' => 'Αυτό το URL είναι μη έγκυρο', + 'Date format invalid' => 'Μη έγκυρη μορφή ημερομηνίας', + 'Time format invalid' => 'Μη έγκυρη μορφή ώρας', + 'Invalid Mail transport' => 'Μη έγκυρη μεταφορά αλληλογραφίας', + 'Color invalid' => 'Μη έγκυρο χρώμα', + 'This value must be greater or equal to %d' => 'Αυτή η τιμή πρέπει να είναι μεγαλύτερη ή ίση με %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Προσθήκη BOM στην αρχή του αρχείου (απαιτείται για το Microsoft Excel)', + 'Just add these tag(s)' => 'Απλώς προσθέστε αυτές τις ετικέτες', + 'Remove internal link(s)' => 'Αφαίρεση εσωτερικού(ών) συνδέσμου(ων)', + 'Import tasks from another project' => 'Εισαγωγή εργασιών από άλλο έργο', + 'Select the project to copy tasks from' => 'Επιλέξτε το έργο από το οποίο θα αντιγράψετε εργασίες', + 'The total maximum allowed attachments size is %sB.' => 'Το συνολικό μέγιστο επιτρεπόμενο μέγεθος συνημμένων είναι %sB.', + 'Add attachments' => 'Προσθήκη συνημμένων', + 'Task #%d "%s" is overdue' => 'Η εργασία #%d «%s» είναι εκπρόθεσμη', + 'Enable notifications by default for all new users' => 'Ενεργοποίηση ειδοποιήσεων από προεπιλογή για όλους τους νέους χρήστες', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Ανάθεση της εργασίας στον δημιουργό της για συγκεκριμένες στήλες, αν δεν έχει οριστεί υπεύθυνος χειροκίνητα', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Ανάθεση μιας εργασίας στον συνδεδεμένο χρήστη κατά την αλλαγή στήλης προς την καθορισμένη στήλη, αν δεν έχει ανατεθεί χρήστης', +]; diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php new file mode 100644 index 0000000..75f38b7 --- /dev/null +++ b/app/Locale/es_ES/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => '.', + 'None' => 'Ninguno', + 'Edit' => 'Modificar', + 'Remove' => 'Suprimir', + 'Yes' => 'Sí', + 'No' => 'No', + 'cancel' => 'cancelar', + 'or' => 'o', + 'Yellow' => 'Amarillo', + 'Blue' => 'Azul', + 'Green' => 'Verde', + 'Purple' => 'Púrpura', + 'Red' => 'Rojo', + 'Orange' => 'Naranja', + 'Grey' => 'Gris', + 'Brown' => 'Marron', + 'Deep Orange' => 'Naranja', + 'Dark Grey' => 'Gris Oscuro', + 'Pink' => 'Rosado', + 'Teal' => 'Verde Azulado', + 'Cyan' => 'Azul Claro', + 'Lime' => 'Lima', + 'Light Green' => 'Verde Claro', + 'Amber' => 'Ámbar', + 'Save' => 'Guardar', + 'Login' => 'Iniciar sesión (Ingresar)', + 'Official website:' => 'Página web oficial :', + 'Unassigned' => 'No asignado', + 'View this task' => 'Ver esta tarea', + 'Remove user' => 'Suprimir un usuario', + 'Do you really want to remove this user: "%s"?' => '¿Realmente quiere suprimir este usuario: « %s » ?', + 'All users' => 'Todos los usuarios', + 'Username' => 'Nombre de usuario', + 'Password' => 'Contraseña', + 'Administrator' => 'Administrador', + 'Sign in' => 'Iniciar sesión', + 'Users' => 'Usuarios', + 'Forbidden' => 'Acceso denegado', + 'Access Forbidden' => 'Acceso denegado', + 'Edit user' => 'Editar un usuario', + 'Logout' => 'Salir', + 'Bad username or password' => 'Usuario o contraseña incorecto', + 'Edit project' => 'Editar el proyecto', + 'Name' => 'Nombre', + 'Projects' => 'Proyectos', + 'No project' => 'Ningún proyecto', + 'Project' => 'Proyecto', + 'Status' => 'Estado', + 'Tasks' => 'Tareas', + 'Board' => 'Tablero', + 'Actions' => 'Acciones', + 'Inactive' => 'Inactivo', + 'Active' => 'Activo', + 'Unable to update this board.' => 'No se puede actualizar este tablero.', + 'Disable' => 'Desactivar', + 'Enable' => 'Activar', + 'New project' => 'Nuevo proyecto', + 'Do you really want to remove this project: "%s"?' => '¿Realmente quiere suprimir este proyecto: « %s » ?', + 'Remove project' => 'Suprimir el proyecto', + 'Edit the board for "%s"' => 'Modificar el tablero para « %s »', + 'Add a new column' => 'Añadir una nueva columna', + 'Title' => 'Titulo', + 'Assigned to %s' => 'Asignada a %s', + 'Remove a column' => 'Suprimir esta columna', + 'Unable to remove this column.' => 'No se puede suprimir esta columna.', + 'Do you really want to remove this column: "%s"?' => '¿Realmente quiere suprimir esta columna : « %s » ?', + 'Settings' => 'Preferencias', + 'Application settings' => 'Parámetros de la aplicación', + 'Language' => 'Idioma', + 'Webhook token:' => 'Token para los webhooks :', + 'API token:' => 'Token de API:', + 'Database size:' => 'Tamaño de la base de datos:', + 'Download the database' => 'Descargar la base de datos', + 'Optimize the database' => 'Optimizar la base de datos', + '(VACUUM command)' => '(Comando VACUUM)', + '(Gzip compressed Sqlite file)' => '(Archivo Sqlite comprimido en Gzip)', + 'Close a task' => 'Cerrar una tarea', + 'Column' => 'Columna', + 'Color' => 'Color', + 'Assignee' => 'Persona asignada', + 'Create another task' => 'Crear una nueva tarea', + 'New task' => 'Nueva tarea', + 'Open a task' => 'Abrir una tarea', + 'Do you really want to open this task: "%s"?' => '¿Realmente quiere abrir esta tarea: « %s » ?', + 'Back to the board' => 'Volver al tablero', + 'There is nobody assigned' => 'No hay nadie asignado a esta tarea', + 'Column on the board:' => 'Columna en el tablero: ', + 'Close this task' => 'Cerrar esta tarea', + 'Open this task' => 'Abrir esta tarea', + 'There is no description.' => 'No hay descripción.', + 'Add a new task' => 'Añadir una nueva tarea', + 'The username is required' => 'El nombre de usuario es obligatorio', + 'The maximum length is %d characters' => 'La longitud máxima es de %d caracteres', + 'The minimum length is %d characters' => 'La longitud mínima es de %d caracteres', + 'The password is required' => 'La contraseña es obligatoria', + 'This value must be an integer' => 'Este valor debe ser un entero', + 'The username must be unique' => 'El nombre de usuario debe ser único', + 'The user id is required' => 'El identificador del usuario es obligatorio', + 'Passwords don\'t match' => 'Las contraseñas no coinciden', + 'The confirmation is required' => 'La confirmación es obligatoria', + 'The project is required' => 'El proyecto es obligatorio', + 'The id is required' => 'El identificador es obligatorio', + 'The project id is required' => 'El identificador del proyecto es obligatorio', + 'The project name is required' => 'El nombre del proyecto es obligatorio', + 'The title is required' => 'El título es obligatorio', + 'Settings saved successfully.' => 'Parámetros guardados correctamente.', + 'Unable to save your settings.' => 'No se pueden guardar sus parámetros.', + 'Database optimization done.' => 'Optimización de la base de datos terminada.', + 'Your project has been created successfully.' => 'El proyecto ha sido creado correctamente.', + 'Unable to create your project.' => 'No se puede crear el proyecto.', + 'Project updated successfully.' => 'El proyecto ha sido actualizado correctamente.', + 'Unable to update this project.' => 'No se puede actualizar el proyecto.', + 'Unable to remove this project.' => 'No se puede suprimir este proyecto.', + 'Project removed successfully.' => 'El proyecto ha sido borrado correctamente.', + 'Project activated successfully.' => 'El proyecto ha sido activado correctamente.', + 'Unable to activate this project.' => 'No se puede activar el proyecto.', + 'Project disabled successfully.' => 'El proyecto ha sido desactivado correctamente.', + 'Unable to disable this project.' => 'No se puede desactivar el proyecto.', + 'Unable to open this task.' => 'No se puede abrir esta tarea.', + 'Task opened successfully.' => 'La tarea ha sido abierta correctamente.', + 'Unable to close this task.' => 'No se puede cerrar esta tarea.', + 'Task closed successfully.' => 'La tarea ha sido cerrada correctamente.', + 'Unable to update your task.' => 'No se puede modificar esta tarea.', + 'Task updated successfully.' => 'La tarea ha sido actualizada correctamente.', + 'Unable to create your task.' => 'No se puede crear esta tarea.', + 'Task created successfully.' => 'La tarea ha sido creada correctamente.', + 'User created successfully.' => 'El usuario ha sido creado correctamente.', + 'Unable to create your user.' => 'No se puede crear este usuario.', + 'User updated successfully.' => 'El usuario ha sido actualizado correctamente.', + 'User removed successfully.' => 'El usuario ha sido creado correctamente.', + 'Unable to remove this user.' => 'No se puede crear este usuario.', + 'Board updated successfully.' => 'El tablero ha sido actualizado correctamente.', + 'Ready' => 'Listo', + 'Backlog' => 'En espera', + 'Work in progress' => 'En curso', + 'Done' => 'Hecho', + 'Application version:' => 'Versión de la aplicación:', + 'Id' => 'Identificador', + 'Public link' => 'Vinculación pública', + 'Timezone' => 'Zona horaria', + 'Sorry, I didn\'t find this information in my database!' => 'Lo siento no he encontrado información en la base de datos!', + 'Page not found' => 'Página no encontrada', + 'Complexity' => 'Complejidad', + 'Task limit' => 'Número máximo de tareas', + 'Task count' => 'Conteo de tareas', + 'User' => 'Usuario', + 'Comments' => 'Comentarios', + 'Comment is required' => 'El comentario es obligatorio', + 'Comment added successfully.' => 'El comentario ha sido añadido correctamente.', + 'Unable to create your comment.' => 'No se puede crear este comentario.', + 'Due Date' => 'Fecha Límite', + 'Invalid date' => 'Fecha no válida', + 'Automatic actions' => 'Acciones automatizadas', + 'Your automatic action has been created successfully.' => 'La acción automatizada ha sido creada correctamente.', + 'Unable to create your automatic action.' => 'No se puede crear esta acción automatizada.', + 'Remove an action' => 'Suprimir una acción', + 'Unable to remove this action.' => 'No se puede suprimir esta accción.', + 'Action removed successfully.' => 'La acción ha sido borrada correctamente.', + 'Automatic actions for the project "%s"' => 'Acciones automatizadas para este proyecto « %s »', + 'Add an action' => 'Agregar una acción', + 'Event name' => 'Nombre del evento', + 'Action' => 'Acción', + 'Event' => 'Evento', + 'When the selected event occurs execute the corresponding action.' => 'Cuando tiene lugar el evento seleccionado, ejecutar la acción correspondiente.', + 'Next step' => 'Etapa siguiente', + 'Define action parameters' => 'Definición de los parametros de la acción', + 'Do you really want to remove this action: "%s"?' => '¿Realmente quiere suprimir esta acción « %s » ?', + 'Remove an automatic action' => 'Suprimir una acción automatizada', + 'Assign the task to a specific user' => 'Asignar una tarea a un usuario especifico', + 'Assign the task to the person who does the action' => 'Asignar la tarea al usuario que hace la acción', + 'Duplicate the task to another project' => 'Duplicar la tarea a otro proyecto', + 'Move a task to another column' => 'Mover una tarea a otra columna', + 'Task modification' => 'Modificación de una tarea', + 'Task creation' => 'Creación de una tarea', + 'Closing a task' => 'Cerrar una tarea', + 'Assign a color to a specific user' => 'Asignar un color a un usuario específico', + 'Position' => 'Posición', + 'Duplicate to project' => 'Duplicar a otro proyecto', + 'Duplicate' => 'Duplicar', + 'Link' => 'Enlace', + 'Comment updated successfully.' => 'El comentario ha sido actualizado correctamente.', + 'Unable to update your comment.' => 'No se puede actualizar este comentario.', + 'Remove a comment' => 'Suprimir un comentario', + 'Comment removed successfully.' => 'El comentario ha sido suprimido correctamente.', + 'Unable to remove this comment.' => 'No se puede suprimir este comentario.', + 'Do you really want to remove this comment?' => '¿Realmente quiere suprimir este comentario?', + 'Current password for the user "%s"' => 'Contraseña actual para el usuario: « %s »', + 'The current password is required' => 'La contraseña es obligatoria', + 'Wrong password' => 'contraseña incorrecta', + 'Unknown' => 'Desconocido', + 'Last logins' => 'Últimos ingresos', + 'Login date' => 'Fecha de ingreso', + 'Authentication method' => 'Método de autenticación', + 'IP address' => 'Dirección IP', + 'User agent' => 'Agente de usuario', + 'Persistent connections' => 'Conexión persistente', + 'No session.' => 'No existe sesión.', + 'Expiration date' => 'Fecha de expiración', + 'Remember Me' => 'Recuérdame', + 'Creation date' => 'Fecha de creación', + 'Everybody' => 'Todo el mundo', + 'Open' => 'Abierto', + 'Closed' => 'Cerrado', + 'Search' => 'Buscar', + 'Nothing found.' => 'Nada hallado.', + 'Due date' => 'Fecha Límite', + 'Description' => 'Descripción', + '%d comments' => '%d comentarios', + '%d comment' => '%d comentario', + 'Email address invalid' => 'Dirección de correo inválida', + 'Your external account is not linked anymore to your profile.' => 'Tu cuenta externa no está vinculada a tu perfil.', + 'Unable to unlink your external account.' => 'No se puede desvincular su cuenta externa.', + 'External authentication failed' => 'Error de autenticación externa', + 'Your external account is linked to your profile successfully.' => 'Su cuenta externa está vinculada a su perfil correctamente.', + 'Email' => 'Correo', + 'Task removed successfully.' => 'Tarea suprimida correctamente.', + 'Unable to remove this task.' => 'No pude suprimir esta tarea.', + 'Remove a task' => 'Borrar una tarea', + 'Do you really want to remove this task: "%s"?' => '¿Realmente quiere suprimir esta tarea: "%s"?', + 'Assign automatically a color based on a category' => 'Asignar un color de forma automática basándose en la categoría', + 'Assign automatically a category based on a color' => 'Asignar una categoría de forma automática basándose en el color', + 'Task creation or modification' => 'Creación o Edición de Tarea', + 'Category' => 'Categoría', + 'Category:' => 'Categoría:', + 'Categories' => 'Categorías', + 'Your category has been created successfully.' => 'Se ha creado tu categoría correctamente.', + 'This category has been updated successfully.' => 'Esta categoría se ha actualizado correctamente.', + 'Unable to update this category.' => 'No se puede actualizar esta categoría.', + 'Remove a category' => 'Suprimir una categoría', + 'Category removed successfully.' => 'Categoría suprimida correctamente.', + 'Unable to remove this category.' => 'No pude suprimir esta categoría.', + 'Category modification for the project "%s"' => 'Modificación de categoría para el proyecto "%s"', + 'Category Name' => 'Nombre de Categoría', + 'Add a new category' => 'Añadir una nueva categoría', + 'Do you really want to remove this category: "%s"?' => '¿Realmente quiere suprimir esta categoría: "%s"?', + 'All categories' => 'Todas las categorías', + 'No category' => 'Sin categoría', + 'The name is required' => 'El nombre es obligatorio', + 'Remove a file' => 'Borrar un fichero', + 'Unable to remove this file.' => 'No pude borrar este fichero.', + 'File removed successfully.' => 'Fichero borrado correctamente.', + 'Attach a document' => 'Adjuntar un documento', + 'Do you really want to remove this file: "%s"?' => '¿Realmente quiere borrar este fichero: "%s"?', + 'Attachments' => 'Adjuntos', + 'Edit the task' => 'Editar la tarea', + 'Add a comment' => 'Añadir un comentario', + 'Edit a comment' => 'Editar un comentario', + 'Summary' => 'Resumen', + 'Time tracking' => 'Control de tiempo', + 'Estimate:' => 'Estimado:', + 'Spent:' => 'Transcurrido:', + 'Do you really want to remove this sub-task?' => '¿Realmente quiere suprimir esta subtarea?', + 'Remaining:' => 'Quedando', + 'hours' => 'horas', + 'estimated' => 'estimado', + 'Sub-Tasks' => 'Sub-Tareas', + 'Add a sub-task' => 'Añadir una subtarea', + 'Original estimate' => 'Horas Presupuestadas', + 'Create another sub-task' => 'Crear otra subtarea', + 'Time spent' => 'Horas Ejecutadas', + 'Edit a sub-task' => 'Editar una subtarea', + 'Remove a sub-task' => 'Suprimir una subtarea', + 'The time must be a numeric value' => 'El tiempo debe de ser un valor numérico', + 'Todo' => 'Por hacer', + 'In progress' => 'En progreso', + 'Sub-task removed successfully.' => 'Sub-tarea suprimida correctamente.', + 'Unable to remove this sub-task.' => 'No pude suprimir esta subtarea.', + 'Sub-task updated successfully.' => 'Sub-tarea actualizada correctamente.', + 'Unable to update your sub-task.' => 'No pude actualizar tu subtarea.', + 'Unable to create your sub-task.' => 'No pude crear tu subtarea.', + 'Maximum size: ' => 'Tamaño máximo', + 'Display another project' => 'Mostrar otro proyecto', + 'Created by %s' => 'Creado por %s', + 'Tasks Export' => 'Exportar tareas', + 'Start Date' => 'Fecha de Inicio', + 'Execute' => 'Ejecutar', + 'Task Id' => 'ID de tarea', + 'Creator' => 'Creador', + 'Modification date' => 'Fecha de modificación', + 'Completion date' => 'Fecha de Fin', + 'Clone' => 'Clonar', + 'Project cloned successfully.' => 'Proyecto clonado correctamente', + 'Unable to clone this project.' => 'Impsible clonar proyecto', + 'Enable email notifications' => 'Habilitar notificaciones por correo electrónico', + 'Task position:' => 'Posición de la tarea', + 'The task #%d has been opened.' => 'La tarea #%d ha sido abierta', + 'The task #%d has been closed.' => 'La tarea #%d ha sido cerrada', + 'Sub-task updated' => 'Subtarea actualizada', + 'Title:' => 'Título:', + 'Status:' => 'Estado:', + 'Assignee:' => 'Asignada a:', + 'Time tracking:' => 'Control de tiempo:', + 'New sub-task' => 'Nueva subtarea', + 'New attachment added "%s"' => 'Nuevo adjunto agregado "%s"', + 'New comment posted by %s' => 'Nuevo comentario agregado por %s', + 'New comment' => 'Nuevo comentario', + 'Comment updated' => 'Comentario actualizado', + 'New subtask' => 'Nueva subtarea', + 'I only want to receive notifications for these projects:' => 'Quiero recibir notificaciones sólo de estos proyectos:', + 'view the task on Kanboard' => 'ver la tarea en Kanboard', + 'Public access' => 'Acceso público', + 'Disable public access' => 'Desactivar acceso público', + 'Enable public access' => 'Activar acceso público', + 'Public access disabled' => 'Acceso público desactivado', + 'Move the task to another project' => 'Mover la tarea a otro proyecto', + 'Move to project' => 'Mover a otro proyecto', + 'Do you really want to duplicate this task?' => '¿Realmente quiere duplicar esta tarea?', + 'Duplicate a task' => 'Duplicar una tarea', + 'External accounts' => 'Cuentas externas', + 'Account type' => 'Tipo de Cuenta', + 'Local' => 'Local', + 'Remote' => 'Remota', + 'Enabled' => 'Activada', + 'Disabled' => 'Deactivada', + 'Login:' => 'Login:', + 'Full Name:' => 'Nombre completo:', + 'Email:' => 'Correo electrónico:', + 'Notifications:' => 'Notificaciones:', + 'Notifications' => 'Notificaciones', + 'Account type:' => 'Tipo de Cuenta:', + 'Edit profile' => 'Editar perfil', + 'Change password' => 'Cambiar contraseña', + 'Password modification' => 'Modificacion de contraseña', + 'External authentications' => 'Autenticación externa', + 'Never connected.' => 'Nunca se ha conectado.', + 'No external authentication enabled.' => 'Sin autenticación externa activa.', + 'Password modified successfully.' => 'Contraseña cambiada correctamente.', + 'Unable to change the password.' => 'No pude cambiar la contraseña.', + 'Change category' => 'Cambiar categoría', + '%s updated the task %s' => '%s actualizó la tarea %s', + '%s opened the task %s' => '%s abrió la tarea %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s movió la tarea %s a la posición #%d de la columna "%s"', + '%s moved the task %s to the column "%s"' => '%s movió la tarea %s a la columna "%s"', + '%s created the task %s' => '%s creó la tarea %s', + '%s closed the task %s' => '%s cerró la tarea %s', + '%s created a subtask for the task %s' => '%s creó una subtarea para la tarea %s', + '%s updated a subtask for the task %s' => '%s actualizó una subtarea para la tarea %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Asignada a %s con una estimación de %s/%sh', + 'Not assigned, estimate of %sh' => 'No asignada, se estima en %sh', + '%s updated a comment on the task %s' => '%s actualizó un comentario de la tarea %s', + '%s commented the task %s' => '%s comentó la tarea %s', + '%s\'s activity' => 'Actividad de %s', + 'RSS feed' => 'Fichero RSS', + '%s updated a comment on the task #%d' => '%s actualizó un comentario de la tarea #%d', + '%s commented on the task #%d' => '%s comentó la tarea #%d', + '%s updated a subtask for the task #%d' => '%s actualizó una subtarea de la tarea #%d', + '%s created a subtask for the task #%d' => '%s creó una subtarea de la tarea #%d', + '%s updated the task #%d' => '%s actualizó la tarea #%d', + '%s created the task #%d' => '%s creó la tarea #%d', + '%s closed the task #%d' => '%s cerró la tarea #%d', + '%s opened the task #%d' => '%s abrió la tarea #%d', + 'Activity' => 'Actividad', + 'Default values are "%s"' => 'Los valores por defecto son "%s"', + 'Default columns for new projects (Comma-separated)' => 'Columnas por defecto de los nuevos proyectos (Separadas mediante comas)', + 'Task assignee change' => 'Cambiar persona asignada a la tarea', + '%s changed the assignee of the task #%d to %s' => '%s cambió al asignado de la tarea #%d a %s', + '%s changed the assignee of the task %s to %s' => '%s cambió la persona asignada de la tarea de %s a %s', + 'New password for the user "%s"' => 'Nueva contraseña para el usuario "%s"', + 'Choose an event' => 'Escoja un evento', + 'Create a task from an external provider' => 'Crear una tarea a partir de un proveedor externo', + 'Change the assignee based on an external username' => 'Cambiar la asignación basado en un nombre de usuario externo', + 'Change the category based on an external label' => 'Cambiar la categoría basado en una etiqueta externa', + 'Reference' => 'Referencia', + 'Label' => 'Etiqueta', + 'Database' => 'Base de Datos', + 'About' => 'Acerca de', + 'Database driver:' => 'Driver de la base de datos', + 'Board settings' => 'Configuraciones del Tablero', + 'Webhook settings' => 'Configuraciones del Webhook', + 'Reset token' => 'Resetear token', + 'API endpoint:' => 'Punto final del API', + 'Refresh interval for personal board' => 'Intervalo de refrescamiento del tablero privado', + 'Refresh interval for public board' => 'Intervalo de refrescamiento del tablero público', + 'Task highlight period' => 'Periodo del realce de la tarea', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Periodo (en segundos) para considerar que una tarea fué modificada recientemente (0 para deshabilitar, 2 días por defecto)', + 'Frequency in second (60 seconds by default)' => 'Frecuencia en segundos (60 segundos por defecto)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frecuencia en segundos (0 para deshabilitar esta característica, 10 segundos por defecto)', + 'Application URL' => 'URL de la aplicación', + 'Token regenerated.' => 'Token regenerado', + 'Date format' => 'Formato de la fecha', + 'ISO format is always accepted, example: "%s" and "%s"' => 'El formato ISO siempre es aceptado, ejemplo: "%s" y "%s"', + 'New personal project' => 'Nuevo proyecto privado', + 'This project is personal' => 'Este proyecto es privado', + 'Add' => 'Añadir', + 'Start date' => 'Fecha de inicio', + 'Time estimated' => 'Horas Presupuestadas', + 'There is nothing assigned to you.' => 'Esto no le está asignado', + 'My tasks' => 'Mis tareas', + 'Activity stream' => 'Flujo de actividad', + 'Dashboard' => 'Tablero', + 'Confirmation' => 'Confirmación', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Create a comment from an external provider' => 'Crear un comentario a partir de un proveedor externo', + 'Project management' => 'Gestión de proyectos', + 'Columns' => 'Columnas', + 'Task' => 'Tarea', + 'Percentage' => 'Porcentaje', + 'Number of tasks' => 'Número de tareas', + 'Task distribution' => 'Distribución de tareas', + 'Analytics' => 'Analítica', + 'Subtask' => 'Subtarea', + 'User repartition' => 'Repartición de usuarios', + 'Clone this project' => 'Clonar este proyecto', + 'Column removed successfully.' => 'Columna removida correctamente', + 'Not enough data to show the graph.' => 'No hay suficiente información para mostrar el gráfico', + 'Previous' => 'Anterior', + 'The id must be an integer' => 'El id debe ser un entero', + 'The project id must be an integer' => 'El id del proyecto debe ser un entero', + 'The status must be an integer' => 'El estado debe ser un entero', + 'The subtask id is required' => 'El id de la subtarea es requerido', + 'The subtask id must be an integer' => 'El id de la subtarea debe ser un entero', + 'The task id is required' => 'El id de la tarea es requerido', + 'The task id must be an integer' => 'El id de la tarea debe ser un entero', + 'The user id must be an integer' => 'El id del usuario debe ser un entero', + 'This value is required' => 'El valor es requerido', + 'This value must be numeric' => 'Este valor debe ser numérico', + 'Unable to create this task.' => 'Imposible crear esta tarea', + 'Cumulative flow diagram' => 'Diagrama de flujo acumulativo', + 'Daily project summary' => 'Resumen diario del proyecto', + 'Daily project summary export' => 'Exportar sumario diario del proyecto', + 'Exports' => 'Exportar', + 'This export contains the number of tasks per column grouped per day.' => 'Esta exportación contiene el número de tereas por columna agrupada por día', + 'Active swimlanes' => 'Carriles activos', + 'Add a new swimlane' => 'Añadir nuevo carril', + 'Default swimlane' => 'Carril por defecto', + 'Do you really want to remove this swimlane: "%s"?' => '¿Realmente quiere remover este carril: "%s"?', + 'Inactive swimlanes' => 'Carriles inactivos', + 'Remove a swimlane' => 'Remover un carril', + 'Swimlane modification for the project "%s"' => 'Modificación del carril para el proyecto "%s"', + 'Swimlane removed successfully.' => 'Carril removido correctamente', + 'Swimlanes' => 'Carriles', + 'Swimlane updated successfully.' => 'Carril actualizado correctamente', + 'Unable to remove this swimlane.' => 'Imposible remover este carril', + 'Unable to update this swimlane.' => 'Imposible actualizar este carril', + 'Your swimlane has been created successfully.' => 'Su carril ha sido creado correctamente', + 'Example: "Bug, Feature Request, Improvement"' => 'Ejemplo: "Error, Solicitud de característica, Mejora"', + 'Default categories for new projects (Comma-separated)' => 'Categorías por defecto de los nuevos proyectos (Separadas mediante comas)', + 'Integrations' => 'Integraciones', + 'Integration with third-party services' => 'Integraciones para servicios de terceros', + 'Subtask Id' => 'ID de Subtareas', + 'Subtasks' => 'Subtareas', + 'Subtasks Export' => 'Exportar Subtareas', + 'Task Title' => 'Título de la tarea', + 'Untitled' => 'Sin título', + 'Application default' => 'Predefinido de la aplicación', + 'Language:' => 'Idioma', + 'Timezone:' => 'Zona horaria', + 'All columns' => 'Todas las columnas', + 'Next' => 'Siguiente', + '#%d' => '#%d', + 'All swimlanes' => 'Todos los carriles', + 'All colors' => 'Todos los colores', + 'Moved to column %s' => 'Movido a columna %s', + 'User dashboard' => 'Tablero del usuario', + 'Allow only one subtask in progress at the same time for a user' => 'Permitir únicamente una subtarea en progreso al mismo tiempo para un usuario', + 'Edit column "%s"' => 'Editar columna "%s"', + 'Select the new status of the subtask: "%s"' => 'Seleccione el nuevo estado de la subtarea: "%s"', + 'Subtask timesheet' => 'Hoja de tiempos de la subtarea', + 'There is nothing to show.' => 'No hay nada que mostrar.', + 'Time Tracking' => 'Control de Tiempo', + 'You already have one subtask in progress' => 'Ya tiene una subtarea en progreso', + 'Which parts of the project do you want to duplicate?' => '¿Que partes del proyecto quiere duplicar?', + 'Disallow login form' => 'Deshabilitar el formulario de inicio de sesión', + 'Start' => 'Inicio', + 'End' => 'Fin', + 'Task age in days' => 'Edad de la tarea en días', + 'Days in this column' => 'Días en esta columna', + '%dd' => '%dd', + 'Add a new link' => 'Añadir nuevo vínculo', + 'Do you really want to remove this link: "%s"?' => '¿Realmente quiere suprimir este vínculo: "%s"?', + 'Do you really want to remove this link with task #%d?' => '¿Realmente quiere suprimir este vínculo con la tarea #%d?', + 'Field required' => 'Campo requerido', + 'Link added successfully.' => 'Vínculo añadido correctamente', + 'Link updated successfully.' => 'Vínculo actualizado correctamente', + 'Link removed successfully.' => 'Vínculo suprimido correctamente', + 'Link labels' => 'Etiquetas de vínculos', + 'Link modification' => 'Modificar vínculo', + 'Opposite label' => 'Etiquetas opuestas', + 'Remove a link' => 'Suprimir un vínculo', + 'The labels must be different' => 'Las etiquetas deben ser diferentes', + 'There is no link.' => 'Ahí no hay un vínculo', + 'This label must be unique' => 'Esta etiqueta debe ser única', + 'Unable to create your link.' => 'No se puede crear su vínculo', + 'Unable to update your link.' => 'No se puede actualizar su vínculo', + 'Unable to remove this link.' => 'No se puede suprimir su vínculo', + 'relates to' => 'se relaciona con', + 'blocks' => 'bloqueos', + 'is blocked by' => 'está bloqueado por', + 'duplicates' => 'duplicados', + 'is duplicated by' => 'es duplicado por', + 'is a child of' => 'es hijo de', + 'is a parent of' => 'es padre de', + 'targets milestone' => 'metas del hito', + 'is a milestone of' => 'es un hito de', + 'fixes' => 'arregla', + 'is fixed by' => 'arreglado por', + 'This task' => 'Esta tarea', + '<1h' => '<1h', + '%dh' => '%dh', + 'Expand tasks' => 'Expandir tareas', + 'Collapse tasks' => 'Colapsar tareas', + 'Expand/collapse tasks' => 'Expandir/colapsar tareas', + 'Close dialog box' => 'Cerrar caja de diálogo', + 'Submit a form' => 'Enviar un formulario', + 'Board view' => 'Vista de tablero', + 'Keyboard shortcuts' => 'Atajos del teclado', + 'Open board switcher' => 'Conmutador de tablero abierto', + 'Application' => 'Aplicación', + 'Compact view' => 'Vista compacta', + 'Horizontal scrolling' => 'Desplazamiento horizontal', + 'Compact/wide view' => 'Vista compacta/amplia', + 'Currency' => 'Moneda', + 'Personal project' => 'Proyecto privado', + 'AUD - Australian Dollar' => 'AUD - Dólar australiano', + 'CAD - Canadian Dollar' => 'CAD - Dólar canadiense', + 'CHF - Swiss Francs' => 'CHF - Franco suizo', + 'Custom Stylesheet' => 'Hoja de estilo personalizada', + 'EUR - Euro' => 'EUR - Euro', + 'GBP - British Pound' => 'GBP - Libra británica', + 'INR - Indian Rupee' => 'INR - Rupia india', + 'JPY - Japanese Yen' => 'JPY - Yen japonés', + 'NZD - New Zealand Dollar' => 'NZD - Dólar de Nueva Zelanda', + 'PEN - Peruvian Sol' => 'PEN - Sol peruano', + 'RSD - Serbian dinar' => 'RSD - Dinar serbio', + 'CNY - Chinese Yuan' => 'CNY - Yuan chino', + 'USD - US Dollar' => 'USD - Dólar estadounidense', + 'VES - Venezuelan Bolívar' => 'VES - Bolívar venezolano', + 'Destination column' => 'Columna destino', + 'Move the task to another column when assigned to a user' => 'Mover la tarea a otra columna cuando sea asignada a un usuario', + 'Move the task to another column when assignee is cleared' => 'Mover la tarea a otra columna cuando se elimine la persona asignada', + 'Source column' => 'Columna de origen', + 'Transitions' => 'Transiciones', + 'Executer' => 'Ejecutor', + 'Time spent in the column' => 'Horas Ejecutadas en la columna', + 'Task transitions' => 'Transiciones de las tareas', + 'Task transitions export' => 'Exportar transiciones de las tareas', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Este reporte contiene todos los movimientos de columna por cada tarea con la fecha, el usuario y el tiempo transcurrido para cada transición', + 'Currency rates' => 'Tipos de cambio', + 'Rate' => 'Tarifa', + 'Change reference currency' => 'Cambiar moneda de referencia', + 'Reference currency' => 'Moneda de referencia', + 'The currency rate has been added successfully.' => 'El tipo de cambio ha sido añadido correctamente.', + 'Unable to add this currency rate.' => 'No se puede añadir este tipo de cambio.', + 'Webhook URL' => 'URL del Webhook', + '%s removed the assignee of the task %s' => '%s eliminó al asignado de la tarea %s', + 'Information' => 'Información', + 'Check two factor authentication code' => 'Verificar el código de autenticación de dos factores', + 'The two factor authentication code is not valid.' => 'El código de autenticación de dos factores no es válido', + 'The two factor authentication code is valid.' => 'El código de autenticación de dos factores es válido', + 'Code' => 'Código', + 'Two factor authentication' => 'Autenticación de dos factores', + 'This QR code contains the key URI: ' => 'Este código QR contiene la clave URI: ', + 'Check my code' => 'Verificar mi código', + 'Secret key: ' => 'Clave secreta: ', + 'Test your device' => 'Pruebe su dispositivo', + 'Assign a color when the task is moved to a specific column' => 'Asignar un color cuando la tarea se mueve a una columna específica', + '%s via Kanboard' => '%s vía Kanboard', + 'Burndown chart' => 'Gráfico de Tareas Pendientes', + 'This chart show the task complexity over the time (Work Remaining).' => 'Este gráfico muestra la complejidad de la tarea durante el tiempo (Trabajo restante)', + 'Screenshot taken %s' => 'Captura de pantalla tomada %s', + 'Add a screenshot' => 'Añadir una captura de pantalla', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Tomar una captura de pantalla y presione CTRL+V o ⌘+V para pegar aquí.', + 'Screenshot uploaded successfully.' => 'Captura de pantalla subida correctamente', + 'SEK - Swedish Krona' => 'SEK - Corona sueca', + 'Identifier' => 'Identificador', + 'Disable two factor authentication' => 'Deshabilitar la autenticación de dos factores', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => '¿Realmente desea desactivar la autenticación de dos factores para este usuario: "%s"?', + 'Edit link' => 'Editar enlace', + 'Start to type task title...' => 'Comenzar a escribir el título de la tarea ...', + 'A task cannot be linked to itself' => 'Una tarea no se puede vincular a sí misma', + 'The exact same link already exists' => 'Ya existe un enlace idéntico', + 'Recurrent task is scheduled to be generated' => 'La tarea periódica está programada para ser generada', + 'Score' => 'Puntuación', + 'The identifier must be unique' => 'El identificador debe ser único', + 'This linked task id doesn\'t exists' => 'Este ID de la tarea enlazada no existe', + 'This value must be alphanumeric' => 'Este valor debe ser alfanumérico', + 'Edit recurrence' => 'Editar recurrencia', + 'Generate recurrent task' => 'Generar tarea recurrente', + 'Trigger to generate recurrent task' => 'Disparador para generar tarea recurrente', + 'Factor to calculate new due date' => 'Factor para calcular la nueva fecha de vencimiento', + 'Timeframe to calculate new due date' => 'Plazo para calcular la nueva fecha de vencimiento', + 'Base date to calculate new due date' => 'Fecha base para calcular la nueva fecha de vencimiento', + 'Action date' => 'Fecha de acción', + 'Base date to calculate new due date: ' => 'Fecha base para calcular la nueva fecha de vencimiento: ', + 'This task has created this child task: ' => 'Esta tarea ha creado esta tarea hija: ', + 'Day(s)' => 'Día(s)', + 'Existing due date' => 'Fecha de vencimiento existente', + 'Factor to calculate new due date: ' => 'Factor para calcular la nueva fecha de vencimiento: ', + 'Month(s)' => 'Mes(es)', + 'This task has been created by: ' => 'Esta tarea ha sido creada por: ', + 'Recurrent task has been generated:' => 'Se ha generado una tarea recurrente:', + 'Timeframe to calculate new due date: ' => 'Plazo para calcular la nueva fecha de vencimiento: ', + 'Trigger to generate recurrent task: ' => 'Disparador para generar la tarea recurrente: ', + 'When task is closed' => 'Cuando la tarea está cerrada', + 'When task is moved from first column' => 'Cuando se mueve la tarea desde la primera columna', + 'When task is moved to last column' => 'Cuando la tarea se mueve a la última columna', + 'Year(s)' => 'Año(s)', + 'Project settings' => 'Configuración del proyecto', + 'Automatically update the start date' => 'Actualizar automáticamente la fecha de inicio', + 'iCal feed' => 'Alimentador de iCal', + 'Preferences' => 'Preferencias', + 'Security' => 'Seguridad', + 'Two factor authentication disabled' => 'Se ha inhabilitado la autenticación de dos factores.', + 'Two factor authentication enabled' => 'Autenticación de dos factores habilitada', + 'Unable to update this user.' => 'No se puede actualizar este usuario.', + 'There is no user management for personal projects.' => 'No hay gestión de usuarios para proyectos privados.', + 'User that will receive the email' => 'Usuario que recibirá el correo electrónico', + 'Email subject' => 'Asunto del email', + 'Date' => 'Fecha', + 'Add a comment log when moving the task between columns' => 'Añadir un registro en los comentarios al mover la tarea entre columnas', + 'Move the task to another column when the category is changed' => 'Mover la tarea a otra columna cuando se cambia la categoría', + 'Send a task by email to someone' => 'Enviar una tarea por correo electrónico a alguien', + 'Reopen a task' => 'Reabrir una tarea', + 'Notification' => 'Notificación', + '%s moved the task #%d to the first swimlane' => '%s movió la tarea #%d al primer carril', + 'Swimlane' => 'Carril', + '%s moved the task %s to the first swimlane' => '%s movió la tarea %s al primer carril', + '%s moved the task %s to the swimlane "%s"' => '%s trasladó la tarea %s al carril "%s"', + 'This report contains all subtasks information for the given date range.' => 'Este informe contiene toda la información de las subtareas para el intervalo de fechas especificado.', + 'This report contains all tasks information for the given date range.' => 'Este informe contiene la información de todas las tareas para el intervalo de fechas dado.', + 'Project activities for %s' => 'Actividades del proyecto para %s', + 'view the board on Kanboard' => 'ver el tablero en Kanboard', + 'The task has been moved to the first swimlane' => 'La tarea se ha movido al primer carril', + 'The task has been moved to another swimlane:' => 'La tarea se ha trasladado a otro carril:', + 'New title: %s' => 'Nuevo título: %s', + 'The task is not assigned anymore' => 'La tarea ya no está asignada', + 'New assignee: %s' => 'Nuevo asignado: %s', + 'There is no category now' => 'No hay categoría ahora', + 'New category: %s' => 'Nueva categoría: %s', + 'New color: %s' => 'Nuevo color: %s', + 'New complexity: %d' => 'Nueva complejidad: %d', + 'The due date has been removed' => 'Se ha eliminado la fecha de vencimiento', + 'There is no description anymore' => 'Ya no hay ninguna descripción', + 'Recurrence settings has been modified' => 'Los parámetros de recurrencia se han modificado', + 'Time spent changed: %sh' => 'Tiempo ejecutado cambiado: %sh', + 'Time estimated changed: %sh' => 'Horas Presupuestadas cambiado: %sh', + 'The field "%s" has been updated' => 'Se ha actualizado el campo "%s"', + 'The description has been modified:' => 'La descripción ha sido modificada:', + 'Do you really want to close the task "%s" as well as all subtasks?' => '¿Realmente desea cerrar la tarea "%s", así como todas sus subtareas?', + 'I want to receive notifications for:' => 'Quiero recibir notificaciones para:', + 'All tasks' => 'Todas las tareas', + 'Only for tasks assigned to me' => 'Sólo para tareas asignadas a mí', + 'Only for tasks created by me' => 'Sólo para tareas creadas por mí', + 'Only for tasks created by me and tasks assigned to me' => 'Sólo para las tareas creadas por mí y asignadas a mí', + '%%Y-%%m-%%d' => '%%Y-%%m-%%d', + 'Total for all columns' => 'Total para todas las columnas', + 'You need at least 2 days of data to show the chart.' => 'Necesita al menos 2 días de datos para mostrar el gráfico.', + '<15m' => '<15m', + '<30m' => '<30m', + 'Stop timer' => 'Detener temporizador', + 'Start timer' => 'Iniciar el temporizador', + 'My activity stream' => 'Mi flujo de actividad', + 'Search tasks' => 'Buscar tareas', + 'Reset filters' => 'Restablecer filtros', + 'My tasks due tomorrow' => 'Mis tareas para mañana', + 'Tasks due today' => 'Tareas pendientes hoy', + 'Tasks due tomorrow' => 'Tareas para mañana', + 'Tasks due yesterday' => 'Tareas vencidas ayer', + 'Closed tasks' => 'Tareas cerradas', + 'Open tasks' => 'Tareas abiertas', + 'Not assigned' => 'No asignado', + 'View advanced search syntax' => 'Ver sintaxis de búsqueda avanzada', + 'Overview' => 'Visión general', + 'Board/Calendar/List view' => 'Vista de Tablero/Calendario/Lista', + 'Switch to the board view' => 'Cambiar a la vista de tablero', + 'Switch to the list view' => 'Cambiar a la vista de lista', + 'Go to the search/filter box' => 'Ir a la caja de búsqueda/filtro', + 'There is no activity yet.' => 'No hay actividad todavía.', + 'No tasks found.' => 'No se han encontrado tareas.', + 'Keyboard shortcut: "%s"' => 'Atajo de teclado: "%s"', + 'List' => 'Lista', + 'Filter' => 'Filtrar', + 'Advanced search' => 'Búsqueda Avanzada', + 'Example of query: ' => 'Ejemplo de consulta: ', + 'Search by project: ' => 'Buscar por proyecto: ', + 'Search by column: ' => 'Buscar por columna: ', + 'Search by assignee: ' => 'Buscar por asignado:', + 'Search by color: ' => 'Buscar por color: ', + 'Search by category: ' => 'Buscar por categoría: ', + 'Search by description: ' => 'Buscar por descripción: ', + 'Search by due date: ' => 'Buscar por fecha de vencimiento: ', + 'Average time spent in each column' => 'Tiempo de permanencia promedio en cada columna', + 'Average time spent' => 'Tiempo ejecutado promedio', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Este gráfico muestra el tiempo promedio invertido en cada columna para las últimas %d tareas.', + 'Average Lead and Cycle time' => 'Tiempo de Espera y de Ciclo promedio', + 'Average lead time: ' => 'Tiempo de espera promedio: ', + 'Average cycle time: ' => 'Tiempo del ciclo promedio: ', + 'Cycle Time' => 'Tiempo del Ciclo', + 'Lead Time' => 'Tiempo de Espera', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Este gráfico muestra el tiempo promedio de espera y de ciclo para las últimas %d tareas.', + 'Average time into each column' => 'Tiempo promedio en cada columna', + 'Lead and cycle time' => 'Tiempo de espera y de ciclo', + 'Lead time: ' => 'Tiempo de Espera: ', + 'Cycle time: ' => 'Tiempo del Ciclo: ', + 'Time spent in each column' => 'Tiempo empleado en cada columna', + 'The lead time is the duration between the task creation and the completion.' => 'El tiempo de espera es la duración entre la creación de la tarea y la finalización.', + 'The cycle time is the duration between the start date and the completion.' => 'El tiempo de ciclo es la duración entre la fecha de inicio y la finalización.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Si la tarea no está cerrada, se utiliza la hora actual en lugar de la fecha de finalización.', + 'Set the start date automatically' => 'Establecer automáticamente la fecha de inicio', + 'Edit Authentication' => 'Editar autenticación', + 'Remote user' => 'Usuario remoto', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Los usuarios remotos no almacenan su contraseña en la base de datos Kanboard, ejemplos: cuentas LDAP, Google y Github.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Si marca la casilla "Deshabilitar el formulario de inicio de sesión", las credenciales ingresadas en el formulario de inicio de sesión serán ignoradas.', + 'Default task color' => 'Color predeterminado de la tarea', + 'This feature does not work with all browsers.' => 'Esta función no funciona con todos los navegadores.', + 'There is no destination project available.' => 'No hay proyecto de destino disponible.', + 'Trigger automatically subtask time tracking' => 'Activar automáticamente el seguimiento de tiempo de la subtarea', + 'Include closed tasks in the cumulative flow diagram' => 'Incluir tareas cerradas en el diagrama de flujo acumulativo', + 'Current swimlane: %s' => 'Carril actual: %s', + 'Current column: %s' => 'Columna actual: %s', + 'Current category: %s' => 'Categoría actual: %s', + 'no category' => 'sin categoria', + 'Current assignee: %s' => 'Asignado actual: %s', + 'not assigned' => 'no asignado', + 'Author:' => 'Autor:', + 'contributors' => 'colaboradores', + 'License:' => 'Licencia:', + 'License' => 'Licencia', + 'Enter the text below' => 'Introduzca el texto a continuación', + 'Start date:' => 'Fecha de inicio:', + 'Due date:' => 'Fecha de vencimiento:', + 'People who are project managers' => 'Personas que son gerentes de proyecto', + 'People who are project members' => 'Personas que son miembros del proyecto', + 'NOK - Norwegian Krone' => 'NOK - Corona Noruega', + 'Show this column' => 'Mostrar esta columna', + 'Hide this column' => 'Ocultar esta columna', + 'End date' => 'Fecha final', + 'Users overview' => 'Visión general de los usuarios', + 'Members' => 'Miembros', + 'Shared project' => 'Proyecto compartido', + 'Project managers' => 'Gerentes de Proyecto', + 'Projects list' => 'Lista de proyectos', + 'End date:' => 'Fecha final:', + 'Change task color when using a specific task link' => 'Cambiar el color de la tarea cuando se utiliza un enlace de tarea específico', + 'Task link creation or modification' => 'Creación o modificación de enlaces de tareas', + 'Milestone' => 'Hito', + 'Reset the search/filter box' => 'Restablecer la caja de búsqueda/filtro', + 'Documentation' => 'Documentación', + 'Author' => 'Autor', + 'Version' => 'Versión', + 'Plugins' => 'Plugins', + 'There is no plugin loaded.' => 'No hay ningún plugin cargado.', + 'My notifications' => 'Mis Notificaciones', + 'Custom filters' => 'Filtros personalizados', + 'Your custom filter has been created successfully.' => 'Su filtro personalizado se ha creado correctamente.', + 'Unable to create your custom filter.' => 'No se puede crear el filtro personalizado.', + 'Custom filter removed successfully.' => 'Filtro personalizado eliminado correctamente.', + 'Unable to remove this custom filter.' => 'No se puede quitar este filtro personalizado.', + 'Edit custom filter' => 'Editar filtro personalizado', + 'Your custom filter has been updated successfully.' => 'Su filtro personalizado se ha actualizado correctamente.', + 'Unable to update custom filter.' => 'No se puede actualizar el filtro personalizado.', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Nuevo archivo adjunto en la tarea #%d: %s', + 'New comment on task #%d' => 'Nuevo comentario sobre la tarea #%d', + 'Comment updated on task #%d' => 'Comentario actualizado en la tarea #%d', + 'New subtask on task #%d' => 'Nueva subtarea en la tarea #%d', + 'Subtask updated on task #%d' => 'Subtarea actualizada en la tarea #%d', + 'New task #%d: %s' => 'Nueva tarea #%d: %s', + 'Task updated #%d' => 'Tarea actualizada #%d', + 'Task #%d closed' => 'Tarea #%d cerrada', + 'Task #%d opened' => 'Tarea #%d abierta', + 'Column changed for task #%d' => 'Columna modificada para la tarea #%d', + 'New position for task #%d' => 'Nueva posición para la tarea #%d', + 'Swimlane changed for task #%d' => 'Carril cambiado para la tarea #%d', + 'Assignee changed on task #%d' => 'Asignado cambiado en la tarea #%d', + '%d overdue tasks' => '%d tareas pendientes', + 'No notification.' => 'Sin notificación.', + 'Mark all as read' => 'marcar todo como leido', + 'Mark as read' => 'Marcar como leído', + 'Total number of tasks in this column across all swimlanes' => 'Número total de tareas en esta columna contando todos los carriles', + 'Collapse swimlane' => 'Colapsar el carril', + 'Expand swimlane' => 'Expandir el carril', + 'Add a new filter' => 'Añadir un nuevo filtro', + 'Share with all project members' => 'Compartir con todos los miembros del proyecto', + 'Shared' => 'Compartido', + 'Owner' => 'Propietario', + 'Unread notifications' => 'Notificaciones no leídas', + 'Notification methods:' => 'Métodos de notificación:', + 'Unable to read your file' => 'No se puede leer el archivo', + '%d task(s) have been imported successfully.' => '%d tarea(s) se han importado correctamente.', + 'Nothing has been imported!' => '¡Nada se ha importado!', + 'Import users from CSV file' => 'Importar usuarios del archivo CSV', + '%d user(s) have been imported successfully.' => '%d usuario(s) se han importado correctamente.', + 'Comma' => 'Coma', + 'Semi-colon' => 'Punto y coma', + 'Tab' => 'Tabulación', + 'Vertical bar' => 'Barra vertical', + 'Double Quote' => 'Comillas dobles', + 'Single Quote' => 'Comillas simples', + '%s attached a file to the task #%d' => '%s adjuntó un archivo a la tarea #%d', + 'There is no column or swimlane activated in your project!' => '¡No hay columna ni carril activado en tu proyecto!', + 'Append filter (instead of replacement)' => 'Agregar el filtro (en lugar del reemplazo)', + 'Append/Replace' => 'Añadir/Reemplazar', + 'Append' => 'Adjuntar', + 'Replace' => 'Reemplazar', + 'Import' => 'Importar', + 'Change sorting' => 'Cambio de orden', + 'Tasks Importation' => 'Importación de Tareas', + 'Delimiter' => 'Delimitador', + 'Enclosure' => 'Contenedor', + 'CSV File' => 'Archivo CSV', + 'Instructions' => 'Instrucciones', + 'Your file must use the predefined CSV format' => 'Su archivo debe utilizar el formato CSV predefinido', + 'Your file must be encoded in UTF-8' => 'Su archivo debe estar codificado en UTF-8', + 'The first row must be the header' => 'La primera fila debe ser el encabezado', + 'Duplicates are not verified for you' => 'Los duplicados no se verifican', + 'The due date must use the ISO format: YYYY-MM-DD' => 'La fecha de vencimiento debe utilizar el formato ISO: AAAA-MM-DD', + 'Download CSV template' => 'Descargar plantilla CSV', + 'No external integration registered.' => 'No se ha registrado ninguna integración externa.', + 'Duplicates are not imported' => 'Los duplicados no se importan', + 'Usernames must be lowercase and unique' => 'Los nombres de usuario deben estar en minúsculas y ser únicos', + 'Passwords will be encrypted if present' => 'Las contraseñas se cifrarán si están presentes', + '%s attached a new file to the task %s' => '%s adjunto un nuevo archivo a la tarea %s', + 'Link type' => 'Tipo de enlace', + 'Assign automatically a category based on a link' => 'Asignar automáticamente una categoría basada en un enlace', + 'BAM - Konvertible Mark' => 'BAM - Marco Convertible', + 'Assignee Username' => 'Usuario asignado', + 'Assignee Name' => 'Nombre del asignado', + 'Groups' => 'Grupos', + 'Members of %s' => 'Miembros de %s', + 'New group' => 'Nuevo grupo', + 'Group created successfully.' => 'Grupo creado correctamente.', + 'Unable to create your group.' => 'No se puede crear su grupo.', + 'Edit group' => 'Editar grupo', + 'Group updated successfully.' => 'Grupo actualizado correctamente.', + 'Unable to update your group.' => 'No se puede actualizar su grupo.', + 'Add group member to "%s"' => 'Agregue el miembro del grupo a "%s"', + 'Group member added successfully.' => 'Miembro del grupo agregado correctamente.', + 'Unable to add group member.' => 'No se puede agregar el miembro del grupo.', + 'Remove user from group "%s"' => 'Quitar usuario del grupo "%s"', + 'User removed successfully from this group.' => 'Usuario eliminado correctamente de este grupo.', + 'Unable to remove this user from the group.' => 'No se puede eliminar este usuario del grupo.', + 'Remove group' => 'Eliminar grupo', + 'Group removed successfully.' => 'Grupo eliminado correctamente.', + 'Unable to remove this group.' => 'No se pudo eliminar este grupo.', + 'Project Permissions' => 'Permisos del proyecto', + 'Manager' => 'Gerente', + 'Project Manager' => 'Gerente de proyecto', + 'Project Member' => 'Miembro del proyecto', + 'Project Viewer' => 'Visor de proyectos', + 'Your account is locked for %d minutes' => 'Tu cuenta está bloqueada durante %d minutos', + 'Invalid captcha' => 'Captcha inválido', + 'The name must be unique' => 'El nombre debe ser único', + 'View all groups' => 'Ver todos los grupos', + 'There is no user available.' => 'No hay usuarios disponibles.', + 'Do you really want to remove the user "%s" from the group "%s"?' => '¿Realmente desea eliminar el usuario "%s" del grupo "%s"?', + 'There is no group.' => 'No hay grupo.', + 'Add group member' => 'Agregar miembro del grupo', + 'Do you really want to remove this group: "%s"?' => '¿Desea realmente eliminar este grupo: "%s"?', + 'There is no user in this group.' => 'No hay ningún usuario en este grupo.', + 'Permissions' => 'Permisos', + 'Allowed Users' => 'Usuarios permitidos', + 'No specific user has been allowed.' => 'Ningún usuario ha sido autorizado específicamente.', + 'Role' => 'Rol', + 'Enter user name...' => 'Introduzca su nombre de usuario...', + 'Allowed Groups' => 'Grupos permitidos', + 'No group has been allowed.' => 'Ningún grupo se ha permitido específicamente.', + 'Group' => 'Grupo', + 'Group Name' => 'Nombre del grupo', + 'Enter group name...' => 'Introduzca el nombre del grupo ...', + 'Role:' => 'Rol:', + 'Project members' => 'Miembros del proyecto', + '%s mentioned you in the task #%d' => '%s te mencionó en la tarea #%d', + '%s mentioned you in a comment on the task #%d' => '%s te ha mencionado en un comentario sobre la tarea #%d', + 'You were mentioned in the task #%d' => 'Te mencionaron en la tarea #%d', + 'You were mentioned in a comment on the task #%d' => 'Usted fue mencionado en un comentario en la tarea #%d', + 'Estimated hours: ' => 'Horas estimadas: ', + 'Actual hours: ' => 'Horas reales: ', + 'Hours Spent' => 'Horas Gastadas', + 'Hours Estimated' => 'Horas Estimadas', + 'Estimated Time' => 'Tiempo Estimado', + 'Actual Time' => 'Tiempo Real', + 'Estimated vs actual time' => 'Horas Presupuestadas vs. tiempo real', + 'RUB - Russian Ruble' => 'RUB - Rublo Ruso', + 'Assign the task to the person who does the action when the column is changed' => 'Asigne la tarea a la persona que realiza la acción cuando se cambia la columna', + 'Close a task in a specific column' => 'Cerrar una tarea en una columna específica', + 'Time-based One-time Password Algorithm' => 'Algoritmo de contraseña de una sola vez basado en el tiempo', + 'Two-Factor Provider: ' => 'Proveedor de dos factores: ', + 'Disable two-factor authentication' => 'Deshabilitar la autenticación de dos factores', + 'Enable two-factor authentication' => 'Habilitar la autenticación de dos factores', + 'There is no integration registered at the moment.' => 'No hay ninguna integración registrada en este momento.', + 'Password Reset for Kanboard' => 'Restablecer contraseña para Kanboard', + 'Forgot password?' => '¿Olvidó su contraseña?', + 'Enable "Forget Password"' => 'Habilitar "Olvidar contraseña"', + 'Password Reset' => 'Restablecimiento de contraseña', + 'New password' => 'Nueva contraseña', + 'Change Password' => 'Cambiar la contraseña', + 'To reset your password click on this link:' => 'Para restablecer su contraseña, haga clic en este enlace:', + 'Last Password Reset' => 'Restablecer la última contraseña', + 'The password has never been reinitialized.' => 'La contraseña nunca ha sido reinicializada.', + 'Creation' => 'Creación', + 'Expiration' => 'Vencimiento', + 'Password reset history' => 'Historial de restablecimiento de contraseñas', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Todas las tareas de la columna "%s" y el carril "%s" han sido cerradas con éxito.', + 'Do you really want to close all tasks of this column?' => '¿Realmente desea cerrar todas las tareas de esta columna?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d tarea(s) en la columna "%s" y el carril "%s" se cerrará.', + 'Close all tasks in this column and this swimlane' => 'Cierre todas las tareas de esta columna', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Ningún plugin ha registrado un método de notificación de proyecto. Todavía puede configurar notificaciones individuales en su perfil de usuario.', + 'My dashboard' => 'Mi tablero', + 'My profile' => 'Mi perfil', + 'Project owner: ' => 'Propietario del proyecto: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'El identificador del proyecto es opcional y debe ser alfanumérico, por ejemplo: MIPROYECTO.', + 'Project owner' => 'Propietario del proyecto', + 'Personal projects do not have users and groups management.' => 'Los proyectos privados no tienen gestión de usuarios y grupos.', + 'There is no project member.' => 'No hay ningún miembro del proyecto.', + 'Priority' => 'Prioridad', + 'Task priority' => 'Prioridad de la tarea', + 'General' => 'General', + 'Dates' => 'Fechas', + 'Default priority' => 'Prioridad predeterminada', + 'Lowest priority' => 'Menor prioridad', + 'Highest priority' => 'Mayor prioridad', + 'Close a task when there is no activity' => 'Cierra una tarea cuando no hay actividad', + 'Duration in days' => 'Duración en días', + 'Send email when there is no activity on a task' => 'Enviar correo electrónico cuando no hay actividad en una tarea', + 'Unable to fetch link information.' => 'No es posible obtener información de enlace.', + 'Daily background job for tasks' => 'Trabajo en segundo plano diario para las tareas', + 'Auto' => 'Auto', + 'Related' => 'Relacionado', + 'Attachment' => 'Archivo adjunto', + 'Web Link' => 'Enlace web', + 'External links' => 'Enlaces externos', + 'Add external link' => 'Añadir enlace externo', + 'Type' => 'Tipo', + 'Dependency' => 'Dependencia', + 'Add internal link' => 'Añadir enlace interno', + 'Add a new external link' => 'Añadir un nuevo enlace externo', + 'Edit external link' => 'Editar enlace externo', + 'External link' => 'Enlace externo', + 'Copy and paste your link here...' => 'Copia y pega el enlace aquí ...', + 'URL' => 'URL', + 'Internal links' => 'Enlaces internos', + 'Assign to me' => 'Asignármelo', + 'Me' => 'Yo', + 'Do not duplicate anything' => 'No duplique nada', + 'Projects management' => 'Gestión de proyectos', + 'Users management' => 'Gestión de usuarios', + 'Groups management' => 'Gestión de grupos', + 'Create from another project' => 'Crear desde otro proyecto', + 'open' => 'abierto', + 'closed' => 'cerrado', + 'Priority:' => 'Prioridad:', + 'Reference:' => 'Referencia:', + 'Complexity:' => 'Complejidad:', + 'Swimlane:' => 'Carril:', + 'Column:' => 'Columna:', + 'Position:' => 'Posición:', + 'Creator:' => 'Creador:', + 'Time estimated:' => 'Horas Presupuestadas:', + '%s hours' => '%s horas', + 'Time spent:' => 'Tiempo usado:', + 'Created:' => 'Creado:', + 'Modified:' => 'Modificado:', + 'Completed:' => 'Terminado:', + 'Started:' => 'Empezado:', + 'Moved:' => 'Movido:', + 'Task #%d' => 'Tarea número %d', + 'Time format' => 'Formato de tiempo', + 'Start date: ' => 'Fecha de inicio: ', + 'End date: ' => 'Fecha final: ', + 'New due date: ' => 'Nueva fecha de vencimiento: ', + 'Start date changed: ' => 'Fecha de inicio cambiada: ', + 'Disable personal projects' => 'Inhabilitar proyectos privados', + 'Do you really want to remove this custom filter: "%s"?' => '¿Desea realmente eliminar este filtro personalizado: "%s"?', + 'Remove a custom filter' => 'Eliminar un filtro personalizado', + 'User activated successfully.' => 'Usuario activado correctamente.', + 'Unable to enable this user.' => 'No se puede habilitar este usuario.', + 'User disabled successfully.' => 'El usuario ha sido desactivado correctamente.', + 'Unable to disable this user.' => 'No se puede deshabilitar este usuario.', + 'All files have been uploaded successfully.' => 'Todos los archivos se han cargado correctamente.', + 'The maximum allowed file size is %sB.' => 'El tamaño máximo de archivo permitido es %sB.', + 'Drag and drop your files here' => 'Arrastra y suelta tus archivos aquí', + 'choose files' => 'Elija el archivo', + 'View profile' => 'Ver perfil', + 'Two Factor' => 'Dos factores', + 'Disable user' => 'Deshabilitar usuario', + 'Do you really want to disable this user: "%s"?' => '¿Desea realmente desactivar este usuario: "%s"?', + 'Enable user' => 'Habilitar usuario', + 'Do you really want to enable this user: "%s"?' => '¿De verdad quieres habilitar a este usuario: "%s"?', + 'Download' => 'Descargar', + 'Uploaded: %s' => 'Subido: %s', + 'Size: %s' => 'Tamaño: %s', + 'Uploaded by %s' => 'Subido por %s', + 'Filename' => 'Nombre del archivo', + 'Size' => 'Tamaño', + 'Column created successfully.' => 'Columna creada correctamente.', + 'Another column with the same name exists in the project' => 'Otra columna con el mismo nombre existe en el proyecto', + 'Default filters' => 'Filtros predeterminados', + 'Your board doesn\'t have any columns!' => '¡Su tablero no tiene columnas!', + 'Change column position' => 'Cambiar la posición de la columna', + 'Switch to the project overview' => 'Cambiar a la vista general del proyecto', + 'User filters' => 'Filtros de usuario', + 'Category filters' => 'Filtros de categorías', + 'Upload a file' => 'Cargar un archivo', + 'View file' => 'Ver archivo', + 'Last activity' => 'Última actividad', + 'Change subtask position' => 'Cambiar la posición de la subtarea', + 'This value must be greater than %d' => 'Este valor no debe de ser más grande que %d', + 'Another swimlane with the same name exists in the project' => 'En el proyecto existe otro carril con el mismo nombre', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Ejemplo: https://example.kanboard.org/ (utilizado para generar URLs absolutas)', + 'Actions duplicated successfully.' => 'Acciones duplicadas correctamente.', + 'Unable to duplicate actions.' => 'No se pueden duplicar acciones.', + 'Add a new action' => 'Añadir una acción nueva', + 'Import from another project' => 'Importar desde otro proyecto', + 'There is no action at the moment.' => 'No hay acción en este momento.', + 'Import actions from another project' => 'Importar acciones de otro proyecto', + 'There is no available project.' => 'No hay proyecto disponible.', + 'Local File' => 'Archivo local', + 'Configuration' => 'Configuración', + 'PHP version:' => 'Versión de PHP:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'Versión del sistema operativo:', + 'Database version:' => 'Versión de base de datos:', + 'Browser:' => 'Navegador:', + 'Task view' => 'Vista de la tarea', + 'Edit task' => 'Editar tarea', + 'Edit description' => 'Editar Descripción', + 'New internal link' => 'Nuevo enlace interno', + 'Display list of keyboard shortcuts' => 'Mostrar lista de atajos de teclado', + 'Avatar' => 'Avatar', + 'Upload my avatar image' => 'Subir mi imagen de avatar', + 'Remove my image' => 'Eliminar mi imagen', + 'The OAuth2 state parameter is invalid' => 'El parámetro de estado OAuth2 no es válido', + 'User not found.' => 'Usuario no encontrado.', + 'Search in activity stream' => 'Buscar en el flujo de actividades', + 'My activities' => 'Mis actividades', + 'Activity until yesterday' => 'Actividad hasta ayer', + 'Activity until today' => 'Actividad hasta hoy', + 'Search by creator: ' => 'Búsqueda por creador: ', + 'Search by creation date: ' => 'Búsqueda por fecha de creación: ', + 'Search by task status: ' => 'Búsqueda por estado de la tarea: ', + 'Search by task title: ' => 'Buscar por título de tarea: ', + 'Activity stream search' => 'Búsqueda de flujo de actividad', + 'Projects where "%s" is manager' => 'Proyectos donde "%s" es gerente', + 'Projects where "%s" is member' => 'Proyectos donde "%s" es miembro', + 'Open tasks assigned to "%s"' => 'Tareas abiertas asignadas a "%s"', + 'Closed tasks assigned to "%s"' => 'Tareas cerradas asignadas a "%s"', + 'Assign automatically a color based on a priority' => 'Asignar automáticamente un color basado en una prioridad', + 'Overdue tasks for the project(s) "%s"' => 'Tareas vencidas para el (los) proyecto(s) "%s"', + 'Upload files' => 'Subir archivos', + 'Installed Plugins' => 'Plugins instalados', + 'Plugin Directory' => 'Directorio de Plugins', + 'Plugin installed successfully.' => 'Plugin instalado correctamente.', + 'Plugin updated successfully.' => 'Plugin actualizado correctamente.', + 'Plugin removed successfully.' => 'Plugin eliminado correctamente.', + 'Subtask converted to task successfully.' => 'Subtarea convertida a la tarea con éxito.', + 'Unable to convert the subtask.' => 'No se puede convertir la subtarea.', + 'Unable to extract plugin archive.' => 'No se puede extraer archivo plugin.', + 'Plugin not found.' => 'No se ha encontrado el plugin.', + 'You don\'t have the permission to remove this plugin.' => 'No tienes permiso para eliminar este plugin.', + 'Unable to download plugin archive.' => 'No es posible descargar archivos plugin.', + 'Unable to write temporary file for plugin.' => 'No se puede escribir el archivo temporal para el plugin.', + 'Unable to open plugin archive.' => 'No se puede abrir el archivo de plugin', + 'There is no file in the plugin archive.' => 'No hay ningún archivo en el archivo de plugins.', + 'Create tasks in bulk' => 'Crear tareas en lote', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Tu instancia de Kanboard no está configurada para instalar complementos desde la interfaz de usuario.', + 'There is no plugin available.' => 'No hay un plugin disponible.', + 'Install' => 'Instalar', + 'Update' => 'Actualizar', + 'Up to date' => 'Hasta la fecha', + 'Not available' => 'No disponible', + 'Remove plugin' => 'Eliminar plugin', + 'Do you really want to remove this plugin: "%s"?' => '¿Realmente desea eliminar este plugin: "%s"?', + 'Uninstall' => 'Desinstalar', + 'Listing' => 'Listado', + 'Metadata' => 'Metadata', + 'Manage projects' => 'Gestionar proyectos', + 'Convert to task' => 'Convertir en tarea', + 'Convert sub-task to task' => 'Convertir subtarea en tarea', + 'Do you really want to convert this sub-task to a task?' => '¿Realmente desea convertir esta subtarea a una tarea?', + 'My task title' => 'Título de mi tarea', + 'Enter one task by line.' => 'Ingrese una tarea por línea.', + 'Number of failed login:' => 'Número de inicio de sesión fallido:', + 'Account locked until:' => 'Cuenta bloqueada hasta:', + 'Email settings' => 'Ajustes de correo electrónico', + 'Email sender address' => 'Dirección del remitente del correo electrónico', + 'Email transport' => 'Transporte por correo electrónico', + 'Webhook token' => 'Token Webhook', + 'Project tags management' => 'Gestión de etiquetas de proyecto', + 'Tag created successfully.' => 'Etiqueta creada correctamente.', + 'Unable to create this tag.' => 'No se puede crear esta etiqueta.', + 'Tag updated successfully.' => 'Etiqueta actualizada correctamente.', + 'Unable to update this tag.' => 'No se puede actualizar esta etiqueta.', + 'Tag removed successfully.' => 'Etiqueta eliminada correctamente.', + 'Unable to remove this tag.' => 'No se ha podido eliminar esta etiqueta.', + 'Global tags management' => 'Gestión de etiquetas globales', + 'Tags' => 'Etiquetas', + 'Tags management' => 'Gestión de etiquetas', + 'Add new tag' => 'Añadir nueva etiqueta', + 'Edit a tag' => 'Modificar una etiqueta', + 'Project tags' => 'Etiquetas del proyecto', + 'There is no specific tag for this project at the moment.' => 'No hay una etiqueta específica para este proyecto en este momento.', + 'Tag' => 'Etiqueta', + 'Remove a tag' => 'Eliminar una etiqueta', + 'Do you really want to remove this tag: "%s"?' => '¿Desea realmente eliminar esta etiqueta: "%s"?', + 'Global tags' => 'Etiquetas globales', + 'There is no global tag at the moment.' => 'No hay una etiqueta global en este momento.', + 'This field cannot be empty' => 'Este campo no puede estar vacío', + 'Close a task when there is no activity in a specific column' => 'Cierra una tarea cuando no hay actividad en una columna específica', + '%s removed a subtask for the task #%d' => '%s eliminó una subtarea para la tarea %d', + '%s removed a comment on the task #%d' => '%s eliminó un comentario para la tarea %d', + 'Comment removed on task #%d' => 'Comentario eliminado en la tarea #%d', + 'Subtask removed on task #%d' => 'Subtarea eliminada en la tarea #%d', + 'Hide tasks in this column in the dashboard' => 'Ocultar tareas en esta columna en el tablero', + '%s removed a comment on the task %s' => '%s eliminó un comentario para la tarea %s', + '%s removed a subtask for the task %s' => '%s eliminó una subtarea para la tarea %s', + 'Comment removed' => 'Comentario eliminado', + 'Subtask removed' => 'Sub-tarea eliminada', + '%s set a new internal link for the task #%d' => '%s estableció un nuevo enlace interno para la tarea %d', + '%s removed an internal link for the task #%d' => '%s eliminó un nuevo enlace interno para la tarea %d', + 'A new internal link for the task #%d has been defined' => 'Se ha definido un nuevo enlace interno para la tarea #%d', + 'Internal link removed for the task #%d' => 'Enlace interno eliminado para la tarea #%d', + '%s set a new internal link for the task %s' => '%s estableció un nuevo enlace interno para la tarea %s', + '%s removed an internal link for the task %s' => '%s eliminó un enlace interno para la tarea %s', + 'Automatically set the due date on task creation' => 'Establecer automáticamente la fecha de vencimiento en la creación de tareas', + 'Move the task to another column when closed' => 'Mover la tarea a otra columna cuando esté cerrada', + 'Move the task to another column when not moved during a given period' => 'Mover la tarea a otra columna cuando no se mueve durante un período determinado', + 'Dashboard for %s' => 'Tablero para %s', + 'Tasks overview for %s' => 'Visión general de tareas para %s', + 'Subtasks overview for %s' => 'Visión general de subtareas para %s', + 'Projects overview for %s' => 'Visión general de proyectos para %s', + 'Activity stream for %s' => 'Flujo de actividad para %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Asignar un color cuando la tarea se mueve a un carril específico', + 'Assign a priority when the task is moved to a specific swimlane' => 'Asignar una prioridad cuando la tarea se mueve a un carril específico', + 'User unlocked successfully.' => 'Usuario desbloqueado correctamente.', + 'Unable to unlock the user.' => 'No se puede desbloquear el usuario.', + 'Move a task to another swimlane' => 'Mueve una tarea a otro carril', + 'Creator Name' => 'Nombre del Creador', + 'Time spent and estimated' => 'Tiempo empleado y estimado', + 'Move position' => 'Mover la posición', + 'Move task to another position on the board' => 'Mover la tarea a otra posición en el tablero', + 'Insert before this task' => 'Insertar antes de esta tarea', + 'Insert after this task' => 'Insertar después de esta tarea', + 'Unlock this user' => 'Desbloquear este usuario', + 'Custom Project Roles' => 'Roles de proyecto personalizados', + 'Add a new custom role' => 'Agregar un nuevo rol personalizado', + 'Restrictions for the role "%s"' => 'Restricciones para el rol "%s"', + 'Add a new project restriction' => 'Añadir una nueva restricción de proyecto', + 'Add a new drag and drop restriction' => 'Añadir una nueva restricción de arrastrar y soltar', + 'Add a new column restriction' => 'Agregar una nueva restricción de columna', + 'Edit this role' => 'Editar este rol', + 'Remove this role' => 'Eliminar este rol', + 'There is no restriction for this role.' => 'No hay restricción para este rol.', + 'Only moving task between those columns is permitted' => 'Sólo se permite el movimiento de una tarea entre esas columnas', + 'Close a task in a specific column when not moved during a given period' => 'Cierra una tarea en una columna específica cuando no se mueve durante un período determinado', + 'Edit columns' => 'Editar columnas', + 'The column restriction has been created successfully.' => 'La restricción de columna se ha creado correctamente.', + 'Unable to create this column restriction.' => 'No se puede crear esta restricción de columna.', + 'Column restriction removed successfully.' => 'Se ha eliminado correctamente la restricción de columna.', + 'Unable to remove this restriction.' => 'No se puede eliminar esta restricción.', + 'Your custom project role has been created successfully.' => 'El rol de proyecto personalizado se ha creado correctamente.', + 'Unable to create custom project role.' => 'No se puede crear un rol de proyecto personalizado.', + 'Your custom project role has been updated successfully.' => 'Su rol de proyecto personalizado se ha actualizado correctamente.', + 'Unable to update custom project role.' => 'No se puede actualizar el rol de proyecto personalizado.', + 'Custom project role removed successfully.' => 'El rol de proyecto personalizado se eliminó correctamente.', + 'Unable to remove this project role.' => 'No se pudo eliminar esta función del proyecto.', + 'The project restriction has been created successfully.' => 'La restricción del proyecto se ha creado con éxito.', + 'Unable to create this project restriction.' => 'No se puede crear esta restricción de proyecto.', + 'Project restriction removed successfully.' => 'Se ha eliminado correctamente la restricción de proyecto.', + 'You cannot create tasks in this column.' => 'No puede crear tareas en esta columna.', + 'Task creation is permitted for this column' => 'La creación de tareas está permitida para esta columna', + 'Closing or opening a task is permitted for this column' => 'El cierre o apertura de una tarea está permitido para esta columna', + 'Task creation is blocked for this column' => 'La creación de tareas está bloqueada para esta columna', + 'Closing or opening a task is blocked for this column' => 'El cierre o apertura de una tarea está bloqueado para esta columna', + 'Task creation is not permitted' => 'No se permite la creación de tareas', + 'Closing or opening a task is not permitted' => 'No se permite cerrar ni abrir una tarea', + 'New drag and drop restriction for the role "%s"' => 'Nueva restricción de arrastrar y soltar para el rol "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Las personas pertenecientes a este rol sólo podrán mover las tareas entre la columna de origen y la de destino.', + 'Remove a column restriction' => 'Eliminar una restricción de columna', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => '¿Realmente desea eliminar esta restricción de columna: "%s" a "%s"?', + 'New column restriction for the role "%s"' => 'Nueva restricción de columna para el rol "%s"', + 'Rule' => 'Regla', + 'Do you really want to remove this column restriction?' => '¿Realmente desea eliminar esta restricción de columna?', + 'Custom roles' => 'Roles personalizados', + 'New custom project role' => 'Nuevo rol de proyecto personalizado', + 'Edit custom project role' => 'Editar un rol de proyecto personalizado', + 'Remove a custom role' => 'Eliminar un rol personalizado', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => '¿Realmente desea eliminar este rol personalizado: "%s"? Todas las personas asignadas a este rol se convertirán en miembros del proyecto.', + 'There is no custom role for this project.' => 'No hay ningun rol personalizado para este proyecto.', + 'New project restriction for the role "%s"' => 'Nueva restricción de proyecto para el rol "%s"', + 'Restriction' => 'Restricción', + 'Remove a project restriction' => 'Eliminar una restricción de proyecto', + 'Do you really want to remove this project restriction: "%s"?' => '¿Realmente desea eliminar esta restricción de proyecto: "%s"?', + 'Duplicate to multiple projects' => 'Duplicar en varios proyectos', + 'This field is required' => 'Este campo es requerido', + 'Moving a task is not permitted' => 'No se permite mover una tarea', + 'This value must be in the range %d to %d' => 'Este valor debe estar en el rango %d a %d', + 'You are not allowed to move this task.' => 'No se le permite mover esta tarea.', + 'API User Access' => 'Acceso de usuario de API', + 'Preview' => 'Previsualizar', + 'Write' => 'Escribir', + 'Write your text in Markdown' => 'Redacta el texto en Markdown', + 'No personal API access token registered.' => 'No se ha registrado ningún token de acceso a API personal.', + 'Your personal API access token is "%s"' => 'Su token de acceso a la API personal es "%s"', + 'Remove your token' => 'Elimine su token', + 'Generate a new token' => 'Generar un nuevo token', + 'Showing %d-%d of %d' => 'Mostrando %d-%d de %d', + 'Outgoing Emails' => 'Correo electrónico saliente', + 'Add or change currency rate' => 'Añadir o cambiar el tipo de cambio', + 'Reference currency: %s' => 'Divisa de referencia: %s', + 'Add custom filters' => 'Añadir filtros personalizados', + 'Export' => 'Exportar', + 'Add link label' => 'Añadir etiqueta de enlace', + 'Incompatible Plugins' => 'Plugins incompatibles', + 'Compatibility' => 'Compatibilidad', + 'Permissions and ownership' => 'Permisos y propiedad', + 'Priorities' => 'Prioridades', + 'Close this window' => 'Cierra esta ventana', + 'Unable to upload this file.' => 'No se puede subir este archivo.', + 'Import tasks' => 'Importar tareas', + 'Choose a project' => 'Elija un proyecto', + 'Profile' => 'Perfil', + 'Application role' => 'Rol de la aplicación', + '%d invitations were sent.' => '%d invitaciones enviadas.', + '%d invitation was sent.' => '%d invitación fue enviada.', + 'Unable to create this user.' => 'No se puede crear este usuario.', + 'Kanboard Invitation' => 'Invitación de Kanboard', + 'Visible on dashboard' => 'Visible en el tablero', + 'Created at:' => 'Creado en:', + 'Updated at:' => 'Actualizado en:', + 'There is no custom filter.' => 'No hay un filtro personalizado.', + 'New User' => 'Nuevo usuario', + 'Authentication' => 'Autenticación', + 'If checked, this user will use a third-party system for authentication.' => 'Si está marcado, este usuario utilizará un sistema de terceros para la autenticación.', + 'The password is necessary only for local users.' => 'La contraseña sólo es necesaria para los usuarios locales.', + 'You have been invited to register on Kanboard.' => 'Te han invitado a registrarte en Kanboard.', + 'Click here to join your team' => 'Haz clic aquí para unirte a tu equipo', + 'Invite people' => 'Invitar personas', + 'Emails' => 'Emails', + 'Enter one email address by line.' => 'Ingrese una dirección de correo electrónico por línea.', + 'Add these people to this project' => 'Añadir a estas personas a este proyecto', + 'Add this person to this project' => 'Añadir a esta persona a este proyecto', + 'Sign-up' => 'Regístrate', + 'Credentials' => 'Credenciales', + 'New user' => 'Añadir un usuario', + 'This username is already taken' => 'Este nombre de usuario ya está en uso', + 'Your profile must have a valid email address.' => 'Su perfil debe tener una dirección de email válido.', + 'TRL - Turkish Lira' => 'TRL - Lira Turca', + 'The project email is optional and could be used by several plugins.' => 'El email del proyecto es opcional y podría ser usado por varios plugins', + 'The project email must be unique across all projects' => 'El email del proyecto debe ser único entre todos los proyectos', + 'The email configuration has been disabled by the administrator.' => 'La configuración de email se ha deshabilitado por el administrador', + 'Close this project' => 'Cerrar este proyecto', + 'Open this project' => 'Abrir este proyecto', + 'Close a project' => 'Cerrar un proyecto', + 'Do you really want to close this project: "%s"?' => '¿Realmente quiere cerrar este proyecto: "%s"?', + 'Reopen a project' => 'Reabrir un proyecto', + 'Do you really want to reopen this project: "%s"?' => '¿Realmente quiere reabrir este proyecto: "%s"?', + 'This project is open' => 'Este proyecto está abierdo', + 'This project is closed' => 'Este proyecto está cerrado', + 'Unable to upload files, check the permissions of your data folder.' => 'No se pueden cargar archivos, verifique los permisos de su carpeta de datos (data)', + 'Another category with the same name exists in this project' => 'Ya existe otra categoría con el mismo nombre en este proyecto', + 'Comment sent by email successfully.' => 'Comentario enviado exitosamente por email', + 'Sent by email to "%s" (%s)' => 'Enviado por email a "%s" (%s)', + 'Unable to read uploaded file.' => 'No se pudo leer el archivo cargado', + 'Database uploaded successfully.' => 'Base de datos cargada exitosamente', + 'Task sent by email successfully.' => 'Tarea enviada exitosamente por email', + 'There is no category in this project.' => 'No hay categorías en este proyecto', + 'Send by email' => 'Enviar por email', + 'Create and send a comment by email' => 'Crear y enviar un comentario por email', + 'Subject' => 'Asunto', + 'Upload the database' => 'Cargar la base de datos', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Podría cargar la base de datos Sqlite descargada previamente (formato Gzip)', + 'Database file' => 'Archivo de Base de Datos', + 'Upload' => 'Cargar', + 'Your project must have at least one active swimlane.' => 'Su proyecto debe tener al menos un carril activo', + 'Project: %s' => 'Proyecto: %s', + 'Automatic action not found: "%s"' => 'No se encontró la acción automática: "%s"', + '%d projects' => '%d proyectos', + '%d project' => '%d proyecto', + 'There is no project.' => 'No hay proyecto', + 'Sort' => 'Ordenar', + 'Project ID' => 'ID Proyecto', + 'Project name' => 'Nombre del Proyecto', + 'Public' => 'Público', + 'Personal' => 'Privado', + '%d tasks' => '%d tareas', + '%d task' => '%d tarea', + 'Task ID' => 'ID Tarea', + 'Assign automatically a color when due date is expired' => 'Asignar un color automáticamente cuando la fecha de vencimiento haya expirado', + 'Total score in this column across all swimlanes' => 'Puntaje Total en esta columna para todos los carriles', + 'HRK - Kuna' => 'HRK - Kuna', + 'ARS - Argentine Peso' => 'ARS - Peso Argentino', + 'COP - Colombian Peso' => 'COP - Peso Colombiano', + '%d groups' => '%d grupos', + '%d group' => '%d grupo', + 'Group ID' => 'ID Grupo', + 'External ID' => 'ID Externa', + '%d users' => '%d usuarios', + '%d user' => '%d usuario', + 'Hide subtasks' => 'Ocultar subtareas', + 'Show subtasks' => 'Mostrar subtareas', + 'Authentication Parameters' => 'Parámetros de Autenticación', + 'API Access' => 'Acceso API', + 'No users found.' => 'No se encontraron usuarios', + 'User ID' => 'ID del Usuario', + 'Notifications are activated' => 'Las notificaciones están activadas', + 'Notifications are disabled' => 'Las notificaciones están deshabilitadas', + 'User disabled' => 'Usuario deshabilitado', + '%d notifications' => '%d notificaciones', + '%d notification' => '%d notificación', + 'There is no external integration installed.' => 'No se ha instalado ninguna integración externa.', + 'You are not allowed to update tasks assigned to someone else.' => 'No se le permite actualizar tareas asignadas a alguien más.', + 'You are not allowed to change the assignee.' => 'No se le permite cambiar la persona asignada.', + 'Task suppression is not permitted' => 'No está permitido la eliminación de tarea', + 'Changing assignee is not permitted' => 'No está permitido cambiar la persona asignada', + 'Update only assigned tasks is permitted' => 'Está permitido actualizar solo las tareas asignadas', + 'Only for tasks assigned to the current user' => 'Solo para las tareas asignadas al usuario actual', + 'My projects' => 'Mis proyectos', + 'You are not a member of any project.' => 'No eres miembro de ningún proyecto.', + 'My subtasks' => 'Mis subtareas', + '%d subtasks' => '%d subtareas', + '%d subtask' => '%d subtarea', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Mover tareas entre esas columnas solo está permitido para las tareas asignadas al usuario actual.', + '[DUPLICATE]' => '[DUPLICAR]', + 'DKK - Danish Krona' => 'DKK - Corona Danesa', + 'Remove user from group' => 'Eliminar usuario del grupo', + 'Assign the task to its creator' => 'Asignar la tarea a su creador', + 'This task was sent by email to "%s" with subject "%s".' => 'Esta tarea fue enviada por correo a "%s" con el asunto "%s".', + 'Predefined Email Subjects' => 'Asuntos de correo predefinidos', + 'Write one subject by line.' => 'Escriba un asunto por línea.', + 'Create another link' => 'Crear otro enlace', + 'BRL - Brazilian Real' => 'BRL - Real brasileño', + 'Add a new Kanboard task' => 'Agregar una nueva tarea de Kanboard', + 'Subtask not started' => 'Subtarea no iniciada', + 'Subtask currently in progress' => 'Subtarea actualmente en progreso', + 'Subtask completed' => 'Subtarea completada', + 'Subtask added successfully.' => 'Subtarea agregada con éxito.', + '%d subtasks added successfully.' => '%d subtareas agregadas con éxito.', + 'Enter one subtask by line.' => 'Ingrese una subtarea por línea.', + 'Predefined Contents' => 'Contenidos predefinidos', + 'Predefined contents' => 'Contenidos predefinidos', + 'Predefined Task Description' => 'Descripción de tarea predefinida', + 'Do you really want to remove this template? "%s"' => '¿Realmente quieres eliminar esta plantilla? "%s"', + 'Add predefined task description' => 'Agregar descripción de tarea predefinida', + 'Predefined Task Descriptions' => 'Descripciones de tareas predefinidas', + 'Template created successfully.' => 'Plantilla creada con éxito', + 'Unable to create this template.' => 'No se puede crear esta plantilla.', + 'Template updated successfully.' => 'No se puede actualizar esta plantilla.', + 'Unable to update this template.' => 'No se puede actualizar esta plantilla.', + 'Template removed successfully.' => 'Plantilla eliminada con éxito.', + 'Unable to remove this template.' => 'No se puede eliminar esta plantilla.', + 'Template for the task description' => 'Plantilla para la descripción de la tarea', + 'The start date is greater than the end date' => 'La fecha de inicio es mayor que la fecha de finalización', + 'Tags must be separated by a comma' => 'Las etiquetas deben estar separadas por una coma', + 'Only the task title is required' => 'Solo se requiere el título de la tarea', + 'Creator Username' => 'Nombre del creador', + 'Color Name' => 'Nombre de color', + 'Column Name' => 'Nombre de columna', + 'Swimlane Name' => 'Nombre del carril', + 'Time Estimated' => 'Tiempo estimado', + 'Time Spent' => 'Tiempo empleado', + 'External Link' => 'Enlace externo', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Esta característica habilita el feed iCal, el feed RSS y la vista de la pizarra pública.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Detener el temporizador de todas las subtareas al mover una tarea a otra columna', + 'Subtask Title' => 'Título de subtarea', + 'Add a subtask and activate the timer when moving a task to another column' => 'Agregar una subtarea y activar el temporizador al mover una tarea a otra columna', + 'days' => 'dias', + 'minutes' => 'minutos', + 'seconds' => 'segundos', + 'Assign automatically a color when preset start date is reached' => 'Asigna automáticamente un color cuando se alcanza la fecha de inicio preestablecida', + 'Move the task to another column once a predefined start date is reached' => 'Mover la tarea a otra columna una vez que se alcanza una fecha de inicio predefinida', + 'This task is now linked to the task %s with the relation "%s"' => 'Esta tarea ahora está vinculada a la tarea %s con la relación "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'El enlace con la relación "%s" con la tarea %s se ha eliminado', + 'Custom Filter:' => 'Filtro personalizado:', + 'Unable to find this group.' => 'No se puede encontrar este grupo.', + '%s moved the task #%d to the column "%s"' => '%s movió la tarea #%d a la columna "%s', + '%s moved the task #%d to the position %d in the column "%s"' => '%s movió la tarea #%d a la posición %d en la columna "%s"', + '%s moved the task #%d to the swimlane "%s"' => '%s movió la tarea #%d al carril "%s"', + '%sh spent' => '%sh empleados', + '%sh estimated' => '%sh estimado', + 'Select All' => 'Seleccionar todo', + 'Unselect All' => 'Deseleccionar todo', + 'Apply action' => 'Aplicar acción', + 'Move selected tasks to another column or swimlane' => 'Mover tareas seleccionadas a otra columna', + 'Edit tasks in bulk' => 'Editar tareas en masa', + 'Choose the properties that you would like to change for the selected tasks.' => 'Elija las propiedades que le gustaría cambiar para las tareas seleccionadas.', + 'Configure this project' => 'Configurar este proyecto', + 'Start now' => 'Empezar ahora', + '%s removed a file from the task #%d' => '%s eliminó un archivo de la tarea #%d', + 'Attachment removed from task #%d: %s' => 'Adjunto eliminado de la tarea #%d: %s', + 'No color' => 'Sin color', + 'Attachment removed "%s"' => 'Adjunto eliminado "%s"', + '%s removed a file from the task %s' => '%s eliminó un archivo de la tarea %s', + 'Move the task to another swimlane when assigned to a user' => 'Mover la tarea a otro carril cuando se le asigna a un usuario', + 'Destination swimlane' => 'Carril de destino', + 'Assign a category when the task is moved to a specific swimlane' => 'Asigne una categoría cuando la tarea se mueva a un carril específico', + 'Move the task to another swimlane when the category is changed' => 'Mueve la tarea a otro carril cuando se cambia la categoría', + 'Reorder this column by priority (ASC)' => 'Reordenar esta columna por prioridad (ASC)', + 'Reorder this column by priority (DESC)' => 'Reordenar esta columna por prioridad (DESC)', + 'Reorder this column by assignee and priority (ASC)' => 'Reordenar esta columna por asignación y prioridad (ASC)', + 'Reorder this column by assignee and priority (DESC)' => 'Reordenar esta columna por asignación y prioridad (DESC)', + 'Reorder this column by assignee (A-Z)' => 'Reordenar esta columna por persona asignada (A-Z)', + 'Reorder this column by assignee (Z-A)' => 'Reordenar esta columna por persona asignada (Z-A)', + 'Reorder this column by due date (ASC)' => 'Reordenar esta columna por fecha de vencimiento (ASC)', + 'Reorder this column by due date (DESC)' => 'Reordenar esta columna por fecha de vencimiento (DESC)', + 'Reorder this column by id (ASC)' => 'Reordenar esta columna por id (ASC)', + 'Reorder this column by id (DESC)' => 'Reordenar esta columna por id (DESC)', + '%s moved the task #%d "%s" to the project "%s"' => '%s movió la tarea #%d "%s" al proyecto "%s', + 'Task #%d "%s" has been moved to the project "%s"' => 'La tarea #%d "%s" se movió al proyecto "%s', + 'Move the task to another column when the due date is less than a certain number of days' => 'Mueva la tarea a otra columna cuando la fecha de vencimiento sea inferior a un cierto número de días', + 'Automatically update the start date when the task is moved away from a specific column' => 'Actualizar automáticamente la fecha de inicio cuando la tarea se aleje de cierta columna', + 'HTTP Client:' => 'Cliente HTTP:', + 'Assigned' => 'Asignado', + 'Task limits apply to each swimlane individually' => 'Los límites de tareas se aplican a cada carril individualmente', + 'Column task limits apply to each swimlane individually' => 'Los límites de tareas de columna se aplican a cada carril individualmente', + 'Column task limits are applied to each swimlane individually' => 'Los límites de tareas de columna se aplican a cada carril individualmente', + 'Column task limits are applied across swimlanes' => 'Los límites de tareas de columna se aplican a través de los carriles', + 'Task limit: ' => 'Límite de tareas: ', + 'Change to global tag' => 'Cambiar a etiqueta global', + 'Do you really want to make the tag "%s" global?' => '¿Realmente quieres hacer la etiqueta "%s" global?', + 'Enable global tags for this project' => 'Habilitar etiquetas globales para este proyecto', + 'Group membership(s):' => 'Membresía(s) de grupo:', + '%s is a member of the following group(s): %s' => '%s es miembro de los siguientes grupos: %s', + '%d/%d group(s) shown' => '%d/%d grupo(s) mostrados', + 'Subtask creation or modification' => 'Creación o modificación de subtarea', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Asigne la tarea a un usuario específico cuando la tarea se mueva a un carril específico', + 'Comment' => 'Comentario', + 'Collapse vertically' => 'Colapsar verticalmente', + 'Expand vertically' => 'Expandir verticalmente', + 'MXN - Mexican Peso' => 'MXN - Peso mexicano', + 'Estimated vs actual time per column' => 'Tiempo estimado vs real por columna', + 'HUF - Hungarian Forint' => 'HUF - Forinto húngaro', + 'XBT - Bitcoin' => 'XBT - Bitcoin', + 'You must select a file to upload as your avatar!' => '¡Debe seleccionar un archivo para subir como su avatar!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => '¡El archivo que subió no es una imagen válida! (¡Solo se permiten *.gif, *.jpg, *.jpeg y *.png!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Establecer automáticamente la fecha de vencimiento cuando la tarea se mueve de una columna específica', + 'No other projects found.' => 'No se encontraron otros proyectos.', + 'Tasks copied successfully.' => 'Tareas copiadas con éxito.', + 'Unable to copy tasks.' => 'No se pueden copiar las tareas.', + 'Theme' => 'Tema', + 'Theme:' => 'Tema:', + 'Light theme' => 'Tema claro', + 'Dark theme' => 'Tema oscuro', + 'Automatic theme - Sync with system' => 'Tema automático - Sincronizar con el sistema', + 'Application managers or more' => 'Gestores de la aplicación o más', + 'Administrators' => 'Administradores', + 'Visibility:' => 'Visibilidad:', + 'Standard users' => 'Usuarios estándar', + 'Visibility is required' => 'La visibilidad es obligatoria', + 'The visibility should be an app role' => 'La visibilidad debe ser un rol de la aplicación', + 'Reply' => 'Responder', + '%s wrote: ' => '%s escribió: ', + 'Number of visible tasks in this column and swimlane' => 'Número de tareas visibles en esta columna y carril', + 'Number of tasks in this swimlane' => 'Número de tareas en este carril', + 'Unable to find another subtask in progress, you can close this window.' => 'No se puede encontrar otra subtarea en progreso, puede cerrar esta ventana.', + 'This theme is invalid' => 'Este tema no es válido', + 'This role is invalid' => 'Este rol no es válido', + 'This timezone is invalid' => 'Esta zona horaria no es válida', + 'This language is invalid' => 'Este idioma no es válido', + 'This URL is invalid' => 'Esta URL no es válida', + 'Date format invalid' => 'Formato de fecha no válido', + 'Time format invalid' => 'Formato de hora no válido', + 'Invalid Mail transport' => 'Transporte de correo no válido', + 'Color invalid' => 'Color no válido', + 'This value must be greater or equal to %d' => 'Este valor debe ser mayor o igual a %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Agregar un BOM al principio del archivo (requerido para Microsoft Excel)', + 'Just add these tag(s)' => 'Agregar solo estas etiquetas', + 'Remove internal link(s)' => 'Eliminar enlaces internos', + 'Import tasks from another project' => 'Importar tareas de otro proyecto', + 'Select the project to copy tasks from' => 'Seleccione el proyecto del que desea copiar tareas', + 'The total maximum allowed attachments size is %sB.' => 'El tamaño máximo total permitido para los archivos adjuntos es %sB.', + 'Add attachments' => 'Agregar archivos adjuntos', + 'Task #%d "%s" is overdue' => 'La tarea #%d "%s" está vencida', + 'Enable notifications by default for all new users' => 'Habilitar notificaciones por defecto para todos los nuevos usuarios', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Asignar la tarea a su creador para columnas específicas si no se ha establecido un responsable manualmente', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Asignar una tarea al usuario conectado al cambiar de columna a la columna especificada si no hay ningún usuario asignado', +]; diff --git a/app/Locale/es_VE/translations.php b/app/Locale/es_VE/translations.php new file mode 100644 index 0000000..7c55433 --- /dev/null +++ b/app/Locale/es_VE/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => '.', + 'None' => 'Ninguno', + 'Edit' => 'Modificar', + 'Remove' => 'Eliminar', + 'Yes' => 'Sí', + 'No' => 'No', + 'cancel' => 'cancelar', + 'or' => 'o', + 'Yellow' => 'amarillo', + 'Blue' => 'azul', + 'Green' => 'verde', + 'Purple' => 'púrpura', + 'Red' => 'rojo', + 'Orange' => 'anaranjado', + 'Grey' => 'gris', + 'Brown' => 'marrón', + 'Deep Orange' => 'anaranjado oscuro', + 'Dark Grey' => 'gris oscuro', + 'Pink' => 'rosado', + 'Teal' => 'verde azulado', + 'Cyan' => 'azul claro', + 'Lime' => 'verde limón', + 'Light Green' => 'verde claro', + 'Amber' => 'ámbar', + 'Save' => 'Guardar', + 'Login' => 'Iniciar sesión', + 'Official website:' => 'Página web oficial :', + 'Unassigned' => 'Sin asignar', + 'View this task' => 'Ver esta tarea', + 'Remove user' => 'Eliminar un usuario(a)', + 'Do you really want to remove this user: "%s"?' => '¿Realmente quiere eliminar este usuario(a): « %s » ?', + 'All users' => 'Todos los usuario(a)s', + 'Username' => 'Nombre de usuario(a)', + 'Password' => 'Contraseña', + 'Administrator' => 'Administrador(a)', + 'Sign in' => 'Iniciar sesión', + 'Users' => 'Usuario(a)s', + 'Forbidden' => 'Proibido', + 'Access Forbidden' => 'Acceso prohibido', + 'Edit user' => 'Editar usuario(a)', + 'Logout' => 'Cerrar sesión', + 'Bad username or password' => 'Usuario(a) incorrecto(a) y/o contraseña incorrecta', + 'Edit project' => 'Editar proyecto', + 'Name' => 'Nombre', + 'Projects' => 'Proyectos', + 'No project' => 'Sin proyecto alguno', + 'Project' => 'Proyecto', + 'Status' => 'Estado', + 'Tasks' => 'Tareas', + 'Board' => 'Tablero', + 'Actions' => 'Acciones', + 'Inactive' => 'Inactivo', + 'Active' => 'Activo', + 'Unable to update this board.' => 'No se puede actualizar este tablero.', + 'Disable' => 'Desactivar', + 'Enable' => 'Activar', + 'New project' => 'Proyecto nuevo', + 'Do you really want to remove this project: "%s"?' => '¿Realmente quiere eliminar este proyecto: « %s » ?', + 'Remove project' => 'Eliminar proyecto', + 'Edit the board for "%s"' => 'Modificar tablero para « %s »', + 'Add a new column' => 'Añadir una nueva columna', + 'Title' => 'Título', + 'Assigned to %s' => 'Asignado(a) a %s', + 'Remove a column' => 'Eliminar esta columna', + 'Unable to remove this column.' => 'No se puede eliminar esta columna.', + 'Do you really want to remove this column: "%s"?' => '¿Realmente quiere eliminar esta columna : « %s » ?', + 'Settings' => 'Configuración de preferencias', + 'Application settings' => 'Parámetros de aplicación', + 'Language' => 'Idioma', + 'Webhook token:' => 'Token para los webhooks :', + 'API token:' => 'Token de la API:', + 'Database size:' => 'Tamaño de la base de datos:', + 'Download the database' => 'Descargar base de datos', + 'Optimize the database' => 'Optimizar base de datos', + '(VACUUM command)' => '(Comando VACUUM)', + '(Gzip compressed Sqlite file)' => '(Archivo Sqlite comprimido en Gzip)', + 'Close a task' => 'Cerrar una tarea', + 'Column' => 'Columna', + 'Color' => 'Color', + 'Assignee' => 'Persona asignada', + 'Create another task' => 'Crear otra tarea', + 'New task' => 'Tarea nueva', + 'Open a task' => 'Abrir una tarea', + 'Do you really want to open this task: "%s"?' => '¿Realmente quiere abrir esta tarea: « %s » ?', + 'Back to the board' => 'Regresar al tablero', + 'There is nobody assigned' => 'Nadie asignado a esta tarea', + 'Column on the board:' => 'Columna en el tablero: ', + 'Close this task' => 'Cerrar esta tarea', + 'Open this task' => 'Abrir esta tarea', + 'There is no description.' => 'No hay descripción.', + 'Add a new task' => 'Añadir una tarea nueva', + 'The username is required' => 'El nombre de usuario(a) es obligatorio', + 'The maximum length is %d characters' => 'La longitud máxima es de %d caracteres', + 'The minimum length is %d characters' => 'La longitud mínima es de %d caracteres', + 'The password is required' => 'La contraseña es obligatoria', + 'This value must be an integer' => 'Este valor debe ser un número entero', + 'The username must be unique' => 'El nombre de usuario(a) debe ser único', + 'The user id is required' => 'El identificador del usuario(a) es obligatorio', + 'Passwords don\'t match' => 'Las contraseñas no coinciden', + 'The confirmation is required' => 'La confirmación es obligatoria', + 'The project is required' => 'El proyecto es obligatorio', + 'The id is required' => 'El identificador es obligatorio', + 'The project id is required' => 'El identificador del proyecto es obligatorio', + 'The project name is required' => 'El nombre del proyecto es obligatorio', + 'The title is required' => 'El título es obligatorio', + 'Settings saved successfully.' => 'Parámetros guardados correctamente.', + 'Unable to save your settings.' => 'No se pueden guardar sus parámetros.', + 'Database optimization done.' => 'Optimización de la base de datos finalizada.', + 'Your project has been created successfully.' => 'El proyecto ha sido creado correctamente.', + 'Unable to create your project.' => 'No se puede crear el proyecto.', + 'Project updated successfully.' => 'El proyecto ha sido actualizado correctamente.', + 'Unable to update this project.' => 'No se puede actualizar el proyecto.', + 'Unable to remove this project.' => 'No se puede eliminar este proyecto.', + 'Project removed successfully.' => 'El proyecto ha sido eliminado correctamente.', + 'Project activated successfully.' => 'El proyecto ha sido activado correctamente.', + 'Unable to activate this project.' => 'No se puede activar el proyecto.', + 'Project disabled successfully.' => 'El proyecto ha sido desactivado correctamente.', + 'Unable to disable this project.' => 'No se puede desactivar el proyecto.', + 'Unable to open this task.' => 'No se puede abrir esta tarea.', + 'Task opened successfully.' => 'La tarea ha sido abierta correctamente.', + 'Unable to close this task.' => 'No se puede cerrar esta tarea.', + 'Task closed successfully.' => 'La tarea ha sido cerrada correctamente.', + 'Unable to update your task.' => 'No se puede modificar esta tarea.', + 'Task updated successfully.' => 'La tarea ha sido actualizada correctamente.', + 'Unable to create your task.' => 'No se puede crear esta tarea.', + 'Task created successfully.' => 'La tarea ha sido creada correctamente.', + 'User created successfully.' => 'El usuario(a) ha sido creado correctamente.', + 'Unable to create your user.' => 'No se puede crear este usuario(a).', + 'User updated successfully.' => 'El usuario(a) ha sido actualizado correctamente.', + 'User removed successfully.' => 'El usuario(a) ha sido creado correctamente.', + 'Unable to remove this user.' => 'No se puede crear este usuario(a).', + 'Board updated successfully.' => 'El tablero ha sido actualizado correctamente.', + 'Ready' => 'Listo', + 'Backlog' => 'En espera', + 'Work in progress' => 'En curso', + 'Done' => 'Hecho', + 'Application version:' => 'Versión de la aplicación:', + 'Id' => 'Identificador', + 'Public link' => 'Vinculación pública', + 'Timezone' => 'Zona horaria', + 'Sorry, I didn\'t find this information in my database!' => 'Lo siento no he encontrado información en la base de datos!', + 'Page not found' => 'Página no encontrada', + 'Complexity' => 'Complejidad', + 'Task limit' => 'Número máximo de tareas', + 'Task count' => 'Conteo de tareas', + 'User' => 'Usuario(a)', + 'Comments' => 'Comentarios', + 'Comment is required' => 'El comentario es obligatorio', + 'Comment added successfully.' => 'El comentario ha sido añadido correctamente.', + 'Unable to create your comment.' => 'No se puede crear este comentario.', + 'Due Date' => 'Fecha límite', + 'Invalid date' => 'Fecha no válida', + 'Automatic actions' => 'Acciones automatizadas', + 'Your automatic action has been created successfully.' => 'La acción automatizada ha sido creada correctamente.', + 'Unable to create your automatic action.' => 'No se puede crear esta acción automatizada.', + 'Remove an action' => 'Eliminar una acción', + 'Unable to remove this action.' => 'No se puede eliminar esta accción.', + 'Action removed successfully.' => 'La acción ha sido eliminada correctamente.', + 'Automatic actions for the project "%s"' => 'Acciones automatizadas para este proyecto « %s »', + 'Add an action' => 'Agregar una acción', + 'Event name' => 'Nombre del evento', + 'Action' => 'Acción', + 'Event' => 'Evento', + 'When the selected event occurs execute the corresponding action.' => 'Cuando tiene lugar el evento seleccionado, ejecutar la acción correspondiente.', + 'Next step' => 'Siguiente etapa', + 'Define action parameters' => 'Definición de los parametros de la acción', + 'Do you really want to remove this action: "%s"?' => '¿Realmente quiere eliminar esta acción « %s » ?', + 'Remove an automatic action' => 'Eliminar una acción automatizada', + 'Assign the task to a specific user' => 'Asignar una tarea a un usuario(a) específico(a)', + 'Assign the task to the person who does the action' => 'Asignar la tarea al usuario quien realiza la acción', + 'Duplicate the task to another project' => 'Duplicar la tarea a otro proyecto', + 'Move a task to another column' => 'Mover una tarea a otra columna', + 'Task modification' => 'Modificación de una tarea', + 'Task creation' => 'Creación de una tarea', + 'Closing a task' => 'Cerrar una tarea', + 'Assign a color to a specific user' => 'Asignar un color a un(a) usuario(a) específico(a)', + 'Position' => 'Posición', + 'Duplicate to project' => 'Duplicar a otro proyecto', + 'Duplicate' => 'Duplicar', + 'Link' => 'Enlace', + 'Comment updated successfully.' => 'El comentario ha sido actualizado correctamente.', + 'Unable to update your comment.' => 'No se puede actualizar este comentario.', + 'Remove a comment' => 'Eliminar un comentario', + 'Comment removed successfully.' => 'El comentario ha sido eliminado correctamente.', + 'Unable to remove this comment.' => 'No se puede eliminar este comentario.', + 'Do you really want to remove this comment?' => '¿Realmente quiere eliminar este comentario?', + 'Current password for the user "%s"' => 'Contraseña actual para el(la) usuario(a): « %s »', + 'The current password is required' => 'La contraseña es obligatoria', + 'Wrong password' => 'contraseña incorrecta', + 'Unknown' => 'Desconocido', + 'Last logins' => 'Últimos ingresos', + 'Login date' => 'Fecha de ingreso', + 'Authentication method' => 'Método de autenticación', + 'IP address' => 'Dirección IP', + 'User agent' => 'Agente de usuario', + 'Persistent connections' => 'Conexión persistente', + 'No session.' => 'No existe sesión.', + 'Expiration date' => 'Fecha de expiración', + 'Remember Me' => 'Recuérdame', + 'Creation date' => 'Fecha de creación', + 'Everybody' => 'Todo el mundo', + 'Open' => 'Abierto', + 'Closed' => 'Cerrado', + 'Search' => 'Buscar', + 'Nothing found.' => 'Nada hallado.', + 'Due date' => 'Fecha Límite', + 'Description' => 'Descripción', + '%d comments' => '%d comentarios', + '%d comment' => '%d comentario', + 'Email address invalid' => 'Dirección de correo inválida', + 'Your external account is not linked anymore to your profile.' => 'Su cuenta externa no está vinculada a tu perfil.', + 'Unable to unlink your external account.' => 'No se puede desvincular su cuenta externa.', + 'External authentication failed' => 'Error de autenticación externa', + 'Your external account is linked to your profile successfully.' => 'Su cuenta externa está vinculada a su perfil correctamente.', + 'Email' => 'Correo electrónico', + 'Task removed successfully.' => 'Tarea eliminada correctamente.', + 'Unable to remove this task.' => 'No pude eliminar esta tarea.', + 'Remove a task' => 'Eliminar una tarea', + 'Do you really want to remove this task: "%s"?' => '¿Realmente quiere eliminar esta tarea: "%s"?', + 'Assign automatically a color based on a category' => 'Asignar un color de forma automática basándose en la categoría', + 'Assign automatically a category based on a color' => 'Asignar una categoría de forma automática basándose en el color', + 'Task creation or modification' => 'Creación o Edición de Tarea', + 'Category' => 'Categoría', + 'Category:' => 'Categoría:', + 'Categories' => 'Categorías', + 'Your category has been created successfully.' => 'Se ha creado su categoría correctamente.', + 'This category has been updated successfully.' => 'Esta categoría se ha actualizado correctamente.', + 'Unable to update this category.' => 'No se puede actualizar esta categoría.', + 'Remove a category' => 'Eliminar una categoría', + 'Category removed successfully.' => 'Categoría eliminidad correctamente.', + 'Unable to remove this category.' => 'No se pudo eliminar esta categoría.', + 'Category modification for the project "%s"' => 'Modificación de categoría para el proyecto "%s"', + 'Category Name' => 'Nombre de categoría', + 'Add a new category' => 'Añadir una categoría nueva', + 'Do you really want to remove this category: "%s"?' => '¿Realmente quiere eliminar esta categoría: "%s"?', + 'All categories' => 'Todas las categorías', + 'No category' => 'Sin categoría', + 'The name is required' => 'El nombre es obligatorio', + 'Remove a file' => 'Borrar un archivo', + 'Unable to remove this file.' => 'No pude borrar este archivo.', + 'File removed successfully.' => 'Archivo eliminado correctamente.', + 'Attach a document' => 'Adjuntar un documento', + 'Do you really want to remove this file: "%s"?' => '¿Realmente quiere eliminar este archivo: "%s"?', + 'Attachments' => 'Archivos adjuntos', + 'Edit the task' => 'Editar la tarea', + 'Add a comment' => 'Añadir un comentario', + 'Edit a comment' => 'Editar un comentario', + 'Summary' => 'Resumen', + 'Time tracking' => 'Control de tiempo', + 'Estimate:' => 'Estimado:', + 'Spent:' => 'Transcurrido:', + 'Do you really want to remove this sub-task?' => '¿Realmente quiere eliminar esta subtarea?', + 'Remaining:' => 'Restante', + 'hours' => 'horas', + 'estimated' => 'estimado', + 'Sub-Tasks' => 'Subtareas', + 'Add a sub-task' => 'Añadir una subtarea', + 'Original estimate' => 'Horas presupuestadas', + 'Create another sub-task' => 'Crear otra subtarea', + 'Time spent' => 'Horas ejecutadas', + 'Edit a sub-task' => 'Editar una subtarea', + 'Remove a sub-task' => 'Suprimir una subtarea', + 'The time must be a numeric value' => 'El tiempo debe de ser un valor numérico', + 'Todo' => 'Por hacer', + 'In progress' => 'En progreso', + 'Sub-task removed successfully.' => 'Subtarea eliminada correctamente.', + 'Unable to remove this sub-task.' => 'No pude eliminar esta subtarea.', + 'Sub-task updated successfully.' => 'Subtarea actualizada correctamente.', + 'Unable to update your sub-task.' => 'No pude actualizar tu subtarea.', + 'Unable to create your sub-task.' => 'No pude crear tu subtarea.', + 'Maximum size: ' => 'Tamaño máximo', + 'Display another project' => 'Mostrar otro proyecto', + 'Created by %s' => 'Creado por %s', + 'Tasks Export' => 'Exportar tareas', + 'Start Date' => 'Fecha de inicio', + 'Execute' => 'Ejecutar', + 'Task Id' => 'ID de tarea', + 'Creator' => 'Creador', + 'Modification date' => 'Fecha de modificación', + 'Completion date' => 'Fecha de finalización', + 'Clone' => 'Clonar', + 'Project cloned successfully.' => 'Proyecto clonado correctamente', + 'Unable to clone this project.' => 'Impsible clonar proyecto', + 'Enable email notifications' => 'Habilitar notificaciones por correo electrónico', + 'Task position:' => 'Posición de la tarea', + 'The task #%d has been opened.' => 'La tarea #%d ha sido abierta', + 'The task #%d has been closed.' => 'La tarea #%d ha sido cerrada', + 'Sub-task updated' => 'Subtarea actualizada', + 'Title:' => 'Título:', + 'Status:' => 'Estado:', + 'Assignee:' => 'Asignada a:', + 'Time tracking:' => 'Control de tiempo:', + 'New sub-task' => 'Nueva subtarea', + 'New attachment added "%s"' => 'Nuevo adjunto agregado "%s"', + 'New comment posted by %s' => 'Nuevo comentario agregado por %s', + 'New comment' => 'Nuevo comentario', + 'Comment updated' => 'Comentario actualizado', + 'New subtask' => 'Nueva subtarea', + 'I only want to receive notifications for these projects:' => 'Quiero recibir notificaciones sólo de estos proyectos:', + 'view the task on Kanboard' => 'ver la tarea en Kanboard', + 'Public access' => 'Acceso público', + 'Disable public access' => 'Desactivar acceso público', + 'Enable public access' => 'Activar acceso público', + 'Public access disabled' => 'Acceso público desactivado', + 'Move the task to another project' => 'Mover la tarea a otro proyecto', + 'Move to project' => 'Mover a otro proyecto', + 'Do you really want to duplicate this task?' => '¿Realmente quiere duplicar esta tarea?', + 'Duplicate a task' => 'Duplicar una tarea', + 'External accounts' => 'Cuentas externas', + 'Account type' => 'Tipo de Cuenta', + 'Local' => 'Local', + 'Remote' => 'Remota', + 'Enabled' => 'Activada', + 'Disabled' => 'Desactivada', + 'Login:' => 'Login:', + 'Full Name:' => 'Nombre completo:', + 'Email:' => 'Correo electrónico:', + 'Notifications:' => 'Notificaciones:', + 'Notifications' => 'Notificaciones', + 'Account type:' => 'Tipo de Cuenta:', + 'Edit profile' => 'Editar perfil', + 'Change password' => 'Cambiar contraseña', + 'Password modification' => 'Modificacion de contraseña', + 'External authentications' => 'Autenticación externa', + 'Never connected.' => 'Nunca se ha conectado.', + 'No external authentication enabled.' => 'Sin autenticación externa activa.', + 'Password modified successfully.' => 'Contraseña cambiada correctamente.', + 'Unable to change the password.' => 'No pude cambiar la contraseña.', + 'Change category' => 'Cambiar categoría', + '%s updated the task %s' => '%s actualizó la tarea %s', + '%s opened the task %s' => '%s abrió la tarea %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s movió la tarea %s a la posición #%d de la columna "%s"', + '%s moved the task %s to the column "%s"' => '%s movió la tarea %s a la columna "%s"', + '%s created the task %s' => '%s creó la tarea %s', + '%s closed the task %s' => '%s cerró la tarea %s', + '%s created a subtask for the task %s' => '%s creó una subtarea para la tarea %s', + '%s updated a subtask for the task %s' => '%s actualizó una subtarea para la tarea %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Asignada a %s con una estimación de %s/%sh', + 'Not assigned, estimate of %sh' => 'No asignada, se estima en %sh', + '%s updated a comment on the task %s' => '%s actualizó un comentario de la tarea %s', + '%s commented the task %s' => '%s comentó la tarea %s', + '%s\'s activity' => 'Actividad de %s', + 'RSS feed' => 'Archivo RSS', + '%s updated a comment on the task #%d' => '%s actualizó un comentario de la tarea #%d', + '%s commented on the task #%d' => '%s comentó la tarea #%d', + '%s updated a subtask for the task #%d' => '%s actualizó una subtarea de la tarea #%d', + '%s created a subtask for the task #%d' => '%s creó una subtarea de la tarea #%d', + '%s updated the task #%d' => '%s actualizó la tarea #%d', + '%s created the task #%d' => '%s creó la tarea #%d', + '%s closed the task #%d' => '%s cerró la tarea #%d', + '%s opened the task #%d' => '%s abrió la tarea #%d', + 'Activity' => 'Actividad', + 'Default values are "%s"' => 'Los valores por defecto son "%s"', + 'Default columns for new projects (Comma-separated)' => 'Columnas por defecto de los nuevos proyectos (Separadas mediante comas)', + 'Task assignee change' => 'Cambiar persona asignada a la tarea', + '%s changed the assignee of the task #%d to %s' => '%s cambió al asignado de la tarea #%d a %s', + '%s changed the assignee of the task %s to %s' => '%s cambió la persona asignada de la tarea de %s a %s', + 'New password for the user "%s"' => 'Nueva contraseña para el usuario(a) "%s"', + 'Choose an event' => 'Seleccione un evento', + 'Create a task from an external provider' => 'Crear una tarea a partir de un proveedor externo', + 'Change the assignee based on an external username' => 'Cambiar la asignación basado en un nombre de usuario(a) externo', + 'Change the category based on an external label' => 'Cambiar la categoría basado en una etiqueta externa', + 'Reference' => 'Referencia', + 'Label' => 'Etiqueta', + 'Database' => 'Base de Datos', + 'About' => 'Acerca de', + 'Database driver:' => 'Controlador de la base de datos', + 'Board settings' => 'Configuraciones del Tablero', + 'Webhook settings' => 'Configuraciones del Webhook', + 'Reset token' => 'Reiniciar token', + 'API endpoint:' => 'Punto extremo del API', + 'Refresh interval for personal board' => 'Intervalo de refrescamiento del tablero privado', + 'Refresh interval for public board' => 'Intervalo de refrescamiento del tablero público', + 'Task highlight period' => 'Período del realce de la tarea', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Periodo (en segundos) para considerar que una tarea fue modificada recientemente (0 para deshabilitar, 2 días por defecto)', + 'Frequency in second (60 seconds by default)' => 'Frecuencia en segundos (60 segundos por defecto)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frecuencia en segundos (0 para deshabilitar esta característica, 10 segundos por defecto)', + 'Application URL' => 'URL de la aplicación', + 'Token regenerated.' => 'Token regenerado', + 'Date format' => 'Formato de la fecha', + 'ISO format is always accepted, example: "%s" and "%s"' => 'El formato ISO siempre es aceptado, ejemplo: "%s" y "%s"', + 'New personal project' => 'Nuevo proyecto privado', + 'This project is personal' => 'Este proyecto es privado', + 'Add' => 'Añadir', + 'Start date' => 'Fecha de inicio', + 'Time estimated' => 'Horas presupuestadas', + 'There is nothing assigned to you.' => 'Nada asignado para usted', + 'My tasks' => 'Mis tareas', + 'Activity stream' => 'Flujo de actividades', + 'Dashboard' => 'Tablero', + 'Confirmation' => 'Confirmación', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Create a comment from an external provider' => 'Crear un comentario a partir de un proveedor externo', + 'Project management' => 'Gestión de proyectos', + 'Columns' => 'Columnas', + 'Task' => 'Tarea', + 'Percentage' => 'Porcentaje', + 'Number of tasks' => 'Número de tareas', + 'Task distribution' => 'Distribución de tareas', + 'Analytics' => 'Analítica', + 'Subtask' => 'Subtarea', + 'User repartition' => 'Usuario(a)s y su repartición de trabajo', + 'Clone this project' => 'Clonar este proyecto', + 'Column removed successfully.' => 'Columna removida correctamente', + 'Not enough data to show the graph.' => 'No hay suficiente información para mostrar el gráfico', + 'Previous' => 'Anterior', + 'The id must be an integer' => 'El identificador debe ser un número entero', + 'The project id must be an integer' => 'El identificador del proyecto debe ser un número entero', + 'The status must be an integer' => 'El estado debe ser un número entero', + 'The subtask id is required' => 'El identificador de la subtarea es requerido', + 'The subtask id must be an integer' => 'El identificador de la subtarea debe ser un número entero', + 'The task id is required' => 'El identificador de la tarea es requerido', + 'The task id must be an integer' => 'El identificador de la tarea debe ser un número entero', + 'The user id must be an integer' => 'El identificador del usuario(a) debe ser un número entero', + 'This value is required' => 'El valor es requerido', + 'This value must be numeric' => 'Este valor debe ser numérico', + 'Unable to create this task.' => 'Imposible crear esta tarea', + 'Cumulative flow diagram' => 'Diagrama de flujo acumulativo', + 'Daily project summary' => 'Resumen diario del proyecto', + 'Daily project summary export' => 'Exportar sumario diario del proyecto', + 'Exports' => 'Exportar', + 'This export contains the number of tasks per column grouped per day.' => 'Esta exportación contiene el número de tereas por columna agrupada por día', + 'Active swimlanes' => 'Carriles activos', + 'Add a new swimlane' => 'Añadir nuevo carril', + 'Default swimlane' => 'Carril por defecto', + 'Do you really want to remove this swimlane: "%s"?' => '¿Realmente quiere remover este carril: "%s"?', + 'Inactive swimlanes' => 'Carriles inactivos', + 'Remove a swimlane' => 'Remover un carril', + 'Swimlane modification for the project "%s"' => 'Modificación del carril para el proyecto "%s"', + 'Swimlane removed successfully.' => 'Carril removido correctamente', + 'Swimlanes' => 'Carriles', + 'Swimlane updated successfully.' => 'Carril actualizado correctamente', + 'Unable to remove this swimlane.' => 'Imposible remover este carril', + 'Unable to update this swimlane.' => 'Imposible actualizar este carril', + 'Your swimlane has been created successfully.' => 'Su carril ha sido creado correctamente', + 'Example: "Bug, Feature Request, Improvement"' => 'Ejemplo: "Error, Solicitud de característica, Mejora"', + 'Default categories for new projects (Comma-separated)' => 'Categorías por defecto de los nuevos proyectos (separadas mediante comas)', + 'Integrations' => 'Integraciones', + 'Integration with third-party services' => 'Integraciones para servicios de terceros', + 'Subtask Id' => 'Identificador de subtareas', + 'Subtasks' => 'Subtareas', + 'Subtasks Export' => 'Exportar subtareas', + 'Task Title' => 'Título de la tarea', + 'Untitled' => 'Sin título', + 'Application default' => 'Predefinido de la aplicación', + 'Language:' => 'Idioma', + 'Timezone:' => 'Zona horaria', + 'All columns' => 'Todas las columnas', + 'Next' => 'Siguiente', + '#%d' => '#%d', + 'All swimlanes' => 'Todos los carriles', + 'All colors' => 'Todos los colores', + 'Moved to column %s' => 'Movido a columna %s', + 'User dashboard' => 'Tablero de el(la) usuario(a)', + 'Allow only one subtask in progress at the same time for a user' => 'Permitir únicamente una subtarea en progreso al mismo tiempo para un usuario(a)', + 'Edit column "%s"' => 'Editar columna "%s"', + 'Select the new status of the subtask: "%s"' => 'Seleccione el nuevo estado de la subtarea: "%s"', + 'Subtask timesheet' => 'Hoja de tiempos de la subtarea', + 'There is nothing to show.' => 'No hay nada que mostrar.', + 'Time Tracking' => 'Control de Tiempo', + 'You already have one subtask in progress' => 'Ya tiene una subtarea en progreso', + 'Which parts of the project do you want to duplicate?' => '¿Que partes del proyecto quiere duplicar?', + 'Disallow login form' => 'Deshabilitar el formulario de inicio de sesión', + 'Start' => 'Inicio', + 'End' => 'Fin', + 'Task age in days' => 'Tiempo de la tarea (en días)', + 'Days in this column' => 'Días en esta columna', + '%dd' => '%dd', + 'Add a new link' => 'Añadir nuevo vínculo', + 'Do you really want to remove this link: "%s"?' => '¿Realmente quiere eliminar este vínculo: "%s"?', + 'Do you really want to remove this link with task #%d?' => '¿Realmente quiere eliminar este vínculo con la tarea #%d?', + 'Field required' => 'Campo requerido', + 'Link added successfully.' => 'Vínculo añadido correctamente', + 'Link updated successfully.' => 'Vínculo actualizado correctamente', + 'Link removed successfully.' => 'Vínculo suprimido correctamente', + 'Link labels' => 'Etiquetas de vínculos', + 'Link modification' => 'Modificar vínculo', + 'Opposite label' => 'Etiquetas opuestas', + 'Remove a link' => 'Suprimir un vínculo', + 'The labels must be different' => 'Las etiquetas deben ser diferentes', + 'There is no link.' => 'No hay vínculo', + 'This label must be unique' => 'Esta etiqueta debe ser única', + 'Unable to create your link.' => 'No se puede crear su vínculo', + 'Unable to update your link.' => 'No se puede actualizar su vínculo', + 'Unable to remove this link.' => 'No se puede suprimir su vínculo', + 'relates to' => 'relacionado con', + 'blocks' => 'bloqueos', + 'is blocked by' => 'esta bloqueado por', + 'duplicates' => 'duplicados', + 'is duplicated by' => 'está duplicado por', + 'is a child of' => 'es hijo de', + 'is a parent of' => 'es padre de', + 'targets milestone' => 'metas del hito', + 'is a milestone of' => 'es un hito de', + 'fixes' => 'arreglo', + 'is fixed by' => 'arreglado por', + 'This task' => 'Esta tarea', + '<1h' => '<1h', + '%dh' => '%dh', + 'Expand tasks' => 'Expandir tareas', + 'Collapse tasks' => 'Colapsar tareas', + 'Expand/collapse tasks' => 'Expandir/colapsar tareas', + 'Close dialog box' => 'Cerrar caudro de diálogo', + 'Submit a form' => 'Enviar un formulario', + 'Board view' => 'Vista de tablero', + 'Keyboard shortcuts' => 'Atajos del teclado', + 'Open board switcher' => 'Conmutador de tablero abierto', + 'Application' => 'Aplicación', + 'Compact view' => 'Vista compacta', + 'Horizontal scrolling' => 'Desplazamiento horizontal', + 'Compact/wide view' => 'Vista compacta/amplia', + 'Currency' => 'Moneda', + 'Personal project' => 'Proyecto privado', + 'AUD - Australian Dollar' => 'AUD - Dólar australiano', + 'CAD - Canadian Dollar' => 'CAD - Dólar canadiense', + 'CHF - Swiss Francs' => 'CHF - Franco suizo', + 'Custom Stylesheet' => 'Hoja de estilo personalizada', + 'EUR - Euro' => 'EUR - Euro', + 'GBP - British Pound' => 'GBP - Libra británica', + 'INR - Indian Rupee' => 'INR - Rupia india', + 'JPY - Japanese Yen' => 'JPY - Yen japonés', + 'NZD - New Zealand Dollar' => 'NZD - Dólar de Nueva Zelanda', + 'PEN - Peruvian Sol' => 'PEN - Sol peruano', + 'RSD - Serbian dinar' => 'RSD - Dinar serbio', + 'CNY - Chinese Yuan' => 'CNY - Yuan Chino', + 'USD - US Dollar' => 'USD - Dólar estadounidense', + 'VES - Venezuelan Bolívar' => 'VES - Bolívar Venezolano', + 'Destination column' => 'Columna destino', + 'Move the task to another column when assigned to a user' => 'Mover la tarea a otra columna cuando sea asignada a un usuario(a)', + 'Move the task to another column when assignee is cleared' => 'Mover la tarea a otra columna cuando se elimine la persona asignada', + 'Source column' => 'Columna de origen', + 'Transitions' => 'Transiciones', + 'Executer' => 'Ejecutor', + 'Time spent in the column' => 'Horas ejecutadas en la columna', + 'Task transitions' => 'Transiciones de las tareas', + 'Task transitions export' => 'Exportar transiciones de las tareas', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Este reporte contiene todos los movimientos de columna por cada tarea con la fecha, el(la) usuario(a) y el tiempo transcurrido para cada transición', + 'Currency rates' => 'Tipos de cambio', + 'Rate' => 'Tarifa', + 'Change reference currency' => 'Cambiar moneda de referencia', + 'Reference currency' => 'Moneda de referencia', + 'The currency rate has been added successfully.' => 'El tipo de cambio ha sido añadido correctamente.', + 'Unable to add this currency rate.' => 'No se puede añadir este tipo de cambio.', + 'Webhook URL' => 'URL del Webhook', + '%s removed the assignee of the task %s' => '%s eliminó al asignado de la tarea %s', + 'Information' => 'Información', + 'Check two factor authentication code' => 'Verificar el código de autenticación de dos factores', + 'The two factor authentication code is not valid.' => 'El código de autenticación de dos factores no es válido', + 'The two factor authentication code is valid.' => 'El código de autenticación de dos factores es válido', + 'Code' => 'Código', + 'Two factor authentication' => 'Autenticación de dos factores', + 'This QR code contains the key URI: ' => 'Este código QR contiene la clave URI: ', + 'Check my code' => 'Verificar mi código', + 'Secret key: ' => 'Clave secreta: ', + 'Test your device' => 'Pruebe su dispositivo', + 'Assign a color when the task is moved to a specific column' => 'Asignar un color cuando la tarea se mueve a una columna específica', + '%s via Kanboard' => '%s vía Kanboard', + 'Burndown chart' => 'Gráfico de Tareas Pendientes', + 'This chart show the task complexity over the time (Work Remaining).' => 'Este gráfico muestra la complejidad de la tarea durante el tiempo (Trabajo restante)', + 'Screenshot taken %s' => 'Captura de pantalla tomada %s', + 'Add a screenshot' => 'Añadir una captura de pantalla', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Tomar una captura de pantalla y presione CTRL+V o ⌘+V para pegar aquí.', + 'Screenshot uploaded successfully.' => 'Captura de pantalla subida correctamente', + 'SEK - Swedish Krona' => 'SEK - Corona sueca', + 'Identifier' => 'Identificador', + 'Disable two factor authentication' => 'Deshabilitar la autenticación de dos factores', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => '¿Realmente desea desactivar la autenticación de dos factores para este usuario(a): "%s"?', + 'Edit link' => 'Editar enlace', + 'Start to type task title...' => 'Comenzar a escribir el título de la tarea ...', + 'A task cannot be linked to itself' => 'Una tarea no se puede vincular a sí misma', + 'The exact same link already exists' => 'Ya existe un enlace idéntico', + 'Recurrent task is scheduled to be generated' => 'La tarea periódica está programada para ser generada', + 'Score' => 'Puntuación', + 'The identifier must be unique' => 'El identificador debe ser único', + 'This linked task id doesn\'t exists' => 'El identificador de la tarea enlazada no existe', + 'This value must be alphanumeric' => 'Este valor debe ser alfanumérico', + 'Edit recurrence' => 'Editar recurrencia', + 'Generate recurrent task' => 'Generar tarea recurrente', + 'Trigger to generate recurrent task' => 'Disparador para generar tarea recurrente', + 'Factor to calculate new due date' => 'Factor para calcular la nueva fecha de vencimiento', + 'Timeframe to calculate new due date' => 'Plazo para calcular la nueva fecha de vencimiento', + 'Base date to calculate new due date' => 'Fecha base para calcular la nueva fecha de vencimiento', + 'Action date' => 'Fecha de acción', + 'Base date to calculate new due date: ' => 'Fecha base para calcular la nueva fecha de vencimiento: ', + 'This task has created this child task: ' => 'Esta tarea ha creado esta tarea hija: ', + 'Day(s)' => 'Día(s)', + 'Existing due date' => 'Fecha de vencimiento existente', + 'Factor to calculate new due date: ' => 'Factor para calcular la nueva fecha de vencimiento: ', + 'Month(s)' => 'Mes(es)', + 'This task has been created by: ' => 'Esta tarea ha sido creada por: ', + 'Recurrent task has been generated:' => 'Se ha generado una tarea recurrente:', + 'Timeframe to calculate new due date: ' => 'Plazo para calcular la nueva fecha de vencimiento: ', + 'Trigger to generate recurrent task: ' => 'Disparador para generar la tarea recurrente: ', + 'When task is closed' => 'Cuando la tarea está cerrada', + 'When task is moved from first column' => 'Cuando se mueve la tarea desde la primera columna', + 'When task is moved to last column' => 'Cuando la tarea se mueve a la última columna', + 'Year(s)' => 'Año(s)', + 'Project settings' => 'Configuración del proyecto', + 'Automatically update the start date' => 'Actualizar automáticamente la fecha de inicio', + 'iCal feed' => 'Alimentador de iCal', + 'Preferences' => 'Preferencias', + 'Security' => 'Seguridad', + 'Two factor authentication disabled' => 'Se ha inhabilitado la autenticación de dos factores.', + 'Two factor authentication enabled' => 'Autenticación de dos factores habilitada', + 'Unable to update this user.' => 'No se puede actualizar este usuario(a).', + 'There is no user management for personal projects.' => 'No hay gestión de usuario(a)s para proyectos privados.', + 'User that will receive the email' => 'Usuario(a) que recibirá el correo electrónico', + 'Email subject' => 'Asunto del correo electrónico', + 'Date' => 'Fecha', + 'Add a comment log when moving the task between columns' => 'Añadir un registro en los comentarios al mover la tarea entre columnas', + 'Move the task to another column when the category is changed' => 'Mover la tarea a otra columna cuando se cambia la categoría', + 'Send a task by email to someone' => 'Enviar una tarea por correo electrónico a alguien', + 'Reopen a task' => 'Reabrir una tarea', + 'Notification' => 'Notificación', + '%s moved the task #%d to the first swimlane' => '%s movió la tarea #%d al primer carril', + 'Swimlane' => 'Carril', + '%s moved the task %s to the first swimlane' => '%s movió la tarea %s al primer carril', + '%s moved the task %s to the swimlane "%s"' => '%s trasladó la tarea %s al carril "%s"', + 'This report contains all subtasks information for the given date range.' => 'Este informe contiene toda la información de las subtareas para el intervalo de fechas especificado.', + 'This report contains all tasks information for the given date range.' => 'Este informe contiene la información de todas las tareas para el intervalo de fechas dado.', + 'Project activities for %s' => 'Actividades del proyecto para %s', + 'view the board on Kanboard' => 'ver el tablero en Kanboard', + 'The task has been moved to the first swimlane' => 'La tarea se ha movido al primer carril', + 'The task has been moved to another swimlane:' => 'La tarea se ha trasladado a otro carril:', + 'New title: %s' => 'Nuevo título: %s', + 'The task is not assigned anymore' => 'La tarea ya no está asignada', + 'New assignee: %s' => 'Nuevo asignado: %s', + 'There is no category now' => 'No hay categoría ahora', + 'New category: %s' => 'Nueva categoría: %s', + 'New color: %s' => 'Nuevo color: %s', + 'New complexity: %d' => 'Nueva complejidad: %d', + 'The due date has been removed' => 'Se ha eliminado la fecha de vencimiento', + 'There is no description anymore' => 'Ya no hay ninguna descripción', + 'Recurrence settings has been modified' => 'Los parámetros de recurrencia se han modificado', + 'Time spent changed: %sh' => 'Tiempo ejecutado cambiado: %sh', + 'Time estimated changed: %sh' => 'Horas Presupuestadas cambiado: %sh', + 'The field "%s" has been updated' => 'Se ha actualizado el campo "%s"', + 'The description has been modified:' => 'La descripción ha sido modificada:', + 'Do you really want to close the task "%s" as well as all subtasks?' => '¿Realmente desea cerrar la tarea "%s", así como todas sus subtareas?', + 'I want to receive notifications for:' => 'Quiero recibir notificaciones para:', + 'All tasks' => 'Todas las tareas', + 'Only for tasks assigned to me' => 'Sólo para tareas asignadas a mí', + 'Only for tasks created by me' => 'Sólo para tareas creadas por mí', + 'Only for tasks created by me and tasks assigned to me' => 'Sólo para las tareas creadas por mí y asignadas a mí', + '%%Y-%%m-%%d' => '%%Y-%%m-%%d', + 'Total for all columns' => 'Total para todas las columnas', + 'You need at least 2 days of data to show the chart.' => 'Necesita al menos 2 días de datos para mostrar el gráfico.', + '<15m' => '<15m', + '<30m' => '<30m', + 'Stop timer' => 'Detener temporizador', + 'Start timer' => 'Iniciar el temporizador', + 'My activity stream' => 'Mi flujo de actividad', + 'Search tasks' => 'Buscar tareas', + 'Reset filters' => 'Restablecer filtros', + 'My tasks due tomorrow' => 'Mis tareas para mañana', + 'Tasks due today' => 'Tareas pendientes hoy', + 'Tasks due tomorrow' => 'Tareas para mañana', + 'Tasks due yesterday' => 'Tareas vencidas ayer', + 'Closed tasks' => 'Tareas cerradas', + 'Open tasks' => 'Tareas abiertas', + 'Not assigned' => 'No asignado', + 'View advanced search syntax' => 'Ver sintaxis de búsqueda avanzada', + 'Overview' => 'Visión general', + 'Board/Calendar/List view' => 'Vista de Tablero/Calendario/Lista', + 'Switch to the board view' => 'Cambiar a la vista de tablero', + 'Switch to the list view' => 'Cambiar a la vista de lista', + 'Go to the search/filter box' => 'Ir a la caja de búsqueda/filtro', + 'There is no activity yet.' => 'No hay actividad todavía.', + 'No tasks found.' => 'No se han encontrado tareas.', + 'Keyboard shortcut: "%s"' => 'Atajo de teclado: "%s"', + 'List' => 'Lista', + 'Filter' => 'Filtrar', + 'Advanced search' => 'Búsqueda Avanzada', + 'Example of query: ' => 'Ejemplo de consulta: ', + 'Search by project: ' => 'Buscar por proyecto: ', + 'Search by column: ' => 'Buscar por columna: ', + 'Search by assignee: ' => 'Buscar por asignado:', + 'Search by color: ' => 'Buscar por color: ', + 'Search by category: ' => 'Buscar por categoría: ', + 'Search by description: ' => 'Buscar por descripción: ', + 'Search by due date: ' => 'Buscar por fecha de vencimiento: ', + 'Average time spent in each column' => 'Tiempo de permanencia promedio en cada columna', + 'Average time spent' => 'Tiempo ejecutado promedio', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Este gráfico muestra el tiempo promedio invertido en cada columna para las últimas %d tareas.', + 'Average Lead and Cycle time' => 'Tiempo de Espera y de Ciclo promedio', + 'Average lead time: ' => 'Tiempo de espera promedio: ', + 'Average cycle time: ' => 'Tiempo del ciclo promedio: ', + 'Cycle Time' => 'Tiempo del Ciclo', + 'Lead Time' => 'Tiempo de Espera', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Este gráfico muestra el tiempo promedio de espera y de ciclo para las últimas %d tareas.', + 'Average time into each column' => 'Tiempo promedio en cada columna', + 'Lead and cycle time' => 'Tiempo de espera y de ciclo', + 'Lead time: ' => 'Tiempo de Espera: ', + 'Cycle time: ' => 'Tiempo del Ciclo: ', + 'Time spent in each column' => 'Tiempo empleado en cada columna', + 'The lead time is the duration between the task creation and the completion.' => 'El tiempo de espera es la duración entre la creación de la tarea y la finalización.', + 'The cycle time is the duration between the start date and the completion.' => 'El tiempo de ciclo es la duración entre la fecha de inicio y la finalización.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Si la tarea no está cerrada, se utiliza la hora actual en lugar de la fecha de finalización.', + 'Set the start date automatically' => 'Establecer automáticamente la fecha de inicio', + 'Edit Authentication' => 'Editar autenticación', + 'Remote user' => 'Usuario(a) remoto', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Los usuario(a)s remotos no almacenan su contraseña en la base de datos Kanboard, ejemplos: cuentas LDAP, Google y Github.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Si marca la casilla "Deshabilitar el formulario de inicio de sesión", las credenciales ingresadas en el formulario de inicio de sesión serán ignoradas.', + 'Default task color' => 'Color predeterminado de la tarea', + 'This feature does not work with all browsers.' => 'Esta función no funciona con todos los navegadores.', + 'There is no destination project available.' => 'No hay proyecto de destino disponible.', + 'Trigger automatically subtask time tracking' => 'Activar automáticamente el seguimiento de tiempo de la subtarea', + 'Include closed tasks in the cumulative flow diagram' => 'Incluir tareas cerradas en el diagrama de flujo acumulativo', + 'Current swimlane: %s' => 'Carril actual: %s', + 'Current column: %s' => 'Columna actual: %s', + 'Current category: %s' => 'Categoría actual: %s', + 'no category' => 'sin categoria', + 'Current assignee: %s' => 'Asignado actual: %s', + 'not assigned' => 'no asignado', + 'Author:' => 'Autor:', + 'contributors' => 'colaboradores', + 'License:' => 'Licencia:', + 'License' => 'Licencia', + 'Enter the text below' => 'Introduzca el texto a continuación', + 'Start date:' => 'Fecha de inicio:', + 'Due date:' => 'Fecha de vencimiento:', + 'People who are project managers' => 'Personas que son gerentes de proyecto', + 'People who are project members' => 'Personas que son miembros del proyecto', + 'NOK - Norwegian Krone' => 'NOK - Corona Noruega', + 'Show this column' => 'Mostrar esta columna', + 'Hide this column' => 'Ocultar esta columna', + 'End date' => 'Fecha final', + 'Users overview' => 'Visión general de los usuario(a)s', + 'Members' => 'Miembros', + 'Shared project' => 'Proyecto compartido', + 'Project managers' => 'Gerentes de Proyecto', + 'Projects list' => 'Lista de proyectos', + 'End date:' => 'Fecha final:', + 'Change task color when using a specific task link' => 'Cambiar el color de la tarea cuando se utiliza un enlace de tarea específico', + 'Task link creation or modification' => 'Creación o modificación de enlaces de tareas', + 'Milestone' => 'Hito', + 'Reset the search/filter box' => 'Restablecer la caja de búsqueda/filtro', + 'Documentation' => 'Documentación', + 'Author' => 'Autor', + 'Version' => 'Versión', + 'Plugins' => 'Complementos', + 'There is no plugin loaded.' => 'No hay ningún complemento cargado.', + 'My notifications' => 'Mis Notificaciones', + 'Custom filters' => 'Filtros personalizados', + 'Your custom filter has been created successfully.' => 'Su filtro personalizado se ha creado correctamente.', + 'Unable to create your custom filter.' => 'No se puede crear el filtro personalizado.', + 'Custom filter removed successfully.' => 'Filtro personalizado eliminado correctamente.', + 'Unable to remove this custom filter.' => 'No se puede quitar este filtro personalizado.', + 'Edit custom filter' => 'Editar filtro personalizado', + 'Your custom filter has been updated successfully.' => 'Su filtro personalizado se ha actualizado correctamente.', + 'Unable to update custom filter.' => 'No se puede actualizar el filtro personalizado.', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Nuevo archivo adjunto en la tarea #%d: %s', + 'New comment on task #%d' => 'Nuevo comentario sobre la tarea #%d', + 'Comment updated on task #%d' => 'Comentario actualizado en la tarea #%d', + 'New subtask on task #%d' => 'Nueva subtarea en la tarea #%d', + 'Subtask updated on task #%d' => 'Subtarea actualizada en la tarea #%d', + 'New task #%d: %s' => 'Nueva tarea #%d: %s', + 'Task updated #%d' => 'Tarea actualizada #%d', + 'Task #%d closed' => 'Tarea #%d cerrada', + 'Task #%d opened' => 'Tarea #%d abierta', + 'Column changed for task #%d' => 'Columna modificada para la tarea #%d', + 'New position for task #%d' => 'Nueva posición para la tarea #%d', + 'Swimlane changed for task #%d' => 'Carril cambiado para la tarea #%d', + 'Assignee changed on task #%d' => 'Asignado cambiado en la tarea #%d', + '%d overdue tasks' => '%d tareas pendientes', + 'No notification.' => 'Sin notificación.', + 'Mark all as read' => 'marcar todo como leido', + 'Mark as read' => 'Marcar como leído', + 'Total number of tasks in this column across all swimlanes' => 'Número total de tareas en esta columna contando todos los carriles', + 'Collapse swimlane' => 'Colapsar el carril', + 'Expand swimlane' => 'Expandir el carril', + 'Add a new filter' => 'Añadir un nuevo filtro', + 'Share with all project members' => 'Compartir con todos los miembros del proyecto', + 'Shared' => 'Compartido', + 'Owner' => 'Propietario', + 'Unread notifications' => 'Notificaciones no leídas', + 'Notification methods:' => 'Métodos de notificación:', + 'Unable to read your file' => 'No se puede leer el archivo', + '%d task(s) have been imported successfully.' => '%d tarea(s) se han importado correctamente.', + 'Nothing has been imported!' => '¡Ningún dato importado!', + 'Import users from CSV file' => 'Importar usuario(a)s de un archivo CSV (valores separados por comas)', + '%d user(s) have been imported successfully.' => '%d usuario(s) se han importado correctamente.', + 'Comma' => 'Coma', + 'Semi-colon' => 'Punto y coma', + 'Tab' => 'Tabulación', + 'Vertical bar' => 'Barra vertical', + 'Double Quote' => 'Comillas dobles', + 'Single Quote' => 'Comillas simples', + '%s attached a file to the task #%d' => '%s adjuntó un archivo a la tarea #%d', + 'There is no column or swimlane activated in your project!' => '¡No hay columna ni carril activado en tu proyecto!', + 'Append filter (instead of replacement)' => 'Agregar el filtro (en lugar del reemplazo)', + 'Append/Replace' => 'Añadir/Reemplazar', + 'Append' => 'Adjuntar', + 'Replace' => 'Reemplazar', + 'Import' => 'Importar', + 'Change sorting' => 'Cambio de orden', + 'Tasks Importation' => 'Importación de Tareas', + 'Delimiter' => 'Delimitador', + 'Enclosure' => 'Contenedor', + 'CSV File' => 'Archivo CSV', + 'Instructions' => 'Instrucciones', + 'Your file must use the predefined CSV format' => 'Su archivo debe utilizar el formato CSV predefinido', + 'Your file must be encoded in UTF-8' => 'Su archivo debe estar codificado en UTF-8', + 'The first row must be the header' => 'La primera fila debe ser el encabezado', + 'Duplicates are not verified for you' => 'Los duplicados no se verifican', + 'The due date must use the ISO format: YYYY-MM-DD' => 'La fecha de vencimiento debe utilizar el formato ISO: AAAA-MM-DD', + 'Download CSV template' => 'Descargar plantilla CSV', + 'No external integration registered.' => 'No se ha registrado ninguna integración externa.', + 'Duplicates are not imported' => 'Los duplicados no se importan', + 'Usernames must be lowercase and unique' => 'Los nombres de usuario(a) deben estar en minúsculas y ser únicos', + 'Passwords will be encrypted if present' => 'Las contraseñas se cifrarán si están presentes', + '%s attached a new file to the task %s' => '%s adjunto un nuevo archivo a la tarea %s', + 'Link type' => 'Tipo de enlace', + 'Assign automatically a category based on a link' => 'Asignar automáticamente una categoría basada en un enlace', + 'BAM - Konvertible Mark' => 'BAM - Marco Convertible', + 'Assignee Username' => 'Usuario(a) asignado', + 'Assignee Name' => 'Nombre del asignado', + 'Groups' => 'Grupos', + 'Members of %s' => 'Miembros de %s', + 'New group' => 'Nuevo grupo', + 'Group created successfully.' => 'Grupo creado correctamente.', + 'Unable to create your group.' => 'No se puede crear su grupo.', + 'Edit group' => 'Editar grupo', + 'Group updated successfully.' => 'Grupo actualizado correctamente.', + 'Unable to update your group.' => 'No se puede actualizar su grupo.', + 'Add group member to "%s"' => 'Agregue el miembro del grupo a "%s"', + 'Group member added successfully.' => 'Miembro del grupo agregado correctamente.', + 'Unable to add group member.' => 'No se puede agregar el miembro del grupo.', + 'Remove user from group "%s"' => 'Quitar usuario del grupo "%s"', + 'User removed successfully from this group.' => 'Usuario(a) eliminadov correctamente de este grupo.', + 'Unable to remove this user from the group.' => 'No se puede eliminar este usuario(a) del grupo.', + 'Remove group' => 'Eliminar grupo', + 'Group removed successfully.' => 'Grupo eliminado correctamente.', + 'Unable to remove this group.' => 'No se pudo eliminar este grupo.', + 'Project Permissions' => 'Permisos del proyecto', + 'Manager' => 'Gerente', + 'Project Manager' => 'Gerente de proyecto', + 'Project Member' => 'Miembro del proyecto', + 'Project Viewer' => 'Visor de proyectos', + 'Your account is locked for %d minutes' => 'Tu cuenta está bloqueada durante %d minutos', + 'Invalid captcha' => 'CAPTCHA inválido', + 'The name must be unique' => 'El nombre debe ser único', + 'View all groups' => 'Ver todos los grupos', + 'There is no user available.' => 'No hay usuario(a)s disponibles.', + 'Do you really want to remove the user "%s" from the group "%s"?' => '¿Realmente desea eliminar el(la) usuario(a) "%s" del grupo "%s"?', + 'There is no group.' => 'No hay grupo.', + 'Add group member' => 'Agregar miembro del grupo', + 'Do you really want to remove this group: "%s"?' => '¿Desea realmente eliminar este grupo: "%s"?', + 'There is no user in this group.' => 'No hay ningún usuario(a) en este grupo.', + 'Permissions' => 'Permisos', + 'Allowed Users' => 'Usuario(a)s permitidos', + 'No specific user has been allowed.' => 'Ningún usuario(a) ha sido autorizado específicamente.', + 'Role' => 'Rol', + 'Enter user name...' => 'Introduzca su nombre de usuario(a)...', + 'Allowed Groups' => 'Grupos permitidos', + 'No group has been allowed.' => 'Ningún grupo se ha permitido específicamente.', + 'Group' => 'Grupo', + 'Group Name' => 'Nombre del grupo', + 'Enter group name...' => 'Introduzca el nombre del grupo ...', + 'Role:' => 'Rol:', + 'Project members' => 'Miembros del proyecto', + '%s mentioned you in the task #%d' => '%s te mencionó en la tarea #%d', + '%s mentioned you in a comment on the task #%d' => '%s te ha mencionado en un comentario sobre la tarea #%d', + 'You were mentioned in the task #%d' => 'Te mencionaron en la tarea #%d', + 'You were mentioned in a comment on the task #%d' => 'Usted fue mencionado en un comentario en la tarea #%d', + 'Estimated hours: ' => 'Horas estimadas: ', + 'Actual hours: ' => 'Horas reales: ', + 'Hours Spent' => 'Horas Gastadas', + 'Hours Estimated' => 'Horas Estimadas', + 'Estimated Time' => 'Tiempo Estimado', + 'Actual Time' => 'Tiempo Real', + 'Estimated vs actual time' => 'Horas Presupuestadas vs. tiempo real', + 'RUB - Russian Ruble' => 'RUB - Rublo Ruso', + 'Assign the task to the person who does the action when the column is changed' => 'Asigne la tarea a la persona que realiza la acción cuando se cambia la columna', + 'Close a task in a specific column' => 'Cerrar una tarea en una columna específica', + 'Time-based One-time Password Algorithm' => 'Algoritmo de contraseña de una sola vez basado en el tiempo', + 'Two-Factor Provider: ' => 'Proveedor de dos factores: ', + 'Disable two-factor authentication' => 'Deshabilitar la autenticación de dos factores', + 'Enable two-factor authentication' => 'Habilitar la autenticación de dos factores', + 'There is no integration registered at the moment.' => 'No hay ninguna integración registrada en este momento.', + 'Password Reset for Kanboard' => 'Restablecer contraseña para Kanboard', + 'Forgot password?' => '¿Olvidó su contraseña?', + 'Enable "Forget Password"' => 'Habilitar "Olvidar contraseña"', + 'Password Reset' => 'Restablecimiento de contraseña', + 'New password' => 'Nueva contraseña', + 'Change Password' => 'Cambiar la contraseña', + 'To reset your password click on this link:' => 'Para restablecer su contraseña, haga clic en este enlace:', + 'Last Password Reset' => 'Restablecer la última contraseña', + 'The password has never been reinitialized.' => 'La contraseña nunca ha sido reinicializada.', + 'Creation' => 'Creación', + 'Expiration' => 'Vencimiento', + 'Password reset history' => 'Historial de restablecimiento de contraseñas', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Todas las tareas de la columna "%s" y el carril "%s" han sido cerradas con éxito.', + 'Do you really want to close all tasks of this column?' => '¿Realmente desea cerrar todas las tareas de esta columna?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d tarea(s) en la columna "%s" y el carril "%s" se cerrará.', + 'Close all tasks in this column and this swimlane' => 'Cierre todas las tareas de esta columna', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Ningún plugin ha registrado un método de notificación de proyecto. Todavía puede configurar notificaciones individuales en su perfil de usuario(a).', + 'My dashboard' => 'Mi tablero', + 'My profile' => 'Mi perfil', + 'Project owner: ' => 'Propietario del proyecto: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'El identificador del proyecto es opcional y debe ser alfanumérico, por ejemplo: MIPROYECTO.', + 'Project owner' => 'Propietario del proyecto', + 'Personal projects do not have users and groups management.' => 'Los proyectos privados no tienen gestión de usuario(a)s y grupos.', + 'There is no project member.' => 'No hay ningún miembro del proyecto.', + 'Priority' => 'Prioridad', + 'Task priority' => 'Prioridad de la tarea', + 'General' => 'General', + 'Dates' => 'Fechas', + 'Default priority' => 'Prioridad predeterminada', + 'Lowest priority' => 'Menor prioridad', + 'Highest priority' => 'Mayor prioridad', + 'Close a task when there is no activity' => 'Cierra una tarea cuando no hay actividad', + 'Duration in days' => 'Duración en días', + 'Send email when there is no activity on a task' => 'Enviar correo electrónico cuando no hay actividad en una tarea', + 'Unable to fetch link information.' => 'No es posible obtener información de enlace.', + 'Daily background job for tasks' => 'Trabajo en segundo plano diario para las tareas', + 'Auto' => 'Auto', + 'Related' => 'Relacionado', + 'Attachment' => 'Archivo adjunto', + 'Web Link' => 'Enlace web', + 'External links' => 'Enlaces externos', + 'Add external link' => 'Añadir enlace externo', + 'Type' => 'Tipo', + 'Dependency' => 'Dependencia', + 'Add internal link' => 'Añadir enlace interno', + 'Add a new external link' => 'Añadir un nuevo enlace externo', + 'Edit external link' => 'Editar enlace externo', + 'External link' => 'Enlace externo', + 'Copy and paste your link here...' => 'Copia y pega el enlace aquí ...', + 'URL' => 'URL', + 'Internal links' => 'Enlaces internos', + 'Assign to me' => 'Asignármelo', + 'Me' => 'Yo', + 'Do not duplicate anything' => 'No duplique nada', + 'Projects management' => 'Gestión de proyectos', + 'Users management' => 'Gestión de usuario(a)s', + 'Groups management' => 'Gestión de grupos', + 'Create from another project' => 'Crear desde otro proyecto', + 'open' => 'abierto', + 'closed' => 'cerrado', + 'Priority:' => 'Prioridad:', + 'Reference:' => 'Referencia:', + 'Complexity:' => 'Complejidad:', + 'Swimlane:' => 'Carril:', + 'Column:' => 'Columna:', + 'Position:' => 'Posición:', + 'Creator:' => 'Creador:', + 'Time estimated:' => 'Horas Presupuestadas:', + '%s hours' => '%s horas', + 'Time spent:' => 'Tiempo usado:', + 'Created:' => 'Creado:', + 'Modified:' => 'Modificado:', + 'Completed:' => 'Terminado:', + 'Started:' => 'Empezado:', + 'Moved:' => 'Movido:', + 'Task #%d' => 'Tarea número %d', + 'Time format' => 'Formato de tiempo', + 'Start date: ' => 'Fecha de inicio: ', + 'End date: ' => 'Fecha final: ', + 'New due date: ' => 'Nueva fecha de vencimiento: ', + 'Start date changed: ' => 'Fecha de inicio cambiada: ', + 'Disable personal projects' => 'Inhabilitar proyectos privados', + 'Do you really want to remove this custom filter: "%s"?' => '¿Desea realmente eliminar este filtro personalizado: "%s"?', + 'Remove a custom filter' => 'Eliminar un filtro personalizado', + 'User activated successfully.' => 'Usuario(a) activado correctamente.', + 'Unable to enable this user.' => 'No se puede habilitar este usuario(a).', + 'User disabled successfully.' => 'El usuario(a) ha sido desactivado correctamente.', + 'Unable to disable this user.' => 'No se puede deshabilitar este usuario(a).', + 'All files have been uploaded successfully.' => 'Todos los archivos se han cargado correctamente.', + 'The maximum allowed file size is %sB.' => 'El tamaño máximo de archivo permitido es %sB.', + 'Drag and drop your files here' => 'Arrastra y suelta tus archivos aquí', + 'choose files' => 'Elija el archivo', + 'View profile' => 'Ver perfil', + 'Two Factor' => 'Dos factores', + 'Disable user' => 'Deshabilitar usuario(a)', + 'Do you really want to disable this user: "%s"?' => '¿Desea realmente desactivar este usuario(a): "%s"?', + 'Enable user' => 'Habilitar usuario(a)', + 'Do you really want to enable this user: "%s"?' => '¿De verdad quieres habilitar a este usuario(a): "%s"?', + 'Download' => 'Descargar', + 'Uploaded: %s' => 'Subido: %s', + 'Size: %s' => 'Tamaño: %s', + 'Uploaded by %s' => 'Subido por %s', + 'Filename' => 'Nombre del archivo', + 'Size' => 'Tamaño', + 'Column created successfully.' => 'Columna creada correctamente.', + 'Another column with the same name exists in the project' => 'Otra columna con el mismo nombre existe en el proyecto', + 'Default filters' => 'Filtros predeterminados', + 'Your board doesn\'t have any columns!' => '¡Su tablero no tiene columnas!', + 'Change column position' => 'Cambiar la posición de la columna', + 'Switch to the project overview' => 'Cambiar a la vista general del proyecto', + 'User filters' => 'Filtros de usuario(a)', + 'Category filters' => 'Filtros de categorías', + 'Upload a file' => 'Cargar un archivo', + 'View file' => 'Ver archivo', + 'Last activity' => 'Última actividad', + 'Change subtask position' => 'Cambiar la posición de la subtarea', + 'This value must be greater than %d' => 'Este valor no debe de ser más grande que %d', + 'Another swimlane with the same name exists in the project' => 'En el proyecto existe otro carril con el mismo nombre', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Ejemplo: https://example.kanboard.org/ (utilizado para generar URLs absolutas)', + 'Actions duplicated successfully.' => 'Acciones duplicadas correctamente.', + 'Unable to duplicate actions.' => 'No se pueden duplicar acciones.', + 'Add a new action' => 'Añadir una acción nueva', + 'Import from another project' => 'Importar desde otro proyecto', + 'There is no action at the moment.' => 'No hay acción en este momento.', + 'Import actions from another project' => 'Importar acciones de otro proyecto', + 'There is no available project.' => 'No hay proyecto disponible.', + 'Local File' => 'Archivo local', + 'Configuration' => 'Configuración', + 'PHP version:' => 'Versión de PHP:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'Versión del sistema operativo:', + 'Database version:' => 'Versión de base de datos:', + 'Browser:' => 'Navegador:', + 'Task view' => 'Vista de la tarea', + 'Edit task' => 'Editar tarea', + 'Edit description' => 'Editar Descripción', + 'New internal link' => 'Nuevo enlace interno', + 'Display list of keyboard shortcuts' => 'Mostrar lista de atajos de teclado', + 'Avatar' => 'Avatar', + 'Upload my avatar image' => 'Subir mi imagen de avatar', + 'Remove my image' => 'Eliminar mi imagen', + 'The OAuth2 state parameter is invalid' => 'El parámetro de estado OAuth2 no es válido', + 'User not found.' => 'Usuario(a) no encontrado(a).', + 'Search in activity stream' => 'Buscar en el flujo de actividades', + 'My activities' => 'Mis actividades', + 'Activity until yesterday' => 'Actividad hasta ayer', + 'Activity until today' => 'Actividad hasta hoy', + 'Search by creator: ' => 'Búsqueda por creador: ', + 'Search by creation date: ' => 'Búsqueda por fecha de creación: ', + 'Search by task status: ' => 'Búsqueda por estado de la tarea: ', + 'Search by task title: ' => 'Buscar por título de tarea: ', + 'Activity stream search' => 'Búsqueda de flujo de actividad', + 'Projects where "%s" is manager' => 'Proyectos donde "%s" es gerente', + 'Projects where "%s" is member' => 'Proyectos donde "%s" es miembro', + 'Open tasks assigned to "%s"' => 'Tareas abiertas asignadas a "%s"', + 'Closed tasks assigned to "%s"' => 'Tareas cerradas asignadas a "%s"', + 'Assign automatically a color based on a priority' => 'Asignar automáticamente un color basado en una prioridad', + 'Overdue tasks for the project(s) "%s"' => 'Tareas vencidas para el (los) proyecto(s) "%s"', + 'Upload files' => 'Subir archivos', + 'Installed Plugins' => 'Plugins instalados', + 'Plugin Directory' => 'Directorio de Plugins', + 'Plugin installed successfully.' => 'Plugin instalado correctamente.', + 'Plugin updated successfully.' => 'Plugin actualizado correctamente.', + 'Plugin removed successfully.' => 'Plugin eliminado correctamente.', + 'Subtask converted to task successfully.' => 'Subtarea convertida a la tarea con éxito.', + 'Unable to convert the subtask.' => 'No se puede convertir la subtarea.', + 'Unable to extract plugin archive.' => 'No se puede extraer archivo plugin.', + 'Plugin not found.' => 'No se ha encontrado el plugin.', + 'You don\'t have the permission to remove this plugin.' => 'No tienes permiso para eliminar este plugin.', + 'Unable to download plugin archive.' => 'No es posible descargar archivos plugin.', + 'Unable to write temporary file for plugin.' => 'No se puede escribir el archivo temporal para el plugin.', + 'Unable to open plugin archive.' => 'No se puede abrir el archivo de plugin', + 'There is no file in the plugin archive.' => 'No hay ningún archivo en el archivo de plugins.', + 'Create tasks in bulk' => 'Crear tareas en lote', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Tu instancia de Kanboard no está configurada para instalar complementos desde la interfaz de usuario(a).', + 'There is no plugin available.' => 'No hay un plugin disponible.', + 'Install' => 'Instalar', + 'Update' => 'Actualizar', + 'Up to date' => 'Hasta la fecha', + 'Not available' => 'No disponible', + 'Remove plugin' => 'Eliminar plugin', + 'Do you really want to remove this plugin: "%s"?' => '¿Realmente desea eliminar este plugin: "%s"?', + 'Uninstall' => 'Desinstalar', + 'Listing' => 'Listado', + 'Metadata' => 'Metadata', + 'Manage projects' => 'Gestionar proyectos', + 'Convert to task' => 'Convertir en tarea', + 'Convert sub-task to task' => 'Convertir subtarea en tarea', + 'Do you really want to convert this sub-task to a task?' => '¿Realmente desea convertir esta subtarea a una tarea?', + 'My task title' => 'Título de mi tarea', + 'Enter one task by line.' => 'Ingrese una tarea por línea.', + 'Number of failed login:' => 'Número de inicio de sesión fallido:', + 'Account locked until:' => 'Cuenta bloqueada hasta:', + 'Email settings' => 'Ajustes de correo electrónico', + 'Email sender address' => 'Dirección del remitente del correo electrónico', + 'Email transport' => 'Transporte por correo electrónico', + 'Webhook token' => 'Token Webhook', + 'Project tags management' => 'Gestión de etiquetas de proyecto', + 'Tag created successfully.' => 'Etiqueta creada correctamente.', + 'Unable to create this tag.' => 'No se puede crear esta etiqueta.', + 'Tag updated successfully.' => 'Etiqueta actualizada correctamente.', + 'Unable to update this tag.' => 'No se puede actualizar esta etiqueta.', + 'Tag removed successfully.' => 'Etiqueta eliminada correctamente.', + 'Unable to remove this tag.' => 'No se ha podido eliminar esta etiqueta.', + 'Global tags management' => 'Gestión de etiquetas globales', + 'Tags' => 'Etiquetas', + 'Tags management' => 'Gestión de etiquetas', + 'Add new tag' => 'Añadir nueva etiqueta', + 'Edit a tag' => 'Modificar una etiqueta', + 'Project tags' => 'Etiquetas del proyecto', + 'There is no specific tag for this project at the moment.' => 'No hay una etiqueta específica para este proyecto en este momento.', + 'Tag' => 'Etiqueta', + 'Remove a tag' => 'Eliminar una etiqueta', + 'Do you really want to remove this tag: "%s"?' => '¿Desea realmente eliminar esta etiqueta: "%s"?', + 'Global tags' => 'Etiquetas globales', + 'There is no global tag at the moment.' => 'No hay una etiqueta global en este momento.', + 'This field cannot be empty' => 'Este campo no puede estar vacío', + 'Close a task when there is no activity in a specific column' => 'Cierra una tarea cuando no hay actividad en una columna específica', + '%s removed a subtask for the task #%d' => '%s eliminó una subtarea para la tarea %d', + '%s removed a comment on the task #%d' => '%s eliminó un comentario para la tarea %d', + 'Comment removed on task #%d' => 'Comentario eliminado en la tarea #%d', + 'Subtask removed on task #%d' => 'Subtarea eliminada en la tarea #%d', + 'Hide tasks in this column in the dashboard' => 'Ocultar tareas en esta columna en el tablero', + '%s removed a comment on the task %s' => '%s eliminó un comentario para la tarea %s', + '%s removed a subtask for the task %s' => '%s eliminó una subtarea para la tarea %s', + 'Comment removed' => 'Comentario eliminado', + 'Subtask removed' => 'Sub-tarea eliminada', + '%s set a new internal link for the task #%d' => '%s estableció un nuevo enlace interno para la tarea %d', + '%s removed an internal link for the task #%d' => '%s eliminó un nuevo enlace interno para la tarea %d', + 'A new internal link for the task #%d has been defined' => 'Se ha definido un nuevo enlace interno para la tarea #%d', + 'Internal link removed for the task #%d' => 'Enlace interno eliminado para la tarea #%d', + '%s set a new internal link for the task %s' => '%s estableció un nuevo enlace interno para la tarea %s', + '%s removed an internal link for the task %s' => '%s eliminó un enlace interno para la tarea %s', + 'Automatically set the due date on task creation' => 'Establecer automáticamente la fecha de vencimiento en la creación de tareas', + 'Move the task to another column when closed' => 'Mover la tarea a otra columna cuando esté cerrada', + 'Move the task to another column when not moved during a given period' => 'Mover la tarea a otra columna cuando no se mueve durante un período determinado', + 'Dashboard for %s' => 'Tablero para %s', + 'Tasks overview for %s' => 'Visión general de tareas para %s', + 'Subtasks overview for %s' => 'Visión general de subtareas para %s', + 'Projects overview for %s' => 'Visión general de proyectos para %s', + 'Activity stream for %s' => 'Flujo de actividad para %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Asignar un color cuando la tarea se mueve a un carril específico', + 'Assign a priority when the task is moved to a specific swimlane' => 'Asignar una prioridad cuando la tarea se mueve a un carril específico', + 'User unlocked successfully.' => 'Usuario(a) desbloqueado(a) correctamente.', + 'Unable to unlock the user.' => 'No se puede desbloquear el usuario(a).', + 'Move a task to another swimlane' => 'Mueve una tarea a otro carril', + 'Creator Name' => 'Nombre del Creador', + 'Time spent and estimated' => 'Tiempo empleado y estimado', + 'Move position' => 'Mover la posición', + 'Move task to another position on the board' => 'Mover la tarea a otra posición en el tablero', + 'Insert before this task' => 'Insertar antes de esta tarea', + 'Insert after this task' => 'Insertar después de esta tarea', + 'Unlock this user' => 'Desbloquear este usuario(a)', + 'Custom Project Roles' => 'Roles de proyecto personalizados', + 'Add a new custom role' => 'Agregar un nuevo rol personalizado', + 'Restrictions for the role "%s"' => 'Restricciones para el rol "%s"', + 'Add a new project restriction' => 'Añadir una nueva restricción de proyecto', + 'Add a new drag and drop restriction' => 'Añadir una nueva restricción de arrastrar y soltar', + 'Add a new column restriction' => 'Agregar una nueva restricción de columna', + 'Edit this role' => 'Editar este rol', + 'Remove this role' => 'Eliminar este rol', + 'There is no restriction for this role.' => 'No hay restricción para este rol.', + 'Only moving task between those columns is permitted' => 'Sólo se permite el movimiento de una tarea entre esas columnas', + 'Close a task in a specific column when not moved during a given period' => 'Cierra una tarea en una columna específica cuando no se mueve durante un período determinado', + 'Edit columns' => 'Editar columnas', + 'The column restriction has been created successfully.' => 'La restricción de columna se ha creado correctamente.', + 'Unable to create this column restriction.' => 'No se puede crear esta restricción de columna.', + 'Column restriction removed successfully.' => 'Se ha eliminado correctamente la restricción de columna.', + 'Unable to remove this restriction.' => 'No se puede eliminar esta restricción.', + 'Your custom project role has been created successfully.' => 'El rol de proyecto personalizado se ha creado correctamente.', + 'Unable to create custom project role.' => 'No se puede crear un rol de proyecto personalizado.', + 'Your custom project role has been updated successfully.' => 'Su rol de proyecto personalizado se ha actualizado correctamente.', + 'Unable to update custom project role.' => 'No se puede actualizar el rol de proyecto personalizado.', + 'Custom project role removed successfully.' => 'El rol de proyecto personalizado se eliminó correctamente.', + 'Unable to remove this project role.' => 'No se pudo eliminar esta función del proyecto.', + 'The project restriction has been created successfully.' => 'La restricción del proyecto se ha creado con éxito.', + 'Unable to create this project restriction.' => 'No se puede crear esta restricción de proyecto.', + 'Project restriction removed successfully.' => 'Se ha eliminado correctamente la restricción de proyecto.', + 'You cannot create tasks in this column.' => 'No puede crear tareas en esta columna.', + 'Task creation is permitted for this column' => 'La creación de tareas está permitida para esta columna', + 'Closing or opening a task is permitted for this column' => 'El cierre o apertura de una tarea está permitido para esta columna', + 'Task creation is blocked for this column' => 'La creación de tareas está bloqueada para esta columna', + 'Closing or opening a task is blocked for this column' => 'El cierre o apertura de una tarea está bloqueado para esta columna', + 'Task creation is not permitted' => 'No se permite la creación de tareas', + 'Closing or opening a task is not permitted' => 'No se permite cerrar ni abrir una tarea', + 'New drag and drop restriction for the role "%s"' => 'Nueva restricción de arrastrar y soltar para el rol "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Las personas pertenecientes a este rol sólo podrán mover las tareas entre la columna de origen y la de destino.', + 'Remove a column restriction' => 'Eliminar una restricción de columna', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => '¿Realmente desea eliminar esta restricción de columna: "%s" a "%s"?', + 'New column restriction for the role "%s"' => 'Nueva restricción de columna para el rol "%s"', + 'Rule' => 'Regla', + 'Do you really want to remove this column restriction?' => '¿Realmente desea eliminar esta restricción de columna?', + 'Custom roles' => 'Roles personalizados', + 'New custom project role' => 'Nuevo rol de proyecto personalizado', + 'Edit custom project role' => 'Editar un rol de proyecto personalizado', + 'Remove a custom role' => 'Eliminar un rol personalizado', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => '¿Realmente desea eliminar este rol personalizado: "%s"? Todas las personas asignadas a este rol se convertirán en miembros del proyecto.', + 'There is no custom role for this project.' => 'No hay ningun rol personalizado para este proyecto.', + 'New project restriction for the role "%s"' => 'Nueva restricción de proyecto para el rol "%s"', + 'Restriction' => 'Restricción', + 'Remove a project restriction' => 'Eliminar una restricción de proyecto', + 'Do you really want to remove this project restriction: "%s"?' => '¿Realmente desea eliminar esta restricción de proyecto: "%s"?', + 'Duplicate to multiple projects' => 'Duplicar en varios proyectos', + 'This field is required' => 'Este campo es requerido', + 'Moving a task is not permitted' => 'No se permite mover una tarea', + 'This value must be in the range %d to %d' => 'Este valor debe estar en el rango %d a %d', + 'You are not allowed to move this task.' => 'No se le permite mover esta tarea.', + 'API User Access' => 'Acceso de usuario de API', + 'Preview' => 'Previsualizar', + 'Write' => 'Escribir', + 'Write your text in Markdown' => 'Redacta el texto en Markdown', + 'No personal API access token registered.' => 'No se ha registrado ningún token de acceso a API personal.', + 'Your personal API access token is "%s"' => 'Su token de acceso a la API personal es "%s"', + 'Remove your token' => 'Elimine su token', + 'Generate a new token' => 'Generar un nuevo token', + 'Showing %d-%d of %d' => 'Mostrando %d-%d de %d', + 'Outgoing Emails' => 'Correo electrónico saliente', + 'Add or change currency rate' => 'Añadir o cambiar el tipo de cambio', + 'Reference currency: %s' => 'Divisa de referencia: %s', + 'Add custom filters' => 'Añadir filtros personalizados', + 'Export' => 'Exportar', + 'Add link label' => 'Añadir etiqueta de enlace', + 'Incompatible Plugins' => 'Plugins incompatibles', + 'Compatibility' => 'Compatibilidad', + 'Permissions and ownership' => 'Permisos y propiedad', + 'Priorities' => 'Prioridades', + 'Close this window' => 'Cierra esta ventana', + 'Unable to upload this file.' => 'No se puede subir este archivo.', + 'Import tasks' => 'Importar tareas', + 'Choose a project' => 'Elija un proyecto', + 'Profile' => 'Perfil', + 'Application role' => 'Rol de la aplicación', + '%d invitations were sent.' => '%d invitaciones enviadas.', + '%d invitation was sent.' => '%d invitación fue enviada.', + 'Unable to create this user.' => 'No se puede crear este(a) usuario(a).', + 'Kanboard Invitation' => 'Invitación de Kanboard', + 'Visible on dashboard' => 'Visible en el tablero', + 'Created at:' => 'Creado en:', + 'Updated at:' => 'Actualizado en:', + 'There is no custom filter.' => 'No hay un filtro personalizado.', + 'New User' => 'Nuevo(a) usuario(a)', + 'Authentication' => 'Autenticación', + 'If checked, this user will use a third-party system for authentication.' => 'Si está marcado, este(a) usuario(a) utilizará un sistema de terceros para la autenticación.', + 'The password is necessary only for local users.' => 'La contraseña sólo es necesaria para lo(a)s usuario(a)s locales.', + 'You have been invited to register on Kanboard.' => 'Te han invitado a registrarte en Kanboard.', + 'Click here to join your team' => 'Haz clic aquí para unirte a tu equipo', + 'Invite people' => 'Invitar personas', + 'Emails' => ' correos electrónicos', + 'Enter one email address by line.' => 'Ingrese una dirección de correo electrónico por línea.', + 'Add these people to this project' => 'Añadir a estas personas a este proyecto', + 'Add this person to this project' => 'Añadir a esta persona a este proyecto', + 'Sign-up' => 'Regístrate', + 'Credentials' => 'Credenciales', + 'New user' => 'Añadir un(a) usuario(a)', + 'This username is already taken' => 'Este nombre de usuario(a) ya está en uso', + 'Your profile must have a valid email address.' => 'Su perfil debe tener una dirección de correo electrónico válido.', + 'TRL - Turkish Lira' => 'TRL - Lira Turca', + 'The project email is optional and could be used by several plugins.' => 'El correo electrónico del proyecto es opcional y podría ser usado por varios plugins', + 'The project email must be unique across all projects' => 'El correo electrónico del proyecto debe ser único entre todos los proyectos', + 'The email configuration has been disabled by the administrator.' => 'La configuración de correo electrónico se ha deshabilitado por el administrador', + 'Close this project' => 'Cerrar este proyecto', + 'Open this project' => 'Abrir este proyecto', + 'Close a project' => 'Cerrar un proyecto', + 'Do you really want to close this project: "%s"?' => '¿Realmente quiere cerrar este proyecto: "%s"?', + 'Reopen a project' => 'Reabrir un proyecto', + 'Do you really want to reopen this project: "%s"?' => '¿Realmente quiere reabrir este proyecto: "%s"?', + 'This project is open' => 'Este proyecto está abierdo', + 'This project is closed' => 'Este proyecto está cerrado', + 'Unable to upload files, check the permissions of your data folder.' => 'No se pueden cargar archivos, verifique los permisos de su carpeta de datos (data)', + 'Another category with the same name exists in this project' => 'Ya existe otra categoría con el mismo nombre en este proyecto', + 'Comment sent by email successfully.' => 'Comentario enviado exitosamente por correo electrónico', + 'Sent by email to "%s" (%s)' => 'Enviado por correo electrónico a "%s" (%s)', + 'Unable to read uploaded file.' => 'No se pudo leer el archivo cargado', + 'Database uploaded successfully.' => 'Base de datos cargada exitosamente', + 'Task sent by email successfully.' => 'Tarea enviada exitosamente por correo electrónico', + 'There is no category in this project.' => 'No hay categorías en este proyecto', + 'Send by email' => 'Enviar por correo electrónico', + 'Create and send a comment by email' => 'Crear y enviar un comentario por correo electrónico', + 'Subject' => 'Asunto', + 'Upload the database' => 'Cargar la base de datos', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Podría cargar la base de datos Sqlite descargada previamente (formato Gzip)', + 'Database file' => 'Archivo de Base de Datos', + 'Upload' => 'Cargar', + 'Your project must have at least one active swimlane.' => 'Su proyecto debe tener al menos un carril activo', + 'Project: %s' => 'Proyecto: %s', + 'Automatic action not found: "%s"' => 'No se encontró la acción automática: "%s"', + '%d projects' => '%d proyectos', + '%d project' => '%d proyecto', + 'There is no project.' => 'No hay proyecto', + 'Sort' => 'Ordenar', + 'Project ID' => 'ID Proyecto', + 'Project name' => 'Nombre del Proyecto', + 'Public' => 'Público', + 'Personal' => 'Privado', + '%d tasks' => '%d tareas', + '%d task' => '%d tarea', + 'Task ID' => 'ID Tarea', + 'Assign automatically a color when due date is expired' => 'Asignar un color automáticamente cuando la fecha de vencimiento haya expirado', + 'Total score in this column across all swimlanes' => 'Puntaje Total en esta columna para todos los carriles', + 'HRK - Kuna' => 'HRK - Kuna', + 'ARS - Argentine Peso' => 'ARS - Peso Argentino', + 'COP - Colombian Peso' => 'COP - Peso Colombiano', + '%d groups' => '%d grupos', + '%d group' => '%d grupo', + 'Group ID' => 'ID Grupo', + 'External ID' => 'ID Externa', + '%d users' => '%d usuario(a)s', + '%d user' => '%d usuario(a)', + 'Hide subtasks' => 'Ocultar subtareas', + 'Show subtasks' => 'Mostrar subtareas', + 'Authentication Parameters' => 'Parámetros de Autenticación', + 'API Access' => 'Acceso API', + 'No users found.' => 'No se encontraron usuario(a)s', + 'User ID' => 'ID del Usuario(a)', + 'Notifications are activated' => 'Las notificaciones están activadas', + 'Notifications are disabled' => 'Las notificaciones están deshabilitadas', + 'User disabled' => 'Usuario(a) deshabilitado(a)', + '%d notifications' => '%d notificaciones', + '%d notification' => '%d notificación', + 'There is no external integration installed.' => 'No se ha instalado ninguna integración externa.', + 'You are not allowed to update tasks assigned to someone else.' => 'No se le permite actualizar tareas asignadas a alguien más.', + 'You are not allowed to change the assignee.' => 'No se le permite cambiar la persona asignada.', + 'Task suppression is not permitted' => 'No está permitido la eliminación de tarea', + 'Changing assignee is not permitted' => 'No está permitido cambiar la persona asignada', + 'Update only assigned tasks is permitted' => 'Está permitido actualizar solo las tareas asignadas', + 'Only for tasks assigned to the current user' => 'Solo para las tareas asignadas al usuario(a) actual', + 'My projects' => 'Mis proyectos', + 'You are not a member of any project.' => 'No eres miembro de ningún proyecto.', + 'My subtasks' => 'Mis subtareas', + '%d subtasks' => '%d subtareas', + '%d subtask' => '%d subtarea', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Mover tareas entre esas columnas solo está permitido para las tareas asignadas al usuario(a) actual.', + '[DUPLICATE]' => '[DUPLICAR]', + 'DKK - Danish Krona' => 'DKK - Corona Danesa', + 'Remove user from group' => 'Eliminar usuario(a) del grupo', + 'Assign the task to its creator' => 'Asignar la tarea a su creador', + 'This task was sent by email to "%s" with subject "%s".' => 'Esta tarea fue enviada por correo a "%s" con el asunto "%s".', + 'Predefined Email Subjects' => 'Asuntos de correo predefinidos', + 'Write one subject by line.' => 'Escriba un asunto por línea.', + 'Create another link' => 'Crear otro enlace', + 'BRL - Brazilian Real' => 'BRL - Real brasileño', + 'Add a new Kanboard task' => 'Agregar una nueva tarea de Kanboard', + 'Subtask not started' => 'Subtarea no iniciada', + 'Subtask currently in progress' => 'Subtarea actualmente en progreso', + 'Subtask completed' => 'Subtarea completada', + 'Subtask added successfully.' => 'Subtarea agregada con éxito.', + '%d subtasks added successfully.' => '%d subtareas agregadas con éxito.', + 'Enter one subtask by line.' => 'Ingrese una subtarea por línea.', + 'Predefined Contents' => 'Contenidos predefinidos', + 'Predefined contents' => 'Contenidos predefinidos', + 'Predefined Task Description' => 'Descripción de tarea predefinida', + 'Do you really want to remove this template? "%s"' => '¿Realmente quieres eliminar esta plantilla? "%s"', + 'Add predefined task description' => 'Agregar descripción de tarea predefinida', + 'Predefined Task Descriptions' => 'Descripciones de tareas predefinidas', + 'Template created successfully.' => 'Plantilla creada con éxito', + 'Unable to create this template.' => 'No se puede crear esta plantilla.', + 'Template updated successfully.' => 'No se puede actualizar esta plantilla.', + 'Unable to update this template.' => 'No se puede actualizar esta plantilla.', + 'Template removed successfully.' => 'Plantilla eliminada con éxito.', + 'Unable to remove this template.' => 'No se puede eliminar esta plantilla.', + 'Template for the task description' => 'Plantilla para la descripción de la tarea', + 'The start date is greater than the end date' => 'La fecha de inicio es mayor que la fecha de finalización', + 'Tags must be separated by a comma' => 'Las etiquetas deben estar separadas por una coma', + 'Only the task title is required' => 'Solo se requiere el título de la tarea', + 'Creator Username' => 'Nombre del creador', + 'Color Name' => 'Nombre de color', + 'Column Name' => 'Nombre de columna', + 'Swimlane Name' => 'Nombre del carril', + 'Time Estimated' => 'Tiempo estimado', + 'Time Spent' => 'Tiempo empleado', + 'External Link' => 'Enlace externo', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Esta característica habilita el feed iCal, el feed RSS y la vista de la pizarra pública.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Detener el temporizador de todas las subtareas al mover una tarea a otra columna', + 'Subtask Title' => 'Título de subtarea', + 'Add a subtask and activate the timer when moving a task to another column' => 'Agregar una subtarea y activar el temporizador al mover una tarea a otra columna', + 'days' => 'dias', + 'minutes' => 'minutos', + 'seconds' => 'segundos', + 'Assign automatically a color when preset start date is reached' => 'Asigna automáticamente un color cuando se alcanza la fecha de inicio preestablecida', + 'Move the task to another column once a predefined start date is reached' => 'Mover la tarea a otra columna una vez que se alcanza una fecha de inicio predefinida', + 'This task is now linked to the task %s with the relation "%s"' => 'Esta tarea ahora está vinculada a la tarea %s con la relación "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'El enlace con la relación "%s" con la tarea %s se ha eliminado', + 'Custom Filter:' => 'Filtro personalizado:', + 'Unable to find this group.' => 'No se puede encontrar este grupo.', + '%s moved the task #%d to the column "%s"' => '%s movió la tarea #%d a la columna "%s', + '%s moved the task #%d to the position %d in the column "%s"' => '%s movió la tarea #%d a la posición %d en la columna "%s"', + '%s moved the task #%d to the swimlane "%s"' => '%s movió la tarea #%d al carril "%s"', + '%sh spent' => '%sh empleados', + '%sh estimated' => '%sh estimado', + 'Select All' => 'Seleccionar todo', + 'Unselect All' => 'Deseleccionar todo', + 'Apply action' => 'Aplicar acción', + 'Move selected tasks to another column or swimlane' => 'Mover tareas seleccionadas a otra columna', + 'Edit tasks in bulk' => 'Editar tareas en masa', + 'Choose the properties that you would like to change for the selected tasks.' => 'Elija las propiedades que le gustaría cambiar para las tareas seleccionadas.', + 'Configure this project' => 'Configurar este proyecto', + 'Start now' => 'Empezar ahora', + '%s removed a file from the task #%d' => '%s eliminó un archivo de la tarea #%d', + 'Attachment removed from task #%d: %s' => 'Adjunto eliminado de la tarea #%d: %s', + 'No color' => 'Sin color', + 'Attachment removed "%s"' => 'Adjunto eliminado "%s"', + '%s removed a file from the task %s' => '%s eliminó un archivo de la tarea %s', + 'Move the task to another swimlane when assigned to a user' => 'Mover la tarea a otro carril cuando se le asigna a un usuario(a)', + 'Destination swimlane' => 'Carril de destino', + 'Assign a category when the task is moved to a specific swimlane' => 'Asigne una categoría cuando la tarea se mueva a un carril específico', + 'Move the task to another swimlane when the category is changed' => 'Mueve la tarea a otro carril cuando se cambia la categoría', + 'Reorder this column by priority (ASC)' => 'Reordenar esta columna por prioridad (ASC)', + 'Reorder this column by priority (DESC)' => 'Reordenar esta columna por prioridad (DESC)', + 'Reorder this column by assignee and priority (ASC)' => 'Reordenar esta columna por asignación y prioridad (ASC)', + 'Reorder this column by assignee and priority (DESC)' => 'Reordenar esta columna por asignación y prioridad (DESC)', + 'Reorder this column by assignee (A-Z)' => 'Reordenar esta columna por persona asignada (A-Z)', + 'Reorder this column by assignee (Z-A)' => 'Reordenar esta columna por persona asignada (Z-A)', + 'Reorder this column by due date (ASC)' => 'Reordenar esta columna por fecha de vencimiento (ASC)', + 'Reorder this column by due date (DESC)' => 'Reordenar esta columna por fecha de vencimiento (DESC)', + 'Reorder this column by id (ASC)' => 'Reordenar esta columna por id (ASC)', + 'Reorder this column by id (DESC)' => 'Reordenar esta columna por id (DESC)', + '%s moved the task #%d "%s" to the project "%s"' => '%s movió la tarea #%d "%s" al proyecto "%s', + 'Task #%d "%s" has been moved to the project "%s"' => 'La tarea #%d "%s" se movió al proyecto "%s', + 'Move the task to another column when the due date is less than a certain number of days' => 'Mueva la tarea a otra columna cuando la fecha de vencimiento sea inferior a un cierto número de días', + 'Automatically update the start date when the task is moved away from a specific column' => 'Actualizar automáticamente la fecha de inicio cuando la tarea se mueve fuera de una columna específica', + 'HTTP Client:' => 'Cliente HTTP:', + 'Assigned' => 'Asignado', + 'Task limits apply to each swimlane individually' => 'Los límites de tareas se aplican a cada carril de forma individual', + 'Column task limits apply to each swimlane individually' => 'Los límites de tareas por columna se aplican a cada carril de forma individual', + 'Column task limits are applied to each swimlane individually' => 'Los límites de tareas por columna se aplican a cada carril de forma individual', + 'Column task limits are applied across swimlanes' => 'Los límites de tareas por columna se aplican a través de los carriles', + 'Task limit: ' => 'Límite de tareas:', + 'Change to global tag' => 'Cambiar a etiqueta global', + 'Do you really want to make the tag "%s" global?' => '¿Realmente quieres hacer global la etiqueta "%s"?', + 'Enable global tags for this project' => 'Habilitar etiquetas globales para este proyecto', + 'Group membership(s):' => 'Membresía(s) de grupo:', + '%s is a member of the following group(s): %s' => '%s es miembro del(los) siguiente(s) grupo(s): %s', + '%d/%d group(s) shown' => '%d/%d grupo(s) mostrado(s)', + 'Subtask creation or modification' => 'Creación o modificación de subtareas', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Asignar la tarea a un usuario específico cuando la tarea se mueve a un carril específico', + 'Comment' => 'Comentario', + 'Collapse vertically' => 'Contraer verticalmente', + 'Expand vertically' => 'Expandir verticalmente', + 'MXN - Mexican Peso' => 'MXN - Peso Mexicano', + 'Estimated vs actual time per column' => 'Tiempo estimado vs tiempo real por columna', + 'HUF - Hungarian Forint' => 'HUF - Florín Húngaro', + 'XBT - Bitcoin' => 'XBT - Bitcoin', + 'You must select a file to upload as your avatar!' => '¡Debes seleccionar un archivo para subir como tu avatar!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => '¡El archivo que subiste no es una imagen válida! (Solo se permiten *.gif, *.jpg, *.jpeg y *.png)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Establecer automáticamente la fecha de vencimiento cuando la tarea se mueve fuera de una columna específica', + 'No other projects found.' => 'No se encontraron otros proyectos.', + 'Tasks copied successfully.' => 'Tareas copiadas con éxito.', + 'Unable to copy tasks.' => 'No se pudieron copiar las tareas.', + 'Theme' => 'Tema', + 'Theme:' => 'Tema:', + 'Light theme' => 'Tema claro', + 'Dark theme' => 'Tema oscuro', + 'Automatic theme - Sync with system' => 'Tema automático - Sincronizar con el sistema', + 'Application managers or more' => 'Gerentes de aplicación o más', + 'Administrators' => 'Administradores', + 'Visibility:' => 'Visibilidad:', + 'Standard users' => 'Usuarios estándar', + 'Visibility is required' => 'La visibilidad es obligatoria', + 'The visibility should be an app role' => 'La visibilidad debe ser un rol de aplicación', + 'Reply' => 'Responder', + '%s wrote: ' => '%s escribió:', + 'Number of visible tasks in this column and swimlane' => 'Número de tareas visibles en esta columna y carril', + 'Number of tasks in this swimlane' => 'Número de tareas en este carril', + 'Unable to find another subtask in progress, you can close this window.' => 'No se puede encontrar otra subtarea en progreso, puede cerrar esta ventana.', + 'This theme is invalid' => 'Este tema no es válido', + 'This role is invalid' => 'Este rol no es válido', + 'This timezone is invalid' => 'Esta zona horaria no es válida', + 'This language is invalid' => 'Este idioma no es válido', + 'This URL is invalid' => 'Esta URL no es válida', + 'Date format invalid' => 'Formato de fecha inválido', + 'Time format invalid' => 'Formato de hora inválido', + 'Invalid Mail transport' => 'Transporte de correo inválido', + 'Color invalid' => 'Color inválido', + 'This value must be greater or equal to %d' => 'Este valor debe ser mayor o igual a %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Agregar un BOM al principio del archivo (requerido para Microsoft Excel)', + 'Just add these tag(s)' => 'Simplemente agregue esta(s) etiqueta(s)', + 'Remove internal link(s)' => 'Eliminar enlace(s) interno(s)', + 'Import tasks from another project' => 'Importar tareas de otro proyecto', + 'Select the project to copy tasks from' => 'Seleccione el proyecto desde el que copiar tareas', + 'The total maximum allowed attachments size is %sB.' => 'El tamaño total máximo permitido para los archivos adjuntos es %sB.', + 'Add attachments' => 'Agregar archivos adjuntos', + 'Task #%d "%s" is overdue' => 'La tarea #%d "%s" está vencida', + 'Enable notifications by default for all new users' => 'Habilitar notificaciones por defecto para todos los nuevos usuarios', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Asignar la tarea a su creador para columnas específicas si no se ha establecido un responsable manualmente', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Asignar una tarea al usuario conectado al cambiar de columna a la columna especificada si no hay ningún usuario asignado', +]; diff --git a/app/Locale/fa_IR/translations.php b/app/Locale/fa_IR/translations.php new file mode 100644 index 0000000..8b4a33f --- /dev/null +++ b/app/Locale/fa_IR/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => '٫', + 'number.thousands_separator' => '٬', + 'None' => 'هیچ', + 'Edit' => 'ویرایش', + 'Remove' => 'حذف', + 'Yes' => 'بلی', + 'No' => 'خیر', + 'cancel' => 'انصراف', + 'or' => 'یا', + 'Yellow' => 'زرد', + 'Blue' => 'آبی', + 'Green' => 'سبز', + 'Purple' => 'ارغوانی', + 'Red' => 'قرمز', + 'Orange' => 'نارنجی', + 'Grey' => 'خاکستری', + 'Brown' => 'قهوه ای', + 'Deep Orange' => 'نارنجی پررنگ', + 'Dark Grey' => 'خاکستری تیره', + 'Pink' => 'صورتی', + 'Teal' => 'آبی سیر', + 'Cyan' => 'یشمی', + 'Lime' => 'لیمویی', + 'Light Green' => 'سبز کمرنگ', + 'Amber' => 'زرد کهربایی', + 'Save' => 'ذخیره', + 'Login' => 'ورود', + 'Official website:' => 'وبسایت رسمی :', + 'Unassigned' => 'محول نشده', + 'View this task' => 'نمایش این کار', + 'Remove user' => 'حذف کاربر', + 'Do you really want to remove this user: "%s"?' => 'واقعاً می خواهید این کاربر حذف شود؟ : « %s » ', + 'All users' => 'تمامی کاربران', + 'Username' => 'نام کاربری', + 'Password' => 'گذرواژه', + 'Administrator' => 'مدیر سیستم', + 'Sign in' => 'ورود', + 'Users' => 'کاربران', + 'Forbidden' => 'ممنوع شده', + 'Access Forbidden' => 'دسترسی غیرمجاز', + 'Edit user' => 'ویرایش کاربر', + 'Logout' => 'خروج', + 'Bad username or password' => 'نام کاربری یا گذرواژه اشتباه', + 'Edit project' => 'ویرایش پروژه', + 'Name' => 'نام', + 'Projects' => 'پروژه ها', + 'No project' => 'پروژه ای نیست', + 'Project' => 'پروژه', + 'Status' => 'وضعیت', + 'Tasks' => 'کارها', + 'Board' => 'برد', + 'Actions' => 'فعالیت ها', + 'Inactive' => 'غیرفعال', + 'Active' => 'فعال', + 'Unable to update this board.' => 'امکان فعال کردن این برد وجود ندارد.', + 'Disable' => 'غیرفعال', + 'Enable' => 'فعال', + 'New project' => 'پروژه جدید', + 'Do you really want to remove this project: "%s"?' => 'واقعاً مایل به حذف این پروژه هستید : « %s » ؟', + 'Remove project' => 'حذف پروژه', + 'Edit the board for "%s"' => 'ویرایش برد برای « %s »', + 'Add a new column' => 'افزودن یک ستون جدید', + 'Title' => 'عنوان', + 'Assigned to %s' => 'اختصاص به %s', + 'Remove a column' => 'حذف یک ستون', + 'Unable to remove this column.' => 'امکان حذف این ستون وجود ندارد.', + 'Do you really want to remove this column: "%s"?' => 'واقعاً مایل به حذف این ستون هستید : « %s » ؟', + 'Settings' => 'تنظیمات', + 'Application settings' => 'تنظیمات برنامه', + 'Language' => 'زبان', + 'Webhook token:' => 'توکن Webhook:', + 'API token:' => 'توکن API:', + 'Database size:' => 'اندازه پایگاه داده :', + 'Download the database' => 'بارگیری پایگاه داده', + 'Optimize the database' => 'بهینه سازی پایگاه داده', + '(VACUUM command)' => '(دستور VACUUM)', + '(Gzip compressed Sqlite file)' => '(فایل فشرده sqlite Gzip)', + 'Close a task' => 'بستن یک کار', + 'Column' => 'ستون', + 'Color' => 'رنگ', + 'Assignee' => 'شخص محول شده', + 'Create another task' => 'ایجاد کاری دیگر', + 'New task' => 'کار جدید', + 'Open a task' => 'یک کار را باز کنید', + 'Do you really want to open this task: "%s"?' => 'واقعاً می خواهید این کار را باز کنید: "%s"؟', + 'Back to the board' => 'بازگشت به برد', + 'There is nobody assigned' => 'به هیچ کسی محول نشده', + 'Column on the board:' => 'ستون در برد', + 'Close this task' => 'این کار بسته شود', + 'Open this task' => 'این کار باز شود', + 'There is no description.' => 'هیچ توضیحی وجود ندارد', + 'Add a new task' => 'یک کار جدید اضافه کنید', + 'The username is required' => 'می بایست نام کاربری را وارد کنید', + 'The maximum length is %d characters' => 'بیشترین طول مجاز %d حرف است', + 'The minimum length is %d characters' => 'کمترین طول مجاز %d حرف است', + 'The password is required' => 'می بایست گذرواژه را وارد کنید', + 'This value must be an integer' => 'این مقدار باید عدد صحیح باشد', + 'The username must be unique' => 'نام کاربری باید یکتا باشد', + 'The user id is required' => 'شناسه کاربر الزامی است', + 'Passwords don\'t match' => 'گذرواژه مطابقت ندارد', + 'The confirmation is required' => 'تصدیق الزامی است', + 'The project is required' => 'پروژه الزامی است', + 'The id is required' => 'شناسه الزامی است', + 'The project id is required' => 'شناسه پروژه الزامی است', + 'The project name is required' => 'نام پروژه الزامی است', + 'The title is required' => 'عنوان الزامی است', + 'Settings saved successfully.' => 'تنظیمات با موفقیت ذخیره شدند.', + 'Unable to save your settings.' => 'تنظیمات شما ذخیره نمی شود.', + 'Database optimization done.' => 'بهینه سازی پایگاه داده انجام شد.', + 'Your project has been created successfully.' => 'پروژه شما با موفقیت ایجاد شد', + 'Unable to create your project.' => 'ایجاد پروژه شما امکان پذیر نیست.', + 'Project updated successfully.' => 'پروژه با موفقیت به روز رسانی شد.', + 'Unable to update this project.' => 'بروز رسانی این پروژه امکان پذیر نیست.', + 'Unable to remove this project.' => 'حذف این پروژه امکان پذیر نیست.', + 'Project removed successfully.' => 'پروژه با موفقیت حذف شد.', + 'Project activated successfully.' => 'پروژه با موفقیت فعال شد.', + 'Unable to activate 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.' => 'بروز رسانی کار شما امکان پذیر نیست.', + 'Task updated successfully.' => 'کار با موفقیت بروز رسانی شد.', + 'Unable to create your task.' => 'ایجاد کار شما امکان پذیر نیست.', + 'Task created successfully.' => 'کار با موفقیت ایجاد شد.', + 'User created successfully.' => 'کاربر با موفقیت ایجاد شد.', + 'Unable to create your user.' => 'ایجاد کاربر شما امکان پذیر نیست.', + 'User updated successfully.' => 'کاربر با موفقیت بروز رسانی شد.', + 'User removed successfully.' => 'کاربر با موفقیت حذف شد.', + 'Unable to remove this user.' => 'حذف این کاربر امکان پذیر نیست.', + 'Board updated successfully.' => 'برد با موفقیت بروز رسانی شد.', + 'Ready' => 'آماده', + 'Backlog' => 'بلاتکلیف', + 'Work in progress' => 'کار در حال انجام', + 'Done' => 'انجام شده', + 'Application version:' => 'نسخه برنامه:', + 'Id' => 'شناسه', + 'Public link' => 'پیوند عمومی', + 'Timezone' => 'منطقه زمانی', + 'Sorry, I didn\'t find this information in my database!' => 'شرمنده، من این اطلاعات را در پایگاه داده ام پیدا نکردم!', + 'Page not found' => 'صفحه پیدا نشد', + 'Complexity' => 'پیچیدگی', + 'Task limit' => 'محدودیت کار', + 'Task count' => 'تعداد کار', + 'User' => 'کاربر', + 'Comments' => 'نظرات', + 'Comment is required' => 'نظر الزامی است', + 'Comment added successfully.' => 'نظر با موفقیت افزوده شد', + 'Unable to create your comment.' => 'ایجاد نظر شما امکان پذیر نیست', + 'Due Date' => 'سر رسید', + 'Invalid date' => 'تاریخ نامعتبر', + 'Automatic actions' => 'اعمال خودکار', + 'Your automatic action has been created successfully.' => 'عمل خودکار شما با موفقیت ایجاد شد', + 'Unable to create your automatic action.' => 'ایجاد عمل خودکار شما امکان پذیر نیست.', + 'Remove an action' => 'حذف یک عمل', + 'Unable to remove this action.' => 'حذف این عمل امکان پذیر نیست.', + 'Action removed successfully.' => 'عمل با موفقیت حذف شد', + 'Automatic actions for the project "%s"' => 'اعمال خودکار برای پروژه "%s"', + 'Add an action' => 'یک عمل اضافه کنید', + 'Event name' => 'نام رویداد', + 'Action' => 'عمل', + 'Event' => 'رویداد', + 'When the selected event occurs execute the corresponding action.' => 'وقتی رویداد انتخابی رخ دهد، عمل متناظر با آن را اجرا کن.', + 'Next step' => 'گام بعدی', + 'Define action parameters' => 'تعریف پارامترهای عمل', + 'Do you really want to remove this action: "%s"?' => 'واقعاً می خواهید این عمل را حذف کنید: "%s" ؟', + 'Remove an automatic action' => 'حذف یک عمل خودکار', + 'Assign the task to a specific user' => 'تخصیص کار به یک کاربر خاص', + 'Assign the task to the person who does the action' => 'تخصیص کار به شخصی که عمل را انجام می دهد،', + 'Duplicate the task to another project' => 'کپی کار به پروژه ای دیگر', + 'Move a task to another column' => 'انتقال یک کار به ستونی دیگر', + 'Task modification' => 'دستکاری کار', + 'Task creation' => 'ایجاد کار', + 'Closing a task' => 'بستن یک کار', + 'Assign a color to a specific user' => 'تخصیص یک رنگ به یک کاربر خاص', + 'Position' => 'موقعیت', + 'Duplicate to project' => 'کپی به پروژه ای دیگر', + 'Duplicate' => 'کپی کردن', + 'Link' => 'پیوند', + 'Comment updated successfully.' => 'نظر با موفقیت بروز رسانی شد.', + 'Unable to update your comment.' => 'بروز رسانی نظر شما امکان پذیر نیست.', + 'Remove a comment' => 'حذف یک نظر', + 'Comment removed successfully.' => 'نظر با موفقیت حذف شد.', + 'Unable to remove this comment.' => 'حذف این نظر امکان پذیر نیست.', + 'Do you really want to remove this comment?' => 'واقعاً مایل به حذف این نظر هستید؟', + 'Current password for the user "%s"' => 'گذرواژه کنونی برای کاربر "%s"', + 'The current password is required' => 'گذرواژه کنونی الزامی است', + 'Wrong password' => 'گذرواژه اشتباه', + 'Unknown' => 'ناشناخته', + 'Last logins' => 'آخرین ورود ها', + 'Login date' => 'تاریخ ورود', + 'Authentication method' => 'شیوه تایید هویت', + 'IP address' => 'آدرس IP', + 'User agent' => 'واسط کاربری', + 'Persistent connections' => 'ارتباطات برقرار شده', + 'No session.' => 'نشستی وجود ندارد.', + 'Expiration date' => 'تاریخ انقضاء', + 'Remember Me' => 'مرا به خاطر بسپار', + 'Creation date' => 'تاریخ ایجاد', + 'Everybody' => 'همه', + 'Open' => 'باز', + 'Closed' => 'بسته', + 'Search' => 'جستجو', + 'Nothing found.' => 'چیزی یافت نشد', + 'Due date' => 'تاریخ سررسید', + 'Description' => 'شرح', + '%d comments' => '%d نظر', + '%d comment' => '%d نظر', + 'Email address invalid' => 'آدرس پست الکترونیکی اشتباه', + 'Your external account is not linked anymore to your profile.' => 'حساب خارجی شما دیگر به پروفایل شما پیوند ندارد', + 'Unable to unlink your external account.' => 'قطع پیوند از حساب کاربری شما امکانپذیر نیست', + 'External authentication failed' => 'تایید هویت خارجی با شکست مواجه شد', + 'Your external account is linked to your profile successfully.' => 'حساب خارجی شما به پروفایل شما با موفقیت پیوند خورد.', + 'Email' => 'پست الکترونیکی', + 'Task removed successfully.' => 'کار با موفقیت حذف شد.', + 'Unable to remove this task.' => 'حذف این کار امکانپذیر نیست.', + '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' => 'اختصاص خودکار یک دسته بندی بر اساس یک رنگ', + 'Task creation or modification' => 'ایجاد یا تغییر کار', + 'Category' => 'دسته بندی', + 'Category:' => 'دسته بندی:', + 'Categories' => 'دسته بندی ها', + 'Your category has been created successfully.' => 'دسته بندی شما با موفقیت ایجاد شد.', + 'This category has been updated successfully.' => 'دسته بندی با موفقیت بروز رسانی شد.', + 'Unable to update this category.' => 'امکان بروز رسانی این دسته بندی وجود ندارد.', + 'Remove a category' => 'حذف یک دسته بندی', + 'Category removed successfully.' => 'دسته بندی با موفقیت حذف شد.', + 'Unable to remove this category.' => 'حذف این دسته بندی امکان پذیر نیست.', + 'Category modification for the project "%s"' => 'تغییر دسته بندی برای پروژه "%s"', + 'Category Name' => 'نام دسته بندی', + 'Add a new category' => 'افزودن یک دسته بندی جدید', + 'Do you really want to remove this category: "%s"?' => 'واقعاً می خواهید دسته بندی "%s" را حذف کنید؟', + 'All categories' => 'همه دسته بندی ها', + 'No category' => 'بدون دسته بندی', + 'The name is required' => 'نام الزامی است', + 'Remove a file' => 'حذف یک فایل', + 'Unable to remove this file.' => 'حذف این فایل امکان پذیر نیست.', + 'File removed successfully.' => 'فایل با موفقیت حذف شد.', + 'Attach a document' => 'پیوست یک سند', + 'Do you really want to remove this file: "%s"?' => 'واقعاً از حذف فایل "%s" اطمینان دارید؟', + 'Attachments' => 'پیوست ها', + 'Edit the task' => 'ویرایش کار', + 'Add a comment' => 'افزودن یک نظر', + 'Edit a comment' => 'ویرایش یک نظر', + 'Summary' => 'خلاصه', + 'Time tracking' => 'پیگیری زمانی', + 'Estimate:' => 'تخمین:', + 'Spent:' => 'صرف شده:', + 'Do you really want to remove this sub-task?' => 'آیا واقعاً از حذف این کار فرعی اطمینان دارید؟', + 'Remaining:' => 'باقیمانده:', + 'hours' => 'ساعت', + 'estimated' => 'تخمینی', + 'Sub-Tasks' => 'کارهای فرعی', + 'Add a sub-task' => 'افزودن یک کار فرعی', + 'Original estimate' => 'تخمین اصلی', + 'Create another sub-task' => 'ایجاد یک کار فرعی دیگر', + 'Time spent' => 'زمان سپری شده', + 'Edit a sub-task' => 'ویرایش یک کار فرعی', + 'Remove a sub-task' => 'حذف یک کار فرعی', + 'The time must be a numeric value' => 'زمان باید بصورت عدد باشد', + 'Todo' => 'لیست جهت انجام کار', + 'In progress' => 'در حال انجام', + 'Sub-task removed successfully.' => 'کار فرعی با موفقیت حذف شد.', + 'Unable to remove this sub-task.' => 'حذف این کار فرعی امکان پذیر نیست.', + 'Sub-task updated successfully.' => 'کار فرعی با موفقیت بروز رسانی شد.', + 'Unable to update your sub-task.' => 'بروز رسانی کار فرعی شما امکان پذیر نیست.', + 'Unable to create your sub-task.' => 'ایجاد کار فرعی شما امکان پذیر نیست.', + 'Maximum size: ' => 'بیشترین اندازه: ', + 'Display another project' => 'نمایش پروژه دیگر', + 'Created by %s' => 'ایجاد شده توسط %s', + 'Tasks Export' => 'برون ریزی کار ها', + 'Start Date' => 'تاریخ شروع', + 'Execute' => 'اجرا', + 'Task Id' => 'شناسه کار', + 'Creator' => 'ایجاد کننده', + 'Modification date' => 'تاریخ تغییر', + 'Completion date' => 'تاریخ تکمیل', + 'Clone' => 'تکثیر', + 'Project cloned successfully.' => 'پروژه با موفقیت تکثیر شد.', + 'Unable to clone this project.' => 'تکثیر این پروژه امکان پذیر نیست.', + 'Enable email notifications' => 'فعال کردن آگاه سازی از طریق پست الکترونیکی', + 'Task position:' => 'جایگاه کار:', + 'The task #%d has been opened.' => 'کار #%d باز شد.', + 'The task #%d has been closed.' => 'کار #%d بسته شد.', + 'Sub-task updated' => 'کار فرعی بروز رسانی شد', + 'Title:' => 'عنوان:', + 'Status:' => 'وضعیت:', + 'Assignee:' => 'شخص محول شده:', + 'Time tracking:' => 'رهگیری زمان:', + 'New sub-task' => 'کار فرعی جدید', + 'New attachment added "%s"' => 'پیوست جدید افزوده شده: "%s"', + 'New comment posted by %s' => 'نظر جدید توسط %s ارسال شد.', + 'New comment' => 'نظر جدید', + 'Comment updated' => 'نظر بروز رسانی شد', + 'New subtask' => 'کار فرعی جدید', + 'I only want to receive notifications for these projects:' => 'می خواهم آگاه سازی ها را فقط برای این پروژه ها دریافت کنم:', + 'view the task on Kanboard' => 'نمایش کار در کن برد', + 'Public access' => 'دسترسی عمومی', + 'Disable public access' => 'غیر فعال کردن دسترسی عمومی', + 'Enable public access' => 'فعال کردن دسترسی عمومی', + 'Public access disabled' => 'دسترسی عمومی غیر فعال شد', + 'Move the task to another project' => 'انتقال کار به پروژه ای دیگر', + 'Move to project' => 'انتقال به پروژه ای دیگر', + 'Do you really want to duplicate this task?' => 'واقعاً می خواهید این کار را کپی کنید؟', + 'Duplicate a task' => 'کپی یک کار', + 'External accounts' => 'حساب های خارجی', + 'Account type' => 'نوع حساب', + 'Local' => 'محلی', + 'Remote' => 'از راه دور', + 'Enabled' => 'فعال', + 'Disabled' => 'غیر فعال', + 'Login:' => 'ورود:', + 'Full Name:' => 'نام کامل:', + 'Email:' => 'پست الکترونیکی:', + 'Notifications:' => 'آگاه سازی ها:', + 'Notifications' => 'آگاه سازی ها', + 'Account type:' => 'نوع حساب:', + 'Edit profile' => 'ویرایش پروفایل', + 'Change password' => 'تغییر گذرواژه', + 'Password modification' => 'تغییر گذرواژه', + 'External authentications' => 'تایید هویت خارجی', + 'Never connected.' => 'هرگز متصل نشده.', + 'No external authentication enabled.' => 'هیچ گونه تایید هویت خارجی فعال نشده.', + 'Password modified successfully.' => 'گذرواژه با موفقیت تغییر کرد.', + 'Unable to change the password.' => 'تغییر گذرواژه امکان پذیر نیست.', + 'Change category' => 'تغییر دسته بندی', + '%s updated the task %s' => 'کاربر %s کار %s را بروز رسانی کرد.', + '%s opened the task %s' => 'کاربر %s کار %s را باز کرد.', + '%s moved the task %s to the position #%d in the column "%s"' => 'کاربر %s کار %s را به موقعیت #%d در ستون "%s" منتقل کرد.', + '%s moved the task %s to the column "%s"' => 'کاربر %s کار %s را به ستون "%s" منتقل کرد.', + '%s created the task %s' => 'کاربر %s کار %s را ایجاد کرد.', + '%s closed the task %s' => 'کاربر %s کار %s را بست.', + '%s created a subtask for the task %s' => 'کاربر %s یک کار فرعی برای کار %s ایجاد کرد.', + '%s updated a subtask for the task %s' => 'کاربر %s یک کار فرعی برای کار %s بروز رسانی کرد.', + 'Assigned to %s with an estimate of %s/%sh' => 'محول شده به %s با تخمین %s/%sh', + 'Not assigned, estimate of %sh' => 'محول نشده، تخمین %sh', + '%s updated a comment on the task %s' => '%s یک نظر در وظیفه %s را به‌روزرسانی کرد', + '%s commented the task %s' => '%s روی وظیفه %s نظر داد', + '%s\'s activity' => 'فعالیت %s', + 'RSS feed' => 'خوراک RSS', + '%s updated a comment 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 را بروز رسانی کرد.', + '%s created the task #%d' => 'کاربر %s کار #%d را ایجاد کرد.', + '%s closed the task #%d' => 'کاربر %s کار #%d را بست.', + '%s opened the task #%d' => 'کاربر %s کار #%d را باز کرد.', + 'Activity' => 'فعالیت', + 'Default values are "%s"' => 'مقادیر پیشفرض این ها هستند: "%s"', + 'Default columns for new projects (Comma-separated)' => 'ستون های پیشفرض برای پروژه جدید - با کاما از هم جدا کنید', + 'Task assignee change' => 'تغییر شخص محول شده برای کار', + '%s changed the assignee of the task #%d to %s' => 'کاربر %s شخص محول شده برای کار #%d را به %s تغییر داد.', + '%s changed the assignee of the task %s to %s' => 'کاربر %s شخص محول شده برای کار %s را به %s تغییر داد.', + 'New password for the user "%s"' => 'گذرواژه جدید برای کاربر "%s"', + 'Choose an event' => 'یک رویداد را انتخاب کنید', + 'Create a task from an external provider' => 'ایجاد یک کار از یک فراهم کننده خارجی', + 'Change the assignee based on an external username' => 'تغییر شخص محول شده بر اساس یک نام کاربری خارجی', + 'Change the category based on an external label' => 'تغییر دسته بندی بر اساس یک برچسب لیبل خارجی', + 'Reference' => 'مرجع', + 'Label' => 'برچسب لیبل', + 'Database' => 'پایگاه داده', + 'About' => 'درباره', + 'Database driver:' => 'راه انداز پایگاه داده:', + 'Board settings' => 'تنظیمات برد', + 'Webhook settings' => 'تنظیمات Webhook', + 'Reset token' => 'بازنشانی توکن', + 'API endpoint:' => 'نقطه پایانی API:', + 'Refresh interval for personal board' => 'فاصله زمانی تازه سازی برد خصوصی', + 'Refresh interval for public board' => 'فاصله زمانی تازه سازی برد عمومی', + 'Task highlight period' => 'دوره اوج کار', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'دوره زمانی که در آن یک کار بعنوان تغییر یافته به تازگی قلمداد می شود - دوره را به ثانیه وارد کنید، برای غیر فعال کردن 0 وارد کنید، بصورت پیشفرض دو روز است.', + 'Frequency in second (60 seconds by default)' => 'تواتر به ثانیه، بصورت پیشفرض 60 ثانیه است.', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'تواتر به ثانیه، 0 برای غیر فعال کردن، بصورت پیشفرض 10 ثانیه است.', + 'Application URL' => 'آدرس اپلیکیشن', + 'Token regenerated.' => 'توکن بازتولید شد.', + 'Date format' => 'فرمت تاریخ', + 'ISO format is always accepted, example: "%s" and "%s"' => 'فرمت ایزو همواره مورد قبول است، مثال: "%s" و "%s"', + 'New personal project' => 'پروژه خصوصی جدید', + 'This project is personal' => 'این پروژه خصوصی است', + 'Add' => 'افزودن', + 'Start date' => 'تاریخ شروع', + 'Time estimated' => 'زمان تخمینی', + 'There is nothing assigned to you.' => 'هیچ چیزی به شما محول نشده است.', + 'My tasks' => 'کارهای من', + 'Activity stream' => 'جریان فعالیت', + 'Dashboard' => 'میز کار', + 'Confirmation' => 'تائیدیه', + 'Webhooks' => 'Webhook ها', + 'API' => 'واسط API', + 'Create a comment from an external provider' => 'ایجاد یک نظر از فراهم کننده خارجی', + 'Project management' => 'مدیریت پروژه', + 'Columns' => 'ستون ها', + 'Task' => 'کار', + 'Percentage' => 'درصد', + 'Number of tasks' => 'تعداد کارها', + 'Task distribution' => 'توزیع کار', + 'Analytics' => 'آمار', + 'Subtask' => 'کار فرعی', + 'User repartition' => 'توزیع مجدد کاربر', + 'Clone this project' => 'کپی این پروژه', + 'Column removed successfully.' => 'ستون با موفقیت حذف شد', + 'Not enough data to show the graph.' => 'داده کافی برای نشان دادن گراف وجود ندارد.', + 'Previous' => 'قبلی', + 'The id must be an integer' => 'شناسه باید از نوع عددی باشد', + 'The project id must be an integer' => 'شناسه پروژه باید از نوع عددی باشد', + 'The status must be an integer' => 'وضعیت باید از نوع عددی باشد', + 'The subtask id is required' => 'شناسه کار فرعی الزامی است', + 'The subtask id must be an integer' => 'شناسه کار فرعی باید از نوع عددی باشد', + 'The task id is required' => 'شناسه کار الزامی است', + 'The task id must be an integer' => 'شناسه کار باید یک عدد باشد', + 'The user id must be an integer' => 'شناسه کاربر باید از یک عدد باشد', + 'This value is required' => 'این مقدار الزامی است', + 'This value must be numeric' => 'این مقدار باید عددی باشد', + 'Unable to create this task.' => 'ایجاد این کار امکان پذیر نیست.', + 'Cumulative flow diagram' => 'نمودار جریان تجمعی', + 'Daily project summary' => 'خلاصه روزانه پروژه', + 'Daily project summary export' => 'برون ریزی خلاصه روزانه پروژه', + 'Exports' => 'برون ریزی ها', + 'This export contains the number of tasks per column grouped per day.' => 'این برون ریزی شامل تعداد کار در ستون است که بر اساس روز دسته بندی شده', + 'Active swimlanes' => 'مسیرهای شنای فعال', + 'Add a new swimlane' => 'افزودن یک مسیر شنا', + 'Default swimlane' => 'مسیر شنای پیشفرض', + 'Do you really want to remove this swimlane: "%s"?' => 'واقعاً می خواهید این مسیر شنا را حذف کنید : "%s" ؟', + 'Inactive swimlanes' => 'مسیرهای شنای غیر فعال', + 'Remove a swimlane' => 'حذف یک مسیر شنا', + 'Swimlane modification for the project "%s"' => 'تغییر مسیر شنا برای پروژه "%s"', + 'Swimlane removed successfully.' => 'مسیر شنا با موفقیت حذف شد.', + 'Swimlanes' => 'مسیرهای شنا', + 'Swimlane updated successfully.' => 'مسیر شنا با موفقیت بروز رسانی شد.', + 'Unable to remove this swimlane.' => 'حذف این مسیر شنا امکان پذیر نیست.', + 'Unable to update this swimlane.' => 'بروز رسانی این مسیر شنا امکان پذیر نیست.', + 'Your swimlane has been created successfully.' => 'مسیر شنای شما با موفقیت ایجاد شد.', + 'Example: "Bug, Feature Request, Improvement"' => 'مثال: "باگ, درخواست ویژگی, بهبود"', + 'Default categories for new projects (Comma-separated)' => 'دسته بندی ها برای پروژه جدید، با کاما از هم جدا کنید', + 'Integrations' => 'تلفیق ها', + 'Integration with third-party services' => 'تلفیق با خدمات شخص ثالث', + 'Subtask Id' => 'شناسه کار فرعی', + 'Subtasks' => 'کارهای فرعی', + 'Subtasks Export' => 'برون ریزی کارهای فرعی', + 'Task Title' => 'عنوان کار', + 'Untitled' => 'بدون عنوان', + 'Application default' => 'پیشفرض اپلیکیشن', + 'Language:' => 'زبان:', + 'Timezone:' => 'ناحیه زمانی:', + 'All columns' => 'همه ستون ها', + 'Next' => 'بعدی', + '#%d' => '#%d', + 'All swimlanes' => 'همه مسیرهای شنا', + 'All colors' => 'تمامی رنگ ها', + 'Moved to column %s' => 'منتقل شده به ستون %s', + 'User dashboard' => 'میز کارِ کاربر', + 'Allow only one subtask in progress at the same time for a user' => 'اجازه فقط یک کار فرعی در حال انجام در یک زمان برای یک کاربر', + 'Edit column "%s"' => 'ویرایش ستون "%s"', + '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?' => 'کدام بخش از پروژه را می خواهید کپی کنید؟', + 'Disallow login form' => 'غیرمجاز کردن فرم ورود', + 'Start' => 'شروع', + 'End' => 'پایان', + 'Task age in days' => 'عمر کار به روز', + 'Days in this column' => 'روزها در این ستون', + '%dd' => '%dروز', + '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' => 'تغییرات پیوند', + 'Opposite label' => 'برچسب مخالف', + 'Remove a link' => 'حذف یک برچسب', + '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' => '%d ساعت', + 'Expand tasks' => 'گستردن کارها', + 'Collapse tasks' => 'جمع کردن کارها', + 'Expand/collapse tasks' => 'جمع/گستردن کارها', + 'Close dialog box' => 'بستن کادر محاوره', + 'Submit a form' => 'ارسال یک فرم', + 'Board view' => 'نمای برد', + 'Keyboard shortcuts' => 'میانبرهای صفحه کلید', + 'Open board switcher' => 'باز کردن سویچر برد', + 'Application' => 'اپلیکیشن', + 'Compact view' => 'نمای جمع و جور', + 'Horizontal scrolling' => 'پیمایش افقی', + 'Compact/wide view' => 'نمای جمع و جور/عریض', + 'Currency' => 'واحد پول', + 'Personal project' => 'پروژه خصوصی', + 'AUD - Australian Dollar' => 'AUD - دلار استرالیا', + 'CAD - Canadian Dollar' => 'CAD - دلار کانادا', + 'CHF - Swiss Francs' => 'CHF - فرانک سویس', + 'Custom Stylesheet' => 'Stylesheet سفارشی', + 'EUR - Euro' => 'EUR - یورو اروپا', + 'GBP - British Pound' => 'GBP - پوند انگلیس', + 'INR - Indian Rupee' => 'INR - روپیه هند', + 'JPY - Japanese Yen' => 'JPY - ین ژاپن', + 'NZD - New Zealand Dollar' => 'NZD - دلار نیوزلند', + 'PEN - Peruvian Sol' => 'PEN - سول پرو', + 'RSD - Serbian dinar' => 'RSD - دینار صرب', + 'CNY - Chinese Yuan' => 'CNY - یوآن چین', + 'USD - US Dollar' => 'USD - دلار آمریکا', + 'VES - Venezuelan Bolívar' => 'VES - بولیوار ونزوئلا', + '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' => 'ستون مبدا', + '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' => 'تغییر مرجع واحد پول', + 'Reference currency' => 'مرجع واحد پول', + 'The currency rate has been added successfully.' => 'نرخ واحد پول با موفقیت افزوده شد.', + 'Unable to add this currency rate.' => 'افزودن این نرخ واحد پول امکان پذیر نیست.', + 'Webhook URL' => 'آدرس Webhook', + '%s removed the assignee of the task %s' => 'کاربر %s شخص محول شده برای کار %s را حذف کرد.', + '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' => 'احراز هویت دو مرحله ای', + 'This QR code contains the key URI: ' => 'این کد QR حاوی کلید URI است: ', + 'Check my code' => 'کد مرا بررسی کن', + 'Secret key: ' => 'کلید سرّی', + 'Test your device' => 'دستگاه خود را آزمایش کنید', + 'Assign a color when the task is moved to a specific column' => 'به هنگام انتقال کار به یک ستون خاص، یک رنگ اختصاص یابد', + '%s via Kanboard' => '%s از طریق کان بُرد', + 'Burndown chart' => 'نمودار کار باقی مانده', + 'This chart show the task complexity over the time (Work Remaining).' => 'این نمودار پیچیدگی کار طی زمان را نشان می دهد (کار باقیمانده)', + 'Screenshot taken %s' => 'اسکرین شات گرفته شد %s', + 'Add a screenshot' => 'افزودن اسکرین شات', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'یک اسکرین شات بگیرید و با زدن دکمه های کنترل+وی یا کامند+وی اینجا بچسبانید.', + 'Screenshot uploaded successfully.' => 'اسکرین شات با موفقیت بارگذاری شد.', + 'SEK - Swedish Krona' => 'SEK - کرون سوئد', + 'Identifier' => 'شناسه', + 'Disable two factor authentication' => 'غیرفعال کردن تایید هویت دو مرحله ای', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'آیا واقعاً می خواهید تایید هویت دو مرحله ای برای کاربر "%s" را غیر فعال کنید؟', + 'Edit link' => 'ویرایش پیوند', + 'Start to type task title...' => 'عنوان کار را بنویسد...', + 'A task cannot be linked to itself' => 'یک کار نمی تواند به خودش پیوند داده شود.', + 'The exact same link already exists' => 'همین لینک اکنون موجود است', + 'Recurrent task is scheduled to be generated' => 'کار متناوب برنامه ریزی شده تا تولید شود', + 'Score' => 'امتیاز', + 'The identifier must be unique' => 'شناسه باید یکتا باشد', + 'This linked task id doesn\'t exists' => 'این کار پیوند داده شده وجود ندارد', + 'This value must be alphanumeric' => 'این مقدار باید الفبایی باشد', + 'Edit recurrence' => 'ویرایش تناوب', + 'Generate recurrent task' => 'تولید کار متناوب', + 'Trigger to generate recurrent task' => 'تنظیم برای تولید کار متناوب', + 'Factor to calculate new due date' => 'فاکتور محاسبه تاریخ سررسید جدید', + 'Timeframe to calculate new due date' => 'دوره زمانی برای محاسبه تاریخ سررسید جدید', + 'Base date to calculate new due date' => 'تاریخ پایه برای محاسبه تاریخ سررسید جدید', + 'Action date' => 'تاریخ عمل', + 'Base date to calculate new due date: ' => 'تاریخ پایه برای محاسبه تاریخ سررسید جدید: ', + 'This task has created this child task: ' => 'این کار این کار فرزند را ایجاد کرده است:', + 'Day(s)' => 'روز(ها)', + 'Existing due date' => 'تاریخ سررسید موجود', + 'Factor to calculate new due date: ' => 'فاکتور محاسبه تاریخ سررسید جدید: ', + 'Month(s)' => 'ماه(ها)', + 'This task has been created by: ' => 'این کار ایجاد شده توسط: ', + 'Recurrent task has been generated:' => 'کار متناوب تولید شد: ', + 'Timeframe to calculate new due date: ' => 'دوره زمانی برای محاسبه تاریخ سررسید جدید: ', + 'Trigger to generate recurrent task: ' => 'تنظیم برای تولید کار متناوب: ', + 'When task is closed' => 'وقتی کار بسته شد', + 'When task is moved from first column' => 'وقتی کار از ستون اول منتقل شد', + 'When task is moved to last column' => 'وقتی کار به ستون آخر منتقل شد', + 'Year(s)' => 'سال(ها)', + 'Project settings' => 'تنظیمات پروژه', + 'Automatically update the start date' => 'بصورت خودکار تاریخ شروع بروز رسانی شود', + 'iCal feed' => 'خوراک iCal', + 'Preferences' => 'اولویت ها', + 'Security' => 'امنیت', + 'Two factor authentication disabled' => 'احراز هویت دو مرحله ای غیر فعال شده است', + 'Two factor authentication enabled' => 'احراز هویت دو مرحله ای فعال است', + 'Unable to update this user.' => 'بروز رسانی این کاربر امکان پذیر نیست.', + 'There is no user management for personal projects.' => 'مدیریت کاربر برای پروژه های خصوصی وجود ندارد.', + 'User that will receive the email' => 'کاربری که پست الکترونیکی را دریافت خواهد کرد', + 'Email subject' => 'موضوع پست الکترونیکی', + 'Date' => 'تاریخ', + 'Add a comment log when moving the task between columns' => 'افزودن یک لاگ نظر وقتی که کاری از یک ستون به ستون دیگر منتقل می شود', + 'Move the task to another column when the category is changed' => 'وقتی دسته بندی تغییر کرد، کار را به ستونی دیگر منتقل کن', + 'Send a task by email to someone' => 'ارسال کار توسط پست الکترونیکی به یک شخص', + 'Reopen a task' => 'بازگشایی مجدد یک کار', + 'Notification' => 'آگاه سازی', + '%s moved the task #%d to the first swimlane' => 'کاربر %s کار #%d را به مسیر شنای اول منتقل کرد', + 'Swimlane' => 'مسیر شنا', + '%s moved the task %s to the first swimlane' => 'کاربر %s کار %s را به مسیر شنای اول منتقل کرد', + '%s moved the task %s to the swimlane "%s"' => 'کاربر %s کار %s را به مسیر شنای "%s" منتقل کرد', + 'This report contains all subtasks information for the given date range.' => 'این گزارش حاوی همه اطلاعات کارهای فرعی برای بازه زمانی داده شده است.', + 'This report contains all tasks information for the given date range.' => 'این گزارش حاوی همه اطلاعات کارها برای بازه زمانی داده شده است.', + 'Project activities for %s' => 'فعالیت های پروژه برای %s', + 'view the board on Kanboard' => 'نمایش برد در کان بُرد', + 'The task has been moved to the first swimlane' => 'کار به مسیر شنای اول منتقل شد', + 'The task has been moved to another swimlane:' => 'کار به مسیر شنای دیگر منتقل شد:', + 'New title: %s' => 'عنوان جدید: %s', + 'The task is not assigned anymore' => 'کار به هیچ کسی محول نشده', + 'New assignee: %s' => 'شخص محول شده جدید: %s', + 'There is no category now' => 'الان هیچگونه دسته بندی وجود ندارد', + 'New category: %s' => 'دسته بندی جدید: %s', + 'New color: %s' => 'رنگ جدید: %s', + 'New complexity: %d' => 'پیچیدگی جدید: %d', + 'The due date has been removed' => 'تاریخ سررسید حذف شد', + 'There is no description anymore' => 'دیگر شرحی وجود ندارد', + 'Recurrence settings has been modified' => 'تکرار تنظیمات اصلاح شد', + 'Time spent changed: %sh' => 'زمان صرف شده تغییر کرد: %sh', + 'Time estimated changed: %sh' => 'زمان تخمینی تغییر کرد: %sh', + 'The field "%s" has been updated' => 'فیلد "%s" بروز رسانی شد', + 'The description has been modified:' => 'شرح تغییر کرد:', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'واقعاً می خواهید کار "%s" و همچنین کارهای فرعی آن را ببندید؟', + 'I want to receive notifications for:' => 'می خواهم آگاه سازی ها را برای این موارد دریافت کنم:', + 'All tasks' => 'تمامی کارها', + 'Only for tasks assigned to me' => 'فقط برای کارهایی که به من محول شده', + 'Only for tasks created by me' => 'فقط برای کارهایی که توسط من ایجاد شده', + 'Only for tasks created by me and tasks assigned to me' => 'فقط برای کارهایی که توسط من ایجاد شده و کارهایی که به من محول شده', + '%%Y-%%m-%%d' => '%%Y-%%m-%%d', + 'Total for all columns' => 'جمع برای تمامی ستون ها', + 'You need at least 2 days of data to show the chart.' => 'برای نمایش نمودار شما نیاز به حداقل 2 روز داده دارید.', + '<15m' => 'زیر یک ربع', + '<30m' => 'زیر نیم ساعت', + 'Stop timer' => 'توقف زمانسنج', + 'Start timer' => 'شروع زمانسنج', + 'My activity stream' => 'جریان فعالیت من', + 'Search tasks' => 'جستجوی کارها', + 'Reset filters' => 'بازنشانی فیلترها', + 'My tasks due tomorrow' => 'کارهای من طی فردا', + 'Tasks due today' => 'کارها طی امروز', + 'Tasks due tomorrow' => 'کارها طی فردا', + 'Tasks due yesterday' => 'کارها طی دیروز', + 'Closed tasks' => 'کارهای بسته شده', + 'Open tasks' => 'کارهای باز', + 'Not assigned' => 'محول نـشده', + 'View advanced search syntax' => 'نمایش دستور زبان جستجوی پیشرفته', + 'Overview' => 'بررسی اجمالی', + 'Board/Calendar/List view' => 'نمای برد/تقویم/لیست', + 'Switch to the board view' => 'تغییر به نمای برد', + 'Switch to the list view' => 'تغییر به نمای لیست', + 'Go to the search/filter box' => 'برو به جعبه جستجو/فیلتر', + 'There is no activity yet.' => 'هنوز فعالیتی وجود ندارد.', + 'No tasks found.' => 'کاری پیدا نشد.', + 'Keyboard shortcut: "%s"' => 'میانبر صفحه کلید: "%s"', + 'List' => 'لیست', + 'Filter' => 'فیلتر', + 'Advanced search' => 'جستجوی پیشرفته', + 'Example of query: ' => 'مثال پرس و جو: ', + 'Search by project: ' => 'جستجو بر اساس پروژه: ', + 'Search by column: ' => 'جستجو بر اساس ستون: ', + 'Search by assignee: ' => 'جستجو بر اساس شخص محول شده: ', + 'Search by color: ' => 'جستجو بر اساس رنگ: ', + 'Search by category: ' => 'جستجو بر اساس دسته بندی: ', + 'Search by description: ' => 'جستجو بر اساس شرح: ', + 'Search by due date: ' => 'جستجو بر اساس تاریخ سررسید: ', + 'Average time spent in each column' => 'زمان صرف شده میانگین در هر ستون', + 'Average time spent' => 'میانگین زمان صرف شده', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'این نمودار زمان صرف شده میانگین در هر ستون برای %d کار آخر را نشان می دهد.', + 'Average Lead and Cycle time' => 'میانگین زمان Lead و Cycle', + 'Average lead time: ' => 'میانگین lead time:‫ ', + 'Average cycle time: ' => 'میانگین cycle time:‫ ', + 'Cycle Time' => 'Cycle Time‫', + 'Lead Time' => 'Lead Time‫', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'این نمودار میانگین زمان Lead و Cycle را برای %d کار آخر طی زمان را نشان می دهد.', + 'Average time into each column' => 'میانگین زمانی برای هر ستون', + 'Lead and cycle time' => 'زمان Lead و Cycle', + 'Lead time: ' => 'Lead time: ‫', + 'Cycle time: ' => 'Cycle time: ‫', + 'Time spent in each column' => 'زمان صرف شده در هر ستون', + 'The lead time is the duration between the task creation and the completion.' => 'اصطلاح Lead time یعنی مدتی که بین ایجاد کار و اتمام کار سپری شده است.', + 'The cycle time is the duration between the start date and the completion.' => 'اصطلاح Cycle time یعنی مدتی که از تاریخ شروع تا تاریخ اتمام سپری شده است.', + 'If the task is not closed the current time is used instead of the completion date.' => 'اگر کار بسته نشده باشد، زمان کنونی بجای زمان اتمام استفاده شده است.', + 'Set the start date automatically' => 'تاریخ شروع بصورت خودکار تنظیم شود', + 'Edit Authentication' => 'ویرایش احراز هویت', + 'Remote user' => 'کاربر راه دور', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'کاربران راه دور گذرواژه خود را در پایگاه داده کان بُرد ذخیره نمی کنند، بطور مثال: LDAP, حساب های گوگل و گیتهاب.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'اگر گزینه "غیرمجاز کردن فرم ورود" را انتخاب کرده باشید، موارد وارد شده در فرم ورود نادیده گرفته خواهند شد.', + 'Default task color' => 'رنگ پیشفرض کار', + 'This feature does not work with all browsers.' => 'این ویژگی در تمامی مرورگرها کار نمی کند.', + 'There is no destination project available.' => 'هیچ پروژه مقصدی وجود ندارد.', + 'Trigger automatically subtask time tracking' => 'بصورت خودکار پیگیری زمان کار فرعی را راه اندازی کن', + 'Include closed tasks in the cumulative flow diagram' => 'کارهای بسته شده نیز در جریان دیاگرام انباشته شامل شود', + 'Current swimlane: %s' => 'مسیر شنای فعلی: %s', + 'Current column: %s' => 'ستون فعلی: %s', + 'Current category: %s' => 'دسته بندی فعلی: %s', + 'no category' => 'بدون دسته بندی', + 'Current assignee: %s' => 'شخص محول شده فعلی: %s', + 'not assigned' => 'محول نشده', + 'Author:' => 'نویسنده:', + 'contributors' => 'شرکت کنندگان', + 'License:' => 'گواهینامه:', + 'License' => 'گواهینامه', + 'Enter the text below' => 'متن زیر را وارد کنید', + 'Start date:' => 'تاریخ شروع:', + 'Due date:' => 'تاریخ سررسید:', + 'People who are project managers' => 'اشخاصی که مدیر پروژه هستند', + 'People who are project members' => 'اشخاصی که عضو پروژه هستند', + 'NOK - Norwegian Krone' => 'NOK - کرون نروژ', + 'Show this column' => 'نمایش این ستون', + 'Hide this column' => 'مخفی کردن این ستون', + 'End date' => 'تاریخ پایان', + 'Users overview' => 'بررسی اجمالی کاربران', + 'Members' => 'اعضاء', + 'Shared project' => 'پروژه مشترک', + 'Project managers' => 'مدیران پروژه', + 'Projects list' => 'لیست پروژه ها', + 'End date:' => 'تاریخ پایان:', + 'Change task color when using a specific task link' => 'رنگ کار وقتی که از پیوند یک کار مشخص استفاده می شود تغییر داده شود', + 'Task link creation or modification' => 'ایجاد یا تغییر پیوند کار', + 'Milestone' => 'نقطه عطف', + 'Reset the search/filter box' => 'بازنشانی جعبه جستجو/فیلتر', + 'Documentation' => 'مستندات', + 'Author' => 'نویسنده', + 'Version' => 'نسخه', + 'Plugins' => 'پلاگین', + 'There is no plugin loaded.' => 'هیچ پلاگینی بارگیری نشده.', + 'My notifications' => 'آگاه سازی های من', + 'Custom filters' => 'فیلترهای سفارشی', + 'Your custom filter has been created successfully.' => 'فیلتر سفارشی شما با موفقیت ایجاد شد.', + 'Unable to create your custom filter.' => 'ایجاد فیلتر سفارشی شما امکان پذیر نیست.', + 'Custom filter removed successfully.' => 'فیلتر سفارشی با موفقیت حذف شد.', + 'Unable to remove this custom filter.' => 'حذف این فیلتر سفارشی امکان پذیر نیست.', + 'Edit custom filter' => 'ویرایش فیلتر سفارشی', + 'Your custom filter has been updated successfully.' => 'فیلتر سفارشی شما با موفقیت بروز رسانی شد.', + 'Unable to update custom filter.' => 'بروز رسانی فیلتر سفارشی امکان پذیر نیست.', + 'Web' => 'وب', + 'New attachment on task #%d: %s' => 'پیوست جدید برای کار #%d: %s', + 'New comment on task #%d' => 'نظر جدید در کار #%d', + 'Comment updated on task #%d' => 'نظر در کار #%d بروز رسانی شد.', + 'New subtask on task #%d' => 'کار فرعی جدید در کار #%d', + 'Subtask updated on task #%d' => 'کار فرعی در کار #%d بروز شد', + 'New task #%d: %s' => 'کار جدید #%d: %s', + 'Task updated #%d' => 'کار بروز شد #%d', + 'Task #%d closed' => 'کار #%d بسته شد', + 'Task #%d opened' => 'کار #%d باز شد', + 'Column changed for task #%d' => 'ستون برای کار #%d تغییر کرد', + 'New position for task #%d' => 'موقعیت جدید برای کار #%d ', + 'Swimlane changed for task #%d' => 'مسیر شنا برای کار #%d تغییر کرد', + 'Assignee changed on task #%d' => 'شخص محول شده در کار #%d تغییر کرد', + '%d overdue tasks' => '%d کار عقب افتاده', + 'No notification.' => 'بدون آگاه سازی.', + 'Mark all as read' => 'همه را بعنوان خوانده شده علامتگذاری کن', + 'Mark as read' => 'علامتگذاری بعنوان خوانده شده', + 'Total number of tasks in this column across all swimlanes' => 'تعداد کل کارهای این ستون طی همه مسیرهای شنا', + 'Collapse swimlane' => 'جمع کردن مسیرهای شنا', + 'Expand swimlane' => 'گستردن مسیرهای شنا', + 'Add a new filter' => 'افزودن یک فیلتر جدید', + 'Share with all project members' => 'اشتراک با تمامی اعضای پروژه', + 'Shared' => 'به اشتراک گذاشته شده', + 'Owner' => 'صاحب', + 'Unread notifications' => 'آگاه سازی های خوانده نشده', + 'Notification methods:' => 'روش های آگاه سازی:', + 'Unable to read your file' => 'خواندن فایل شما امکان پذیر نیست', + '%d task(s) have been imported successfully.' => 'تعداد %d عدد کار با موفقیت درون ریزی شد.', + 'Nothing has been imported!' => 'هیچی چیزی درون ریزی نشد!', + 'Import users from CSV file' => 'درون ریزی کاربران از طریق فایل CSV', + '%d user(s) have been imported successfully.' => 'تعداد %d کاربر با موفقیت درون ریزی شدند.', + 'Comma' => 'کاما', + 'Semi-colon' => 'سمی کالن', + 'Tab' => 'تب', + 'Vertical bar' => 'تب عمودی', + 'Double Quote' => 'دابل کوت', + 'Single Quote' => 'سینگل کوت', + '%s attached a file to the task #%d' => 'کاربر %s یک فایل را به کار #%d پیوست کرد.', + 'There is no column or swimlane activated in your project!' => 'هیچ ستون یا مسیر شنای فعال در پروژه شما نیست!', + 'Append filter (instead of replacement)' => 'فیلتر افزوده شود (بجای اینکه جایگزین شود)', + 'Append/Replace' => 'افزودن/جایگزینی', + 'Append' => 'افزودن', + 'Replace' => 'جایگزینی', + 'Import' => 'درون ریزی', + 'Change sorting' => 'تغییر مرتب سازی', + 'Tasks Importation' => 'برون ریزی کردن کارها', + 'Delimiter' => 'جدا ساز', + 'Enclosure' => 'محصور ساز', + 'CSV File' => 'فایل CSV', + 'Instructions' => 'دستورالعمل ها', + 'Your file must use the predefined CSV format' => 'فایل شما باید از فرمت از پیش تعریف شده CSV استفاده کند', + 'Your file must be encoded in UTF-8' => 'کدبندی فایل شما باید UTF-8 باشد', + 'The first row must be the header' => 'اولین ردیف باید سربرگ باشد', + 'Duplicates are not verified for you' => 'تکراری ها برای شما بررسی نمی شوند', + 'The due date must use the ISO format: YYYY-MM-DD' => 'تاریخ سررسید باید از استاندارد ISO بصورت YYYY-MM-DD پیروی کند.', + 'Download CSV template' => 'بارگیری قالب CSV', + 'No external integration registered.' => 'هیچ تلفیق خارجی ثبت نشده.', + 'Duplicates are not imported' => 'تکراری ها درون ریزی نشدند', + 'Usernames must be lowercase and unique' => 'نام های کاربری باید با حروف کوچک بوده و یکتا باشند', + 'Passwords will be encrypted if present' => 'گذرواژه ها در صورت وجود رمزنگاری می شوند', + '%s attached a new file to the task %s' => 'کاربر %s یک فایل جدید به کار %s پیوست کرد.', + 'Link type' => 'نوع پیوند', + 'Assign automatically a category based on a link' => 'اختصاص خودکار یک دسته بندی بر اساس یک پیوند', + 'BAM - Konvertible Mark' => 'BAM - مارک تبدیل پذیر بوسنی هرزگوین', + 'Assignee Username' => 'نام کاربری شخص محول شده', + 'Assignee Name' => 'نام شخص محول شده', + 'Groups' => 'گروه ها', + 'Members of %s' => 'اعضای %s', + 'New group' => 'گروه جدید', + 'Group created successfully.' => 'گروه با موفقیت ایجاد شد.', + 'Unable to create your group.' => 'ایجاد گروه شما امکان پذیر نیست.', + 'Edit group' => 'ویرایش گروه', + 'Group updated successfully.' => 'گروه با موفقیت بروز رسانی شد.', + 'Unable to update your group.' => 'بروز رسانی گروه شما امکان پذیر نیست.', + 'Add group member to "%s"' => 'افزودن عضو گروه به "%s"', + 'Group member added successfully.' => 'عضو گروه با موفقیت افزوده شد.', + 'Unable to add group member.' => 'افزودن عضو گروه امکان پذیر نیست.', + 'Remove user from group "%s"' => 'حذف کاربر از گروه "%s"', + 'User removed successfully from this group.' => 'کاربر با موفقیت از این گروه حذف شد.', + 'Unable to remove this user from the group.' => 'حذف این کاربر از گروه امکان پذیر نیست.', + 'Remove group' => 'حذف گروه', + 'Group removed successfully.' => 'گروه با موفقیت حذف شد.', + 'Unable to remove this group.' => 'حذف این گروه امکان پذیر نیست.', + 'Project Permissions' => 'مجوز های پروژه', + 'Manager' => 'مدیر', + 'Project Manager' => 'مدیر پروژه', + 'Project Member' => 'عضو پروژه', + 'Project Viewer' => 'بازدید کننده پروژه', + 'Your account is locked for %d minutes' => 'حساب کاربری شما به مدت %d دقیقه مسدود شده', + 'Invalid captcha' => 'کد امنیتی کپچا نامعتبر است', + 'The name must be unique' => 'نام بایستی یکتا باشد', + 'View all groups' => 'نمایش همه گروه ها', + 'There is no user available.' => 'کاربری در دسترس نیست', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'واقعاً می خواهید کاربر "%s" را از گروه "%s" حذف کنید؟', + 'There is no group.' => 'گروهی وجود ندارد.', + 'Add group member' => 'افزون عضو گروه', + 'Do you really want to remove this group: "%s"?' => 'واقعاً می خواهید این گروه را حذف کنید؟ : "%s"', + 'There is no user in this group.' => 'کاربری در این گروه نیست.', + 'Permissions' => 'مجوز ها', + 'Allowed Users' => 'کاربران مجاز', + 'No specific user has been allowed.' => 'به کاربری بصورت خاص اجازه داده نشده است.', + 'Role' => 'نقش', + 'Enter user name...' => 'نام کاربری را وارد کنید...', + 'Allowed Groups' => 'گروه های مجاز', + 'No group has been allowed.' => 'گروهی بصورت خاص اجازه داده نشده است.', + 'Group' => 'گروه', + 'Group Name' => 'نام گروه', + 'Enter group name...' => 'نام گروه را وارد کنید...', + 'Role:' => 'نقش:', + 'Project members' => 'اعضای پروژه', + '%s mentioned you in the task #%d' => 'کاربر %s از شما در کار #%d نام بری کرده است.', + '%s mentioned you in a comment on the task #%d' => 'کاربر %s از شما در نظری که در مورد کار #%d بوده نام بری کرده است', + 'You were mentioned in the task #%d' => 'شما در کار #%d نام بری شدید.', + 'You were mentioned in a comment on the task #%d' => 'شما در نظری که در مورد کار #%d بوده نام بری شدید.', + 'Estimated hours: ' => 'ساعت های تخمینی: ', + 'Actual hours: ' => 'ساعت های واقعی: ', + 'Hours Spent' => 'ساعت های صرف شده', + 'Hours Estimated' => 'ساعت های تخمینی', + 'Estimated Time' => 'زمان تخمینی', + 'Actual Time' => 'زمان واقعی', + 'Estimated vs actual time' => 'مقایسه زمان تخمینی با زمان واقعی', + 'RUB - Russian Ruble' => 'RUB - روبل روسیه', + 'Assign the task to the person who does the action when the column is changed' => 'کار را به شخصی که عمل را وقتی ستون تغییر کرد انجام می دهد محول کن', + 'Close a task in a specific column' => 'یک کار را در یک ستون مشخص ببند', + 'Time-based One-time Password Algorithm' => 'الگوریتم گذرواژه یکبارمصرف مبتنی بر زمان', + 'Two-Factor Provider: ' => 'فراهم کننده دو مرحله ای: ', + 'Disable two-factor authentication' => 'احراز هویت دو مرحله ای غیر فعال گردد', + 'Enable two-factor authentication' => 'احراز هویت دو مرحله ای فعال گردد', + 'There is no integration registered at the moment.' => 'تلفیق ثبت شده در این لحظه وجود ندارد.', + 'Password Reset for Kanboard' => 'بازنشانی گذرواژه برای کان بُرد', + 'Forgot password?' => 'فراموشی گذرواژه؟', + 'Enable "Forget Password"' => 'فعال کردن "فراموشی گذرواژه"', + 'Password Reset' => 'بازنشانی گذرواژه', + 'New password' => 'گذرواژه جدید', + 'Change Password' => 'تغییر گذرواژه', + 'To reset your password click on this link:' => 'برای بازنشانی گذرواژه خود روی پیوند زیر کلیک کنید:', + 'Last Password Reset' => 'آخرین بازنشانی گذرواژه', + 'The password has never been reinitialized.' => 'گذرواژه هرگز از نو مقداردهی نشده است.', + 'Creation' => 'ایجاد', + 'Expiration' => 'انقضاء', + 'Password reset history' => 'تاریخچه بازنشانی گذرواژه', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'تمامی کارهای ستون "%s" و مسیر شنای "%s" با موفقیت بسته شدند.', + 'Do you really want to close all tasks of this column?' => 'واقعاً می خواهید همه کارهای این ستون را ببندید؟', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d کار در ستون "%s" و مسیر شنای "%s" بسته خواهند شد.', + 'Close all tasks in this column and this swimlane' => 'تمامی کارهای این ستون بسته شود', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'هیچ پلاگینی یک روش آگاه سازی در پروژه را ثبت نکرده است. شما هنوز می توانید آگاه سازی های مجزا را در پروفایل کاربری خود تنظیم کنید.', + 'My dashboard' => 'میز کار من', + 'My profile' => 'پروفایل من', + 'Project owner: ' => 'صاحب پروژه: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'شناسه پروژه اختیاری است و باید حتما از حروف انگلیسی یا عدد تشکیل شود. مثال MYPROJECT', + 'Project owner' => 'صاحب پروژه', + 'Personal projects do not have users and groups management.' => 'پروژه های خصوصی مدیریت کاربر و گروه ندارند', + 'There is no project member.' => 'عضوی در پروژه وجود ندارد', + 'Priority' => 'اولویت', + 'Task priority' => 'اولویت کار', + 'General' => 'عمومی', + 'Dates' => 'تاریخ ها', + 'Default priority' => 'اولویت پیشفرض', + 'Lowest priority' => 'کمترین اولویت', + 'Highest priority' => 'بیشترین اولویت', + 'Close a task when there is no activity' => 'یک کار را در صورت نبود فعالیت ببند', + 'Duration in days' => 'مدت به روز', + 'Send email when there is no activity on a task' => 'در صورت عدم فعالیت در یک کار، نامه الکترونیکی ارسال کن', + 'Unable to fetch link information.' => 'امکان دریافت اطلاعات پیوند وجود ندارد.', + 'Daily background job for tasks' => 'امور پس زمینه روزانه برای کارها', + 'Auto' => 'خودکار', + 'Related' => 'مرتبط', + 'Attachment' => 'پیوست', + 'Web Link' => 'پیوند وب', + 'External links' => 'پیوند های خارجی', + 'Add external link' => 'افزودن پیوند خارجی', + 'Type' => 'نوع', + 'Dependency' => 'وابستگی', + 'Add internal link' => 'افزودن پیوند داخلی', + 'Add a new external link' => 'افزودن یک پیوند خارجی جدید', + 'Edit external link' => 'ویرایش پیوند خارجی', + 'External link' => 'پیوند خارجی', + 'Copy and paste your link here...' => 'پیوند خود را اینجا کپی پیست کنید...', + 'URL' => 'آدرس', + 'Internal links' => 'پیوند های داخلی', + 'Assign to me' => 'به من محول کن', + 'Me' => 'من', + 'Do not duplicate anything' => 'هیچ چیزی را کپی نکن', + 'Projects management' => 'مدیریت پروژه', + 'Users management' => 'مدیریت کاربران', + 'Groups management' => 'مدیریت گروه ها', + 'Create from another project' => 'ایجاد از پروژه ای دیگر', + 'open' => 'باز', + 'closed' => 'بسته شده', + 'Priority:' => 'اولویت:', + 'Reference:' => 'مرجع:', + 'Complexity:' => 'پیچیدگی:', + 'Swimlane:' => 'مسیر شنا:', + 'Column:' => 'ستون:', + 'Position:' => 'موقعیت:', + 'Creator:' => 'ایجاد کننده:', + 'Time estimated:' => 'زمان تخمینی:', + '%s hours' => '%s ساعت', + 'Time spent:' => 'زمان صرف شده:', + 'Created:' => 'ایجاد شده:', + 'Modified:' => 'تغییر یافته:', + 'Completed:' => 'تکمیل شده:', + 'Started:' => 'شروع شده:', + 'Moved:' => 'منتقل شده:', + 'Task #%d' => 'کار #%d', + 'Time format' => 'فرمت زمان', + 'Start date: ' => 'تاریخ شروع: ', + 'End date: ' => 'تاریخ پایان: ', + 'New due date: ' => 'تاریخ سررسید جدید: ', + 'Start date changed: ' => 'تاریخ شروع تغییر کرده: ', + 'Disable personal projects' => 'غیر فعال کردن پروژه های خصوصی', + 'Do you really want to remove this custom filter: "%s"?' => 'واقعاً می خواهید این فیلتر سفارشی را حذف کنید؟ "%s"', + 'Remove a custom filter' => 'حذف یک فیلتر سفارشی', + 'User activated successfully.' => 'کاربر با موفقیت فعال شد.', + 'Unable to enable this user.' => 'فعال کردن این کاربر امکان پذیر نیست.', + 'User disabled successfully.' => 'کاربر با موفقیت غیر فعال شد.', + 'Unable to disable this user.' => 'غیرفعال کردن این کاربر امکان پذیر نیست.', + 'All files have been uploaded successfully.' => 'همه فایل ها با موفقیت بارگذاری شدند.', + 'The maximum allowed file size is %sB.' => 'بیشترین اندازه فایل مجاز %sB است.', + 'Drag and drop your files here' => 'فایل های خود را در اینجا کشیده و رها کنید.', + 'choose files' => 'انتخاب فایل ها', + 'View profile' => 'نمایش پروفایل', + 'Two Factor' => 'دو مرحله ای', + 'Disable user' => 'غیر فعال کردن کاربر', + 'Do you really want to disable this user: "%s"?' => 'واقعاً می خواهید این کاربر را غیر فعال کنید؟ "%s"', + 'Enable user' => 'فعال کردن کاربر', + 'Do you really want to enable this user: "%s"?' => 'واقعاً می خواهید این کاربر را فعال کنید؟ "%s"', + 'Download' => 'بارگیری', + 'Uploaded: %s' => 'بارگذاری: %s', + 'Size: %s' => 'اندازه: %s', + 'Uploaded by %s' => 'بارگذاری شده توسط %s', + 'Filename' => 'نام فایل', + 'Size' => 'اندازه', + 'Column created successfully.' => 'ستون با موفقیت ایجاد شد.', + 'Another column with the same name exists in the project' => 'ستونی دیگر با همین نام در پروژه وجود دارد', + 'Default filters' => 'فیلترهای پیشفرض', + 'Your board doesn\'t have any columns!' => 'برد شما هیچ ستونی ندارد!', + 'Change column position' => 'تغییر موقعیت ستون', + 'Switch to the project overview' => 'تغییر به حالت بررسی اجمالی پروژه', + 'User filters' => 'فیلترهای کاربر', + 'Category filters' => 'فیلترهای دسته بندی', + 'Upload a file' => 'بارگذاری یک فایل', + 'View file' => 'نمایش فایل', + 'Last activity' => 'آخرین فعالیت', + 'Change subtask position' => 'تغییر موقعیت کار فرعی', + 'This value must be greater than %d' => 'این مقدار باید از %d بزرگتر باشد', + 'Another swimlane with the same name exists in the project' => 'یک مسیر شنای دیگر با همین نام در پروژه وجود دارد', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'مثال: https://example.kanboard.org/ (برای تولید نشانی های کامل استفاده شده)', + 'Actions duplicated successfully.' => 'اعمال با موفقیت کپی شدند', + 'Unable to duplicate actions.' => 'کپی اعمال امکان پذیر نیست.', + 'Add a new action' => 'افزودن یک عمل جدید', + 'Import from another project' => 'درون ریزی از پروژه ای دیگر', + 'There is no action at the moment.' => 'در این لحظه هیچ عملی وجود ندارد.', + 'Import actions from another project' => 'درون ریزی اعمال از پروژه ای دیگر', + 'There is no available project.' => 'پروژه ای در دسترس نیست.', + 'Local File' => 'فایل محلی', + 'Configuration' => 'پیکربندی', + 'PHP version:' => 'نسخه PHP:', + 'PHP SAPI:' => 'رابط SAPI PHP:', + 'OS version:' => 'نسخه سیستم عامل:', + 'Database version:' => 'نسخه پایگاه داده:', + 'Browser:' => 'مرورگر:', + 'Task view' => 'نمای کار', + 'Edit task' => 'ویرایش کار', + 'Edit description' => 'ویرایش شرح', + 'New internal link' => 'لینک داخلی جدید', + 'Display list of keyboard shortcuts' => 'نمایش لیست از میانبرهای صفحه کلید', + 'Avatar' => 'آواتار', + 'Upload my avatar image' => 'بارگذاری تصویر آواتار من', + 'Remove my image' => 'حذف تصویر من', + 'The OAuth2 state parameter is invalid' => 'وضعیت پارامتر OAuth2 معتبر نیست.', + 'User not found.' => 'کاربر یافت نشد.', + 'Search in activity stream' => 'جستجو در جریان فعالیت', + 'My activities' => 'فعالیت های من', + 'Activity until yesterday' => 'فعالیت تا دیروز', + 'Activity until today' => 'فعالیت تا امروز', + 'Search by creator: ' => 'جستجو بر اساس ایجاد کننده: ', + 'Search by creation date: ' => 'جستجو بر اساس تاریخ ایجاد: ', + 'Search by task status: ' => 'جستجو بر اساس وضعیت کار: ', + 'Search by task title: ' => 'جستجو بر اساس عنوان کار: ', + 'Activity stream search' => 'جستجوی جریان فعالیت', + 'Projects where "%s" is manager' => 'پروژه هایی که در آن "%s" مدیر است', + 'Projects where "%s" is member' => 'پروژه هایی که در آن "%s" عضو است', + 'Open tasks assigned to "%s"' => 'کارهای بازی که به "%s" محول شده است', + 'Closed tasks assigned to "%s"' => 'کارهای بسته ای که به "%s" محول شده است', + 'Assign automatically a color based on a priority' => 'اختصاص خودکار یک رنگ بر اساس یک اولویت', + 'Overdue tasks for the project(s) "%s"' => 'کارهای عقب مانده برای پروژه (های) "%s"', + 'Upload files' => 'بارگذاری فایل ها', + 'Installed Plugins' => 'پلاگین های نصب شده', + 'Plugin Directory' => 'دایرکتوری پلاگین', + 'Plugin installed successfully.' => 'پلاگین با موفقیت نصب شد.', + 'Plugin updated successfully.' => 'پلاگین با موفقیت بروز رسانی شد.', + 'Plugin removed successfully.' => 'پلاگین با موفقیت حذف شد.', + 'Subtask converted to task successfully.' => 'کار فرعی با موفقیت به کار تبدیل شد.', + 'Unable to convert the subtask.' => 'تبدیل کار فرعی امکان پذیر نیست.', + 'Unable to extract plugin archive.' => 'برون ریزی آرشیو پلاگین امکان پذیر نیست.', + 'Plugin not found.' => 'پلاگین یافت نشد.', + 'You don\'t have the permission to remove this plugin.' => 'شما اجازه حذف این پلاگین را ندارید.', + 'Unable to download plugin archive.' => 'امکان دانلود آرشیو پلاگین وجود ندارد.', + 'Unable to write temporary file for plugin.' => 'نوشتن فایل موقت برای پلاگین امکان پذیر نیست.', + 'Unable to open plugin archive.' => 'باز کردن آرشیو پلاگین امکان پذیر نیست.', + 'There is no file in the plugin archive.' => 'فایلی در آرشیو پلاگین وجود ندارد.', + 'Create tasks in bulk' => 'ایجاد کارها بصورت انبوه', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'کان بُرد شما طوری تنظیم نشده که بتوان پلاگین ها را از رابط کاربری نصب کرد.', + 'There is no plugin available.' => 'پلاگینی در دسترس نیست.', + 'Install' => 'نصب', + 'Update' => 'بروز رسانی', + 'Up to date' => 'بروز', + 'Not available' => 'در دسترس نیست', + 'Remove plugin' => 'حذف پلاگین', + 'Do you really want to remove this plugin: "%s"?' => 'واقعاً می خواهید این پلاگین را حذف کنید؟ : "%s"', + 'Uninstall' => 'حذف نصب', + 'Listing' => 'لیست کردن', + 'Metadata' => 'متا دیتا', + 'Manage projects' => 'مدیریت پروژه ها', + 'Convert to task' => 'تبدیل به کار', + 'Convert sub-task to task' => 'تبدیل کار فرعی به کار', + 'Do you really want to convert this sub-task to a task?' => 'واقعاً می خواهید این کار فرعی را به کار تبدیل کنید؟', + 'My task title' => 'عنوان کار من', + 'Enter one task by line.' => 'یک کار را در یک خط وارد کنید.', + 'Number of failed login:' => 'تعداد ورودهای ناموفق:', + 'Account locked until:' => 'حساب قفل شده تا:', + 'Email settings' => 'تنظیمات پست الکترونیکی', + 'Email sender address' => 'آدرس فرستنده پست الکترونیکی', + 'Email transport' => 'ترابری پست الکترونیکی', + 'Webhook token' => 'توکن Webhook', + 'Project tags management' => 'مدیریت برچسب های پروژه', + 'Tag created successfully.' => 'برچسب با موفقیت ایجاد شد', + 'Unable to create this tag.' => 'ایجاد این برچسب امکان پذیر نیست.', + 'Tag updated successfully.' => 'برچسب با موفقیت بروز رسانی شد.', + 'Unable to update this tag.' => 'بروز رسانی این برچسب امکان پذیر نیست.', + 'Tag removed successfully.' => 'برچسب با موفقیت حذف شد.', + 'Unable to remove this tag.' => 'حذف این برچسب امکان پذیر نیست.', + 'Global tags management' => 'مدیریت جامع برچسب ها', + 'Tags' => 'برچسب ها', + 'Tags management' => 'مدیریت برچسب ها', + 'Add new tag' => 'افزودن برچسب جدید', + 'Edit a tag' => 'ویرایش یک برچسب', + 'Project tags' => 'برچسب های پروژه', + 'There is no specific tag for this project at the moment.' => 'برچسب خاصی برای این پروژه در حال حاضر موجود نیست.', + 'Tag' => 'برچسب', + 'Remove a tag' => 'حذف یک برچسب', + 'Do you really want to remove this tag: "%s"?' => 'واقعاً می خواهید برچسب : "%s" را حذف کنید؟', + 'Global tags' => 'برچسب های جامع', + 'There is no global tag at the moment.' => 'در حال حاضر برچسب جامعی وجود ندارد.', + 'This field cannot be empty' => 'این فیلد نمی تواند خالی باشد', + 'Close a task when there is no activity in a specific column' => 'وقتی هیچ فعالیتی در یک ستون مشخص وجود ندارد یک کار را ببند', + '%s removed a subtask for the task #%d' => 'کاربر %s یک کار فرعی برای کار #%d را حذف کرد.', + '%s removed a comment on the task #%d' => 'کاربر %s یک نظر برای کار #%d را حذف کرد.', + 'Comment removed on task #%d' => 'نظر برای کار #%d حذف شد.', + 'Subtask removed on task #%d' => 'کار فرعی برای کار #%d حذف شد.', + 'Hide tasks in this column in the dashboard' => 'کارهای این ستون را در میز کار مخفی کن', + '%s removed a comment on the task %s' => 'کاربر %s یک نظر برای کار %s را حذف کرد.', + '%s removed a subtask for the task %s' => 'کاربر %s یک کار فرعی برای کار %s را حذف کرد.', + 'Comment removed' => 'نظر حذف شد', + 'Subtask removed' => 'کار فرعی حذف شد', + '%s set a new internal link for the task #%d' => 'کاربر %s یک لینک داخلی برای کار #%d تنظیم کرد', + '%s removed an internal link for the task #%d' => 'کاربر %s یک لینک داخلی برای کار #%d را حذف کرد.', + 'A new internal link for the task #%d has been defined' => 'یک لینک داخلی برای کار #%d تعریف شد', + 'Internal link removed for the task #%d' => 'لینک داخلی برای کار #%d حذف شد.', + '%s set a new internal link for the task %s' => 'کاربر %s یک لینک داخلی برای کار %s تنظیم کرد', + '%s removed an internal link for the task %s' => 'کاربر %s یک لینک داخلی برای کار %s را حذف کرد.', + 'Automatically set the due date on task creation' => 'بصورت خودکار تاریخ سررسید را به هنگام ایجاد کار تنظیم کن', + 'Move the task to another column when closed' => 'کار را وقتی بسته شد به ستونی دیگر منتقل کن', + 'Move the task to another column when not moved during a given period' => 'کار را وقتی در طی یک دوره داده شده منتقل نشده باشد، به ستونی دیگر منتقل کن', + 'Dashboard for %s' => 'میز کارِ %s', + 'Tasks overview for %s' => 'بررسی اجمالی کارهایِ %s', + 'Subtasks overview for %s' => 'بررسی اجمالی کارهای فرعیِ %s', + 'Projects overview for %s' => 'بررسی اجمالی پروژه هایِ %s', + 'Activity stream for %s' => 'جریان فعالیتِ %s', + 'Assign a color when the task is moved to a specific swimlane' => 'وقتی کار به یک مسیر شنای مشخص منتقل شد، به آن یک رنگ اختصاص بده', + 'Assign a priority when the task is moved to a specific swimlane' => 'وقتی که کار به یک مسیر شنای مشخص منتقل شد یک اولویت به آن اختصاص بده', + 'User unlocked successfully.' => 'کاربر با موفقیت از حالت قفل خارج شد.', + 'Unable to unlock the user.' => 'خروج از حالت قفل برای کاربر امکان پذیر نیست.', + 'Move a task to another swimlane' => 'انتقال کار به مسیر شنای دیگر', + 'Creator Name' => 'نام ایجاد کننده', + 'Time spent and estimated' => 'زمان صرف شده و تخمینی', + 'Move position' => 'انتقال موقعیت', + 'Move task to another position on the board' => 'انتقال کار به موقعیتی دیگر در برد', + 'Insert before this task' => 'درج قبل از این کار', + 'Insert after this task' => 'درج بعد از این کار', + 'Unlock this user' => 'قفل گشایی این کاربر', + 'Custom Project Roles' => 'نقش های سفارشی پروژه', + 'Add a new custom role' => 'افزودن یک نقش سفارشی جدید', + 'Restrictions for the role "%s"' => 'محدودیت ها برای نقش "%s"', + 'Add a new project restriction' => 'افزودن یک محدودیت پروژه جدید', + 'Add a new drag and drop restriction' => 'افزودن یک محدودیت بکش و رهاکن جدید', + 'Add a new column restriction' => 'افزودن یک محدودیت ستونی جدید', + 'Edit this role' => 'ویرایش این نقش', + 'Remove this role' => 'حذف این نقش', + 'There is no restriction for this role.' => 'محدودیتی برای این نقش وجود ندارد.', + 'Only moving task between those columns is permitted' => 'تنها انتقال کار بین آن ستون ها مجاز است.', + 'Close a task in a specific column when not moved during a given period' => 'بستن یک کار در یک ستون خاص وقتی طی یک دوره داده شده منتقل نشده باشد', + 'Edit columns' => 'ویرایش ستون ها', + 'The column restriction has been created successfully.' => 'محدودیت ستون با موفقیت ایجاد شد.', + 'Unable to create this column restriction.' => 'ایجاد این محدودیت ستون امکان پذیر نیست.', + 'Column restriction removed successfully.' => 'محدودیت ستون با موفقیت حذف شد.', + 'Unable to remove this restriction.' => 'حذف این محدودیت امکان پذیر نیست.', + 'Your custom project role has been created successfully.' => 'نقش سفارشی پروژه شما با موفقیت ایجاد شد.', + 'Unable to create custom project role.' => 'ایجاد نقش پروژه امکان پذیر نیست.', + 'Your custom project role has been updated successfully.' => 'نقش سفارشی پروژه شما با موفقیت بروز رسانی شد.', + 'Unable to update custom project role.' => 'بروز رسانی نقش سفارشی پروژه شما امکان پذیر نیست.', + 'Custom project role removed successfully.' => 'نقش سفارشی پروژه با موفقیت حذف شد.', + 'Unable to remove this project role.' => 'حذف این نقش پروژه امکان پذیر نیست.', + 'The project restriction has been created successfully.' => 'محدودیت پروژه با موفقیت ایجاد شد.', + 'Unable to create this project restriction.' => 'ایجاد این محدودیت پروژه امکان پذیر نیست.', + 'Project restriction removed successfully.' => 'محدودیت پروژه با موفقیت حذف شد.', + 'You cannot create tasks in this column.' => 'نمی توانید در این ستون کار ایجاد کنید.', + 'Task creation is permitted for this column' => 'ایجاد کار برای این ستون مجاز است', + 'Closing or opening a task is permitted for this column' => 'بستن یا باز کردن کار برای این ستون مجاز است', + 'Task creation is blocked for this column' => 'ایجاد کار برای این ستون مسدود است', + 'Closing or opening a task is blocked for this column' => 'باز یا بستن یک کار برای این ستون مسدود است', + 'Task creation is not permitted' => 'ایجاد کار مجاز نیست', + 'Closing or opening a task is not permitted' => 'بستن یا باز کردن یک کار مجاز نیست', + 'New drag and drop restriction for the role "%s"' => 'محدودیت جدید کشیدن و رها کردن برای نقش "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'اشخاص متعلق به این نقش قادر خواهند بود کارها را فقط بین ستون مبدا و مقصد منتقل کنند.', + 'Remove a column restriction' => 'حذف یک محدودیت ستون', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'واقعاً می خواهید این محدودیت ستون را حذف کنید؟ : "%s" به "%s"', + 'New column restriction for the role "%s"' => 'محدودیت جدید ستون برای نقش "%s"', + 'Rule' => 'قانون', + 'Do you really want to remove this column restriction?' => 'آیا واقعاً می خواهید این محدودیت ستون را حذف کنید؟', + 'Custom roles' => 'نقش های سفارشی', + 'New custom project role' => 'نقش سفارشی جدید پروژه', + 'Edit custom project role' => 'ویرایش نقش سفارشی پروژه', + 'Remove a custom role' => 'حذف یک نقش سفارشی', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'واقعاً می خواهید این نقش سفارشی را حذف کنید؟ : "%s" تمامی اشخاصی که به این نقش اختصاص یافته اند تبدیل به عضو پروژه می شوند.', + 'There is no custom role for this project.' => 'هیچ نقش سفارشی برای این پروژه وجود ندارد.', + 'New project restriction for the role "%s"' => 'محدودیت پروژه جدید برای نقش "%s"', + 'Restriction' => 'محدودیت', + 'Remove a project restriction' => 'حذف یک محدودیت پروژه', + 'Do you really want to remove this project restriction: "%s"?' => 'آیا واقعاً می خواهید محدودیت این پروژه را حذف کنید؟ : "%s"', + 'Duplicate to multiple projects' => 'کپی به چندین پروژه', + 'This field is required' => 'این فیلد اجباری است', + 'Moving a task is not permitted' => 'انتقال یک کار مجاز نیست', + 'This value must be in the range %d to %d' => 'این مقدار باید از بازه %d تا %d باشد', + 'You are not allowed to move this task.' => 'شما مجاز به انتقال این کار نمی باشید', + 'API User Access' => 'API دسترسی کاربر', + 'Preview' => 'پیش نمایش', + 'Write' => 'نوشتن', + 'Write your text in Markdown' => 'متن خود را با فرمت مارکدان بنویسید (Markdown)', + 'No personal API access token registered.' => 'هیچ توکن API دسترسی شخصی ثبت نشده است.', + 'Your personal API access token is "%s"' => 'توکن دسترسی API شخصی شما برابر است با: "%s"', + 'Remove your token' => 'توکن خود را حذف کنید', + 'Generate a new token' => 'تولید یک توکن جدید', + 'Showing %d-%d of %d' => 'در حال نمایش %d تا %d از %d', + 'Outgoing Emails' => 'ایمیل های خروجی', + 'Add or change currency rate' => 'نرخ ارز را اضافه کنید یا تغییر دهید', + 'Reference currency: %s' => 'ارز مرجع: %s', + 'Add custom filters' => 'افزودن فیلتر سفارشی', + 'Export' => 'برون ریزی', + 'Add link label' => 'افزودن برچسب پیوند', + 'Incompatible Plugins' => 'پلاگین های ناسازگار', + 'Compatibility' => 'سازگاری', + 'Permissions and ownership' => 'مجوزها و مالکیت', + 'Priorities' => 'مشخصات', + 'Close this window' => 'این پنجره را ببند', + 'Unable to upload this file.' => 'بارگذاری این فایل امکان پذیر نیست', + 'Import tasks' => 'درون ریزی کارها', + 'Choose a project' => 'انتخاب یک پروژه', + 'Profile' => 'پروفایل', + 'Application role' => 'نقش اپلیکیشنی', + '%d invitations were sent.' => 'تعداد %d دعوتنامه ارسال شد.', + '%d invitation was sent.' => 'تعداد %d دعوتنامه ارسال شد.', + 'Unable to create this user.' => 'ایجاد این کاربر امکان پذیر نیست.', + 'Kanboard Invitation' => 'دعوتنامه کان بُرد', + 'Visible on dashboard' => 'قابل مشاهده در میز کار', + 'Created at:' => 'ایجاد شده در:', + 'Updated at:' => 'بروز شده در:', + 'There is no custom filter.' => 'هیچ فیلتر سفارشی وجود ندارد.', + 'New User' => 'کاربر جدید', + 'Authentication' => 'احراز هویت', + 'If checked, this user will use a third-party system for authentication.' => 'اگر انتخاب شده باشد، این کاربر از یک سیستم ثالث برای احراز هویت استفاده خواهد کرد.', + 'The password is necessary only for local users.' => 'گذرواژه فقط برای کاربران محلی اجباری است', + 'You have been invited to register on Kanboard.' => 'شما برای ثبت نام در کان بُرد دعوت شده اید.', + 'Click here to join your team' => 'برای پیوستن به تیم خود اینجا کلیک کنید', + 'Invite people' => 'دعوت افراد', + 'Emails' => 'ایمیل ها', + 'Enter one email address by line.' => 'هر ایمیل را در یک خط وارد کنید.', + 'Add these people to this project' => 'این افراد را به این پروژه اضافه کنید', + 'Add this person to this project' => 'این شخص را به این پروژه اضافه کنید', + 'Sign-up' => 'ثبت نام', + 'Credentials' => 'اعتبارنامه ها', + 'New user' => 'کاربر جدید', + 'This username is already taken' => 'این نام کاربری در حال حاضر استفاده شده است', + 'Your profile must have a valid email address.' => 'پروفایل شما باید یک آدرس ایمیل معتبر داشته باشد', + 'TRL - Turkish Lira' => 'TRL - لیر ترکیه', + 'The project email is optional and could be used by several plugins.' => 'ایمیل پروژه اختیاری است و می تواند توسط چندین پلاگین مورد استفاده قرار گیرد.', + 'The project email must be unique across all projects' => 'ایمیل پروژه باید بین همه پروژه ها یکتا باشد', + 'The email configuration has been disabled by the administrator.' => 'تنظیمات ایمیل توسط مدیر سیستم غیر فعال شده است', + 'Close this project' => 'بستن این پروژه', + 'Open this project' => 'باز کردن این پروژه', + 'Close a project' => 'بستن یک پروژه', + 'Do you really want to close this project: "%s"?' => 'واقعاً می خواهید این پروژه را ببندید؟ : "%s"', + 'Reopen a project' => 'بازگشایی مجدد یک پروژه', + 'Do you really want to reopen this project: "%s"?' => 'واقعاً می خواهید این پروژه را مجددا بازگشایی کنید؟ : "%s"', + 'This project is open' => 'این پروژه باز است', + 'This project is closed' => 'این پروژه بسته است', + 'Unable to upload files, check the permissions of your data folder.' => 'بارگذاری فایل امکان پذیر نیست، مجوزهای پوشه data را بررسی کنید.', + 'Another category with the same name exists in this project' => 'یک دسته بندی دیگر با همین نام در این پروژه وجود دارد', + 'Comment sent by email successfully.' => 'نظر با موفقیت توسط ایمیل ارسال شد.', + 'Sent by email to "%s" (%s)' => 'توسط ایمیل ارسال شد به "%s" (%s)', + 'Unable to read uploaded file.' => 'خواندن فایل بارگذاری شده امکان پذیر نیست.', + 'Database uploaded successfully.' => 'پایگاه داده با موفقیت بارگذاری شد.', + 'Task sent by email successfully.' => 'کار با موفقیت توسط ایمیل ارسال شد.', + 'There is no category in this project.' => 'هیچ گونه دسته بندی در این پروژه وجود ندارد.', + 'Send by email' => 'ارسال توسط ایمیل', + 'Create and send a comment by email' => 'ایجاد و ارسال یک نظر توسط ایمیل', + 'Subject' => 'موضوع', + 'Upload the database' => 'بارگذاری پایگاه داده', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'می توانید پایگاه داده Sqlite که قبلا بارگیری کردید را بارگذاری کنید (با فرمت Gzip)', + 'Database file' => 'فایل پایگاه داده', + 'Upload' => 'بارگذاری', + 'Your project must have at least one active swimlane.' => 'پروژه شما باید حداقل یک مسیر شنای فعال داشته باشد', + 'Project: %s' => 'پروژه: %s', + 'Automatic action not found: "%s"' => 'عمل خودکار یافت نشد: %s', + '%d projects' => '%d پروژه', + '%d project' => '%d پروژه', + 'There is no project.' => 'هیچ پروژه ای وجود ندارد', + 'Sort' => 'مرتب سازی', + 'Project ID' => 'شناسه پروژه', + 'Project name' => 'نام پروژه', + 'Public' => 'عمومی', + 'Personal' => 'خصوصی', + '%d tasks' => '%d کار', + '%d task' => '%d کار', + 'Task ID' => 'شناسه کار', + 'Assign automatically a color when due date is expired' => 'وقتی تاریخ سررسید منقضی شد، بصورت خودکار یک رنگ به آن اختصاص بده', + 'Total score in this column across all swimlanes' => 'امتیاز کل در این ستون بین تمامی مسیرهای شنا', + 'HRK - Kuna' => 'HRK - کونای کرواسی', + 'ARS - Argentine Peso' => 'ARS - پزو آرژانتین', + 'COP - Colombian Peso' => 'COP - پزو کلمبیا', + '%d groups' => '%d گروه', + '%d group' => '%d گروه', + 'Group ID' => 'شناسه گروه', + 'External ID' => 'شناسه خارجی', + '%d users' => '%d کاربر', + '%d user' => '%d کاربر', + 'Hide subtasks' => 'مخفی کردن کارهای فرعی', + 'Show subtasks' => 'نمایش کارهای فرعی', + 'Authentication Parameters' => 'پارامتر های احراز هویت', + 'API Access' => 'دسترسی API', + 'No users found.' => 'کاربری یافت نشد.', + 'User ID' => 'شناسه کاربر', + 'Notifications are activated' => 'آگاه سازی ها فعال شدند', + 'Notifications are disabled' => 'آگاه سازی ها غیر فعال شدند', + 'User disabled' => 'کاربر غیرفعال شد', + '%d notifications' => '%d آگاه سازی', + '%d notification' => '%d آگاه سازی', + 'There is no external integration installed.' => 'هیچ تلفیق خارجی نصب نشده است.', + 'You are not allowed to update tasks assigned to someone else.' => 'شما اجازه بروز رسانی کارهایی که به شخص دیگر محول شده را ندارید.', + 'You are not allowed to change the assignee.' => 'شما اجازه تغییر شخص محول شده را ندارید.', + 'Task suppression is not permitted' => 'زدودن کار مجاز نیست', + 'Changing assignee is not permitted' => 'تغییر شخص محول شده مجاز نیست', + 'Update only assigned tasks is permitted' => 'فقط بروز رسانی کارهای محول شده مجاز است', + 'Only for tasks assigned to the current user' => 'فقط برای کارهایی که به کاربر جاری محول شده', + 'My projects' => 'پروژه های من', + 'You are not a member of any project.' => 'شما عضو هیچ پروژه ای نمی باشید.', + 'My subtasks' => 'کارهای فرعی من', + '%d subtasks' => '%d کار فرعی', + '%d subtask' => '%d کار فرعی', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'تنها انتقال کار بین ستون هایی مجاز است که آن کار به کاربر جاری محول شده باشد', + '[DUPLICATE]' => '[تکراری]', + 'DKK - Danish Krona' => 'DKK - کرون دانمارک', + 'Remove user from group' => 'حذف کاربر از گروه', + 'Assign the task to its creator' => 'محول کردن کار به ایجاد کننده آن', + 'This task was sent by email to "%s" with subject "%s".' => 'این کار توسط ایمیل به "%s" با موضوع "%s" ارسال شد.', + 'Predefined Email Subjects' => 'موضوعات ایمیل از پیش تعریف شده', + 'Write one subject by line.' => 'در هر خط یک موضوع بنویسید.', + 'Create another link' => 'ایجاد پیوندی دیگر', + 'BRL - Brazilian Real' => 'BRL - رال برزیل', + 'Add a new Kanboard task' => 'ایجاد یک کار جدید کان بُرد', + 'Subtask not started' => 'کار فرعی شروع نشده', + 'Subtask currently in progress' => 'کار فرعی در حال حاضر در حال انجام است', + 'Subtask completed' => 'کار فرعی تکمیل شده', + 'Subtask added successfully.' => 'کار فرعی با موفقیت افزوده شد.', + '%d subtasks added successfully.' => '%d کار فرعی با موفقیت افزوده شد.', + 'Enter one subtask by line.' => 'در هر خط یک کار فرعی وارد کنید.', + 'Predefined Contents' => 'محتوای از پیش تعریف شده', + 'Predefined contents' => 'محتوای از پیش تعریف شده', + 'Predefined Task Description' => 'شرح کار از پیش تعریف شده', + 'Do you really want to remove this template? "%s"' => 'آیا واقعاً می خواهید این قالب را حذف کنید؟ "%s"', + 'Add predefined task description' => 'افزودن شرح کار از پیش تعریف شده', + 'Predefined Task Descriptions' => 'شرح کار از پیش تعریف شده', + 'Template created successfully.' => 'قالب با موفقیت ایجاد شد.', + 'Unable to create this template.' => 'ایجاد این قالب امکان پذیر نیست.', + 'Template updated successfully.' => 'قالب با موفقیت بروز شد.', + 'Unable to update this template.' => 'بروز رسانی این قالب امکان پذیر نیست.', + 'Template removed successfully.' => 'قالب با موفقیت حذف شد.', + 'Unable to remove this template.' => 'حذف این قالب امکان پذیر نیست.', + 'Template for the task description' => 'قالب برای این شرح کار', + 'The start date is greater than the end date' => 'تاریخ شروع از تاریخ پایان جلوتر است', + 'Tags must be separated by a comma' => 'برچسب ها باید با کاما از هم جدا شوند', + 'Only the task title is required' => 'تنها عنوان کار الزامی است', + 'Creator Username' => 'نام کاربری ایجاد کننده', + 'Color Name' => 'نام رنگ', + 'Column Name' => 'نام ستون', + 'Swimlane Name' => 'نام مسیر شنا', + 'Time Estimated' => 'زمان تخمینی', + 'Time Spent' => 'زمان سپری شده', + 'External Link' => 'لینک خارجی', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'این ویژگی خوراک iCal، خوراک RSS و نمای عمومی برد را فعال می کند.', + 'Stop the timer of all subtasks when moving a task to another column' => 'زمان سنج همه کارهای فرعی را وقتی که کار به ستون دیگری منتقل می شود متوقف کن', + 'Subtask Title' => 'عنوان کار فرعی', + 'Add a subtask and activate the timer when moving a task to another column' => 'وقتی کار به ستون دیگری منتقل می شود، یک کار فرعی به آن افزوده و زمان سنج فعال شود', + 'days' => 'روز', + 'minutes' => 'دقیقه', + 'seconds' => 'ثانیه', + 'Assign automatically a color when preset start date is reached' => 'هنگامی که تاریخ شروع از قبل تنظیم شده فرا برسد، بصورت خودکار یک رنگ به آن اختصاص بده', + 'Move the task to another column once a predefined start date is reached' => 'هنگامی که تاریخ شروع از پیش تعریف شده ای فرا رسید، کار را به ستون دیگری منتقل کن', + 'This task is now linked to the task %s with the relation "%s"' => 'این کار اکنون به کار %s با ارتباط "%s" پیوند خورده است.', + 'The link with the relation "%s" to the task %s has been removed' => 'پیوند با ارتباط "%s" به کار %s حذف شد.', + 'Custom Filter:' => 'فیلتر سفارشی:', + 'Unable to find this group.' => 'یافتن این گروه امکان پذیر نیست.', + '%s moved the task #%d to the column "%s"' => 'کاربر %s کار #%d را به ستون "%s" منتقل کرد.', + '%s moved the task #%d to the position %d in the column "%s"' => 'کاربر %s کار #%d را به موقعیت %d در ستون "%s" منتقل کرد.', + '%s moved the task #%d to the swimlane "%s"' => 'کاربر %s کار #%d را به مسیر شنای "%s" منتقل کرد', + '%sh spent' => '%sh سپری شده', + '%sh estimated' => '%sh تخمین زده شده', + 'Select All' => 'انتخاب همه', + 'Unselect All' => 'عدم انتخاب همه', + 'Apply action' => 'انجام عمل', + 'Move selected tasks to another column or swimlane' => 'کارهای انتخاب شده به ستون دیگری منتقل شوند', + 'Edit tasks in bulk' => 'ویرایش کارها بصورت دسته ای', + 'Choose the properties that you would like to change for the selected tasks.' => 'مشخصاتی که می خواهید برای کارهای انتخاب شده تغییر کنند را انتخاب کنید.', + 'Configure this project' => 'پیکربندی این پروژه', + 'Start now' => 'اکنون شروع شود', + '%s removed a file from the task #%d' => 'کاربر %s یک فایل را از کار #%d حذف کرد', + 'Attachment removed from task #%d: %s' => 'پیوست از کار #%d حذف شد: %s', + 'No color' => 'بدون رنگ', + 'Attachment removed "%s"' => 'پیوست حذف شد "%s"', + '%s removed a file from the task %s' => 'کاربر %s یک فایل را از کار %s حذف کرد', + 'Move the task to another swimlane when assigned to a user' => 'هنگامی که کاری به یک کاربر محول شد، آن کار را به مسیر شنای دیگر منتقل کن', + 'Destination swimlane' => 'مسیر شنای مقصد', + 'Assign a category when the task is moved to a specific swimlane' => 'هنگامی که کار به یک مسیر شنای مشخص منتقل شد، یک دسته بندی به آن اختصاص یابد', + 'Move the task to another swimlane when the category is changed' => 'هنگامی که دسته بندی تغییر کرد، کار را به مسیر شنایی دیگر منتقل کن', + 'Reorder this column by priority (ASC)' => 'مرتب سازی این ستون بر اساس اولویت (صعودی)', + 'Reorder this column by priority (DESC)' => 'مرتب سازی این ستون بر اساس اولویت (نزولی)', + 'Reorder this column by assignee and priority (ASC)' => 'مرتب سازی این ستون بر اساس شخص محول شده و اولویت (صعودی)', + 'Reorder this column by assignee and priority (DESC)' => 'مرتب سازی این ستون بر اساس شخص محول شده و اولویت (نزولی)', + 'Reorder this column by assignee (A-Z)' => 'مرتب سازی این ستون بر اساس شخص محول شده (حروف الفبا صعودی)', + 'Reorder this column by assignee (Z-A)' => 'مرتب سازی این ستون بر اساس شخص محول شده (حروف الفبا نزولی)', + 'Reorder this column by due date (ASC)' => 'مرتب سازی این ستون بر اساس تاریخ سررسید (صعودی)', + 'Reorder this column by due date (DESC)' => 'مرتب سازی این ستون بر اساس تاریخ سررسید (نزولی)', + 'Reorder this column by id (ASC)' => 'مرتب‌سازی این ستون بر اساس شناسه (صعودی)', + 'Reorder this column by id (DESC)' => 'مرتب‌سازی این ستون بر اساس شناسه (نزولی)', + '%s moved the task #%d "%s" to the project "%s"' => 'کاربر %s کار #%d "%s" را به پروژه "%s" منتقل کرد.', + 'Task #%d "%s" has been moved to the project "%s"' => 'کار #%d "%s" به پروژه "%s" منتقل شد.', + 'Move the task to another column when the due date is less than a certain number of days' => 'هنگامی که تاریخ سررسید کمتر از تعداد روز مشخصی شد، کار را به ستونی دیگر منتقل کن', + 'Automatically update the start date when the task is moved away from a specific column' => 'هنگامی که کار از ستونی مشخص منتقل شد، تاریخ شروع کار را بصورت خودکار بروز رسانی کن', + 'HTTP Client:' => 'کلاینت HTTP:', + 'Assigned' => 'محول شده', + 'Task limits apply to each swimlane individually' => 'محدودیت های کار به هر مسیر شنا بصورت مجزا اعمال می گردد', + 'Column task limits apply to each swimlane individually' => 'محدودیت های کارهای هر ستون به مسیرهای شنای مجزا اعمال می گردد', + 'Column task limits are applied to each swimlane individually' => 'محدودیت های کارهای هر ستون برای هر مسیر شنا مجزا است', + 'Column task limits are applied across swimlanes' => 'محدودیت های کارهای هر ستون برای همه مسیرهای شنا اعمال شده است', + 'Task limit: ' => 'محدودیت کار: ', + 'Change to global tag' => 'تبدیل به برچسب جامع', + 'Do you really want to make the tag "%s" global?' => 'واقعا می خواهید برچسب "%s" را تبدیل به برچسب جامع کنید؟', + 'Enable global tags for this project' => 'برچسب های جامع برای این پروژه فعال گردند', + 'Group membership(s):' => 'عضویت(های) گروهی:', + '%s is a member of the following group(s): %s' => '%s عضوی از این گروه(ها) است: %s', + '%d/%d group(s) shown' => '%d/%d گروه(ها) نمایش داده شده است', + 'Subtask creation or modification' => 'ایجاد یا تغییر کار فرعی', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'وقتی که کار به یک مسیر شنای خاص منتقل شد، آن را به یک کاربر خاص اختصاص بده', + 'Comment' => 'توضیح', + 'Collapse vertically' => 'جمع شدن عمودی', + 'Expand vertically' => 'باز شدن عمودی', + 'MXN - Mexican Peso' => 'MXN - پزوی مکزیک', + 'Estimated vs actual time per column' => 'زمان تخمینی در قیاس با واقعی برای هر ستون', + 'HUF - Hungarian Forint' => 'HUF - فورینت مجراستان', + 'XBT - Bitcoin' => 'XBT - بیت کوین', + 'You must select a file to upload as your avatar!' => 'برای بارگذاری تصویر نمایه تان باید فایلی انتخاب کنید!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'فایلی که بارگذاری کردید یک تصویر معتبر نیست! (فقط *.gif, *.jpg, *.jpeg و *.png مجاز هستند!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'تاریخ سررسید به‌طور خودکار هنگام جابجایی وظیفه از یک ستون خاص تنظیم شود', + 'No other projects found.' => 'هیچ پروژه دیگری یافت نشد.', + 'Tasks copied successfully.' => 'وظایف با موفقیت کپی شدند.', + 'Unable to copy tasks.' => 'کپی کردن وظایف امکان‌پذیر نیست.', + 'Theme' => 'پوسته', + 'Theme:' => 'پوسته:', + 'Light theme' => 'پوسته روشن', + 'Dark theme' => 'پوسته تیره', + 'Automatic theme - Sync with system' => 'پوسته خودکار - هماهنگ با سیستم', + 'Application managers or more' => 'مدیران برنامه یا بالاتر', + 'Administrators' => 'مدیران', + 'Visibility:' => 'قابلیت مشاهده:', + 'Standard users' => 'کاربران عادی', + 'Visibility is required' => 'مشاهده‌پذیری لازم است', + 'The visibility should be an app role' => 'مشاهده‌پذیری باید یک نقش برنامه باشد', + 'Reply' => 'پاسخ', + '%s wrote: ' => '%s نوشت:', + 'Number of visible tasks in this column and swimlane' => 'تعداد وظایف قابل مشاهده در این ستون و ردیف شناور', + 'Number of tasks in this swimlane' => 'تعداد وظایف در این ردیف شناور', + 'Unable to find another subtask in progress, you can close this window.' => 'زیر‌وظیفه دیگری در حال انجام یافت نشد، می‌توانید این پنجره را ببندید.', + 'This theme is invalid' => 'این پوسته نامعتبر است', + 'This role is invalid' => 'این نقش نامعتبر است', + 'This timezone is invalid' => 'این منطقه زمانی نامعتبر است', + 'This language is invalid' => 'این زبان نامعتبر است', + 'This URL is invalid' => 'این نشانی اینترنتی نامعتبر است', + 'Date format invalid' => 'فرمت تاریخ نامعتبر است', + 'Time format invalid' => 'فرمت زمان نامعتبر است', + 'Invalid Mail transport' => 'انتقال ایمیل نامعتبر است', + 'Color invalid' => 'رنگ نامعتبر است', + 'This value must be greater or equal to %d' => 'این مقدار باید بزرگتر یا مساوی %d باشد', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'افزودن BOM در ابتدای فایل (مورد نیاز برای Microsoft Excel)', + 'Just add these tag(s)' => 'فقط این برچسب(ها) را اضافه کنید', + 'Remove internal link(s)' => 'پیوند(های) داخلی را حذف کنید', + 'Import tasks from another project' => 'وارد کردن وظایف از پروژه‌ای دیگر', + 'Select the project to copy tasks from' => 'پروژه‌ای را که می‌خواهید وظایف از آن کپی شود انتخاب کنید', + 'The total maximum allowed attachments size is %sB.' => 'حداکثر حجم مجاز پیوست‌ها %sB است.', + 'Add attachments' => 'افزودن پیوست‌ها', + 'Task #%d "%s" is overdue' => 'کار #%d "%s" از موعد گذشته است', + 'Enable notifications by default for all new users' => 'به‌طور پیش‌فرض آگاه‌سازی‌ها را برای همه کاربران جدید فعال کنید', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'اگر مسئول به‌صورت دستی تعیین نشده باشد، برای ستون‌های مشخص، کار را به ایجادکنندهٔ آن واگذار کن', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'اگر کاربری تعیین نشده باشد، هنگام تغییر ستون به ستون مشخص‌شده، کار را به کاربر واردشده اختصاص بده', +]; diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php new file mode 100644 index 0000000..a3e85a3 --- /dev/null +++ b/app/Locale/fi_FI/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => ' ', + 'None' => 'Ei mikään', + 'Edit' => 'Muokkaa', + 'Remove' => 'Poista', + 'Yes' => 'Kyllä', + 'No' => 'Ei', + 'cancel' => 'peruuta', + 'or' => 'tai', + 'Yellow' => 'Keltainen', + 'Blue' => 'Sininen', + 'Green' => 'Vihreä', + 'Purple' => 'Violetti', + 'Red' => 'Punainen', + 'Orange' => 'Oranssi', + 'Grey' => 'Harmaa', + 'Brown' => 'Ruskea', + 'Deep Orange' => 'Tumma oranssi', + 'Dark Grey' => 'Tummanharmaa', + 'Pink' => 'Pinkki', + 'Teal' => 'Sinivihreä', + 'Cyan' => 'Syaani', + 'Lime' => 'Limetin vihreä', + 'Light Green' => 'Vaaleanvihreä', + 'Amber' => 'Keltainen', + 'Save' => 'Tallenna', + 'Login' => 'Sisäänkirjautuminen', + 'Official website:' => 'Virallinen verkkosivu:', + 'Unassigned' => 'Ei suorittajaa', + 'View this task' => 'Näytä tämä tehtävä', + 'Remove user' => 'Poista käyttäjä', + 'Do you really want to remove this user: "%s"?' => 'Oletko varma että haluat poistaa käyttäjän "%s"?', + 'All users' => 'Kaikki käyttäjät', + 'Username' => 'Käyttäjänimi', + 'Password' => 'Salasana', + 'Administrator' => 'Ylläpitäjä', + 'Sign in' => 'Kirjaudu sisään', + 'Users' => 'Käyttäjät', + 'Forbidden' => 'Estetty', + 'Access Forbidden' => 'Pääsy estetty', + 'Edit user' => 'Muokkaa käyttäjää', + 'Logout' => 'Kirjaudu ulos', + 'Bad username or password' => 'Väärä käyttäjätunnus tai salasana', + 'Edit project' => 'Muokkaa projektia', + 'Name' => 'Nimi', + 'Projects' => 'Projektit', + 'No project' => 'Ei projektia', + 'Project' => 'Projekti', + 'Status' => 'Status', + 'Tasks' => 'Tehtävät', + 'Board' => 'Taulu', + 'Actions' => 'Toiminnot', + 'Inactive' => 'Ei aktiivinen', + 'Active' => 'Aktiivinen', + 'Unable to update this board.' => 'Taulun muuttaminen ei onnistunut.', + 'Disable' => 'Disabloi', + 'Enable' => 'Aktivoi', + 'New project' => 'Uusi projekti', + 'Do you really want to remove this project: "%s"?' => 'Haluatko varmasti poistaa projektin: "%s"?', + 'Remove project' => 'Poista projekti', + 'Edit the board for "%s"' => 'Muokkaa taulua projektille "%s"', + 'Add a new column' => 'Lisää uusi sarake', + 'Title' => 'Nimi', + 'Assigned to %s' => 'Tekijä: %s', + 'Remove a column' => 'Poista sarake', + 'Unable to remove this column.' => 'Sarakkeen poistaminen ei onnistunut.', + 'Do you really want to remove this column: "%s"?' => 'Haluatko varmasti poistaa sarakkeen "%s"?', + 'Settings' => 'Asetukset', + 'Application settings' => 'Ohjelman asetukset', + 'Language' => 'Kieli', + 'Webhook token:' => 'Webhooks avain:', + 'API token:' => 'API-tunnus:', + 'Database size:' => 'Tietokannan koko:', + 'Download the database' => 'Lataa tietokanta', + 'Optimize the database' => 'Optimoi tietokanta', + '(VACUUM command)' => '(VACUUM-komento)', + '(Gzip compressed Sqlite file)' => '(Gzip-pakattu Sqlite-tiedosto)', + 'Close a task' => 'Sulje tehtävä', + 'Column' => 'Sarake', + 'Color' => 'Väri', + 'Assignee' => 'Suorittaja', + 'Create another task' => 'Luo toinen tehtävä', + 'New task' => 'Uusi tehtävä', + 'Open a task' => 'Avaa tehtävä', + 'Do you really want to open this task: "%s"?' => 'Haluatko varmasti avata tehtävän: "%s"?', + 'Back to the board' => 'Takaisin tauluun', + 'There is nobody assigned' => 'Ei suorittajaa', + 'Column on the board:' => 'Sarake taululla: ', + 'Close this task' => 'Sulje tämä tehtävä', + 'Open this task' => 'Avaa tämä tehtävä', + 'There is no description.' => 'Ei kuvausta.', + 'Add a new task' => 'Lisää uusi tehtävä', + 'The username is required' => 'Käyttäjätunnut vaaditaan', + 'The maximum length is %d characters' => 'Maksimipituus on %d merkkiä', + 'The minimum length is %d characters' => 'Vähimmäispituus on %d merkkiä', + 'The password is required' => 'Salasana vaaditaan', + 'This value must be an integer' => 'Tämän arvon täytyy olla numero', + 'The username must be unique' => 'Käyttäjänimi täytyy olla uniikki', + 'The user id is required' => 'Käyttäjän id on pakollinen', + 'Passwords don\'t match' => 'Salasanat eivät täsmää', + 'The confirmation is required' => 'Varmistus vaaditaan', + 'The project is required' => 'Projekti on pakollinen', + 'The id is required' => 'ID vaaditaan', + 'The project id is required' => 'Projektin ID on pakollinen', + 'The project name is required' => 'Projektin nimi on pakollinen', + 'The title is required' => 'Otsikko vaaditaan', + 'Settings saved successfully.' => 'Asetukset tallennettu onnistuneesti.', + 'Unable to save your settings.' => 'Asetusten tallentaminen epäonnistui.', + 'Database optimization done.' => 'Tietokannan optimointi suoritettu.', + 'Your project has been created successfully.' => 'Projekti luotiin onnistuneesti.', + 'Unable to create your project.' => 'Projektin luominen epäonnistui.', + 'Project updated successfully.' => 'Projekti päivitettiin onnistuneesti.', + 'Unable to update this project.' => 'Projektin muuttaminen epäonnistui.', + 'Unable to remove this project.' => 'Projektin poistaminen epäonnistui.', + 'Project removed successfully.' => 'Projekti poistettiin onnistuneesti.', + 'Project activated successfully.' => 'Projekti aktivoitiin onnistuneesti.', + 'Unable to activate this project.' => 'Projektin aktivoiminen epäonnistui.', + 'Project disabled successfully.' => 'Projektin disabloiminen onnistui.', + 'Unable to disable this project.' => 'Projektin disabloiminen epäonnistui.', + 'Unable to open this task.' => 'Tehtävän avaus epäonnistui.', + 'Task opened successfully.' => 'Tehtävä avattiin onnistuneesti.', + 'Unable to close this task.' => 'Tehtävän sulkeminen epäonnistui.', + 'Task closed successfully.' => 'Tehtävä suljettiin onnistuneesti.', + 'Unable to update your task.' => 'Tehtävän muokkaaminen epäonnistui.', + 'Task updated successfully.' => 'Tehtävä päivitettiin onnistuneesti.', + 'Unable to create your task.' => 'Tehtävän luominen epäonnistui.', + 'Task created successfully.' => 'Tehtävä luotiin onnistuneesti.', + 'User created successfully.' => 'Käyttäjä lisättiin onnistuneesti.', + 'Unable to create your user.' => 'Käyttäjän lisäys epäonnistui.', + 'User updated successfully.' => 'Käyttäjätietojen päivitys onnistui.', + 'User removed successfully.' => 'Käyttäjä poistettiin onnistuneesti.', + 'Unable to remove this user.' => 'Käyttäjän poistaminen epäonnistui.', + 'Board updated successfully.' => 'Taulu päivitettiin onnistuneesti.', + 'Ready' => 'Valmis', + 'Backlog' => 'Tehtäväjono', + 'Work in progress' => 'Työnalla', + 'Done' => 'Tehty', + 'Application version:' => 'Ohjelman versio:', + 'Id' => 'Id', + 'Public link' => 'Julkinen linkki', + 'Timezone' => 'Aikavyöhyke', + 'Sorry, I didn\'t find this information in my database!' => 'Anteeksi, en löytänyt tätä tietoa tietokannastani', + 'Page not found' => 'Sivua ei löydy', + 'Complexity' => 'Monimutkaisuus', + 'Task limit' => 'Tehtävien maksimimäärä', + 'Task count' => 'Tehtävien määrä', + 'User' => 'Käyttäjät', + 'Comments' => 'Kommentit', + 'Comment is required' => 'Kommentti vaaditaan', + 'Comment added successfully.' => 'Kommentti lisättiin onnistuneesti.', + 'Unable to create your comment.' => 'Kommentin lisäys epäonnistui.', + 'Due Date' => 'Deadline', + 'Invalid date' => 'Virheellinen päiväys', + 'Automatic actions' => 'Automaattiset toiminnot', + 'Your automatic action has been created successfully.' => 'Toiminto suoritettiin onnistuneesti.', + 'Unable to create your automatic action.' => 'Automaattisen toiminnon luominen epäonnistui.', + 'Remove an action' => 'Poista toiminto', + 'Unable to remove this action.' => 'Toiminnon poistaminen epäonnistui.', + 'Action removed successfully.' => 'Toiminto poistettiin onnistuneesti.', + 'Automatic actions for the project "%s"' => 'Automaattiset toiminnot projektille "%s"', + 'Add an action' => 'Lisää toiminto', + 'Event name' => 'Tapahtuman nimi', + 'Action' => 'Toiminto', + 'Event' => 'Tapahtuma', + 'When the selected event occurs execute the corresponding action.' => 'Kun valittu tapahtuma tapahtuu, suorita vastaava toiminto.', + 'Next step' => 'Seuraava vaihe', + 'Define action parameters' => 'Määrittele toiminnon parametrit', + 'Do you really want to remove this action: "%s"?' => 'Oletko varma että haluat poistaa toiminnon "%s"?', + 'Remove an automatic action' => 'Poista automaattintn toiminto', + 'Assign the task to a specific user' => 'Osoita tehtävä käyttäjälle', + 'Assign the task to the person who does the action' => 'Määritä suorittaja tehtävälle', + 'Duplicate the task to another project' => 'Monista tehtävä toiselle projektille', + 'Move a task to another column' => 'Siirrä tehtävä toiseen sarakkeeseen', + 'Task modification' => 'Tehtävän muokkaus', + 'Task creation' => 'Tehtävän luominen', + 'Closing a task' => 'Tehtävää suljetaan', + 'Assign a color to a specific user' => 'Valitse väri käyttäjälle', + 'Position' => 'Positio', + 'Duplicate to project' => 'Kopioi toiseen projektiin', + 'Duplicate' => 'Monista', + 'Link' => 'Linkki', + 'Comment updated successfully.' => 'Kommentti päivitettiin onnistuneesti.', + 'Unable to update your comment.' => 'Kommentin päivitys epäonnistui.', + 'Remove a comment' => 'Poista kommentti', + 'Comment removed successfully.' => 'Kommentti poistettiin onnistuneesti.', + 'Unable to remove this comment.' => 'Kommentin poistaminen epäonnistui.', + 'Do you really want to remove this comment?' => 'Haluatko varmasti poistaa tämän kommentin?', + 'Current password for the user "%s"' => 'Käyttäjän "%s" salasana', + 'The current password is required' => 'Salasana vaaditaan', + 'Wrong password' => 'Väärä salasana', + 'Unknown' => 'Tuntematon', + 'Last logins' => 'Viimeisimmät kirjautumiset', + 'Login date' => 'Kirjautumispäivä', + 'Authentication method' => 'Autentikointimenetelmä', + 'IP address' => 'IP-Osoite', + 'User agent' => 'Selain', + 'Persistent connections' => 'Voimassa olevat yhteydet', + 'No session.' => 'Ei sessioita.', + 'Expiration date' => 'Vanhentumispäivä', + 'Remember Me' => 'Muista minut', + 'Creation date' => 'Luomispäivä', + 'Everybody' => 'Kaikki', + 'Open' => 'Avoin', + 'Closed' => 'Suljettu', + 'Search' => 'Etsi', + 'Nothing found.' => 'Ei löytynyt.', + 'Due date' => 'Deadline', + 'Description' => 'Kuvaus', + '%d comments' => '%d kommenttia', + '%d comment' => '%d kommentti', + 'Email address invalid' => 'Email ei kelpaa', + 'Your external account is not linked anymore to your profile.' => 'Ulkoinen tilisi ei ole enää linkitetty profiiliisi.', + 'Unable to unlink your external account.' => 'Ulkoisen tilin irrottaminen epäonnistui.', + 'External authentication failed' => 'Ulkoinen tunnistautuminen epäonnistui', + 'Your external account is linked to your profile successfully.' => 'Ulkoinen tilisi on linkitetty profiiliisi onnistuneesti.', + 'Email' => 'Sähköposti', + 'Task removed successfully.' => 'Tehtävä poistettiin onnistuneesti.', + 'Unable to remove this task.' => 'Tehtävän poistaminen epäonnistui.', + 'Remove a task' => 'Poista tehtävä', + 'Do you really want to remove this task: "%s"?' => 'Haluatko varmasti poistaa tehtävän: "%s"?', + 'Assign automatically a color based on a category' => 'Aseta väri automaattisesti kategorian mukaan', + 'Assign automatically a category based on a color' => 'Aseta kategoria automaattisesti värin mukaan', + 'Task creation or modification' => 'Tehtävän luonti tai muuttaminen', + 'Category' => 'Kategoria', + 'Category:' => 'Kategoria:', + 'Categories' => 'Kategoriat', + 'Your category has been created successfully.' => 'Kategoria luotiin onnistuneesti.', + 'This category has been updated successfully.' => 'Kategoriaa muokattiin onnistuneesti.', + 'Unable to update this category.' => 'Kategorian muokkaaminen epäonnistui.', + 'Remove a category' => 'Poista kategoria', + 'Category removed successfully.' => 'Kategoria poistettu onnistuneesti.', + 'Unable to remove this category.' => 'Kategorian poisto epäonnistui.', + 'Category modification for the project "%s"' => 'Kategorian muutos projektissa "%s"', + 'Category Name' => 'Kategorian nimi', + 'Add a new category' => 'Lisää uusi kategoria', + 'Do you really want to remove this category: "%s"?' => 'Haluatko varmasti poistaa kategorian: "%s"?', + 'All categories' => 'Kaikki kategoriat', + 'No category' => 'Kategoriaa ei löydy', + 'The name is required' => 'Nimi vaaditaan', + 'Remove a file' => 'Poista tiedosto', + 'Unable to remove this file.' => 'Tiedoston poistaminen epäonnistui.', + 'File removed successfully.' => 'Tiedosto poistettiin onnistuneesti.', + 'Attach a document' => 'Liitä dokumentti', + 'Do you really want to remove this file: "%s"?' => 'Haluatko varmasti poistaa tiedoston: "%s"?', + 'Attachments' => 'Liitteet', + 'Edit the task' => 'Muokkaa tehtävää', + 'Add a comment' => 'Lisää kommentti', + 'Edit a comment' => 'Muokkaa kommenttia', + 'Summary' => 'Yhteenveto', + 'Time tracking' => 'Ajan seuranta', + 'Estimate:' => 'Arvio:', + 'Spent:' => 'Käytetty:', + 'Do you really want to remove this sub-task?' => 'Haluatko varmasti poistaa tämän alitehtävän?', + 'Remaining:' => 'Jäljellä', + 'hours' => 'tuntia', + 'estimated' => 'estimoitu', + 'Sub-Tasks' => 'Alitehtävät', + 'Add a sub-task' => 'Lisää alitehtävä', + 'Original estimate' => 'Alkuperäinen estimaatti', + 'Create another sub-task' => 'Lisää toinen alitehtävä', + 'Time spent' => 'Käytetty aika', + 'Edit a sub-task' => 'Muokkaa alitehtävää', + 'Remove a sub-task' => 'Poista alitehtävä', + 'The time must be a numeric value' => 'Ajan pitää olla numero', + 'Todo' => 'Todo', + 'In progress' => 'Työnalla', + 'Sub-task removed successfully.' => 'Alitehtävä poistettu onnistuneesti.', + 'Unable to remove this sub-task.' => 'Alitehtävän poistaminen epäonnistui.', + 'Sub-task updated successfully.' => 'Alitehtävä päivitettiin onnistuneesti.', + 'Unable to update your sub-task.' => 'Alitehtävän päivitys epäonnistui.', + 'Unable to create your sub-task.' => 'Alitehtävän luonti epäonnistui.', + 'Maximum size: ' => 'Maksimikoko: ', + 'Display another project' => 'Näytä toinen projekti', + 'Created by %s' => 'Luonut: %s', + 'Tasks Export' => 'Tehtävien vienti', + 'Start Date' => 'Aloituspäivä', + 'Execute' => 'Suorita', + 'Task Id' => 'Tehtävän ID', + 'Creator' => 'Luonut', + 'Modification date' => 'Muokkauspäivä', + 'Completion date' => 'Valmistumispäivä', + 'Clone' => 'Kahdenna', + 'Project cloned successfully.' => 'Projekti kahdennettu onnistuneesti', + 'Unable to clone this project.' => 'Projektin kahdennus epäonnistui', + 'Enable email notifications' => 'Ota käyttöön sähköposti-ilmoitukset', + 'Task position:' => 'Tehtävän sijainti', + 'The task #%d has been opened.' => 'Tehtävä #%d on avattu', + 'The task #%d has been closed.' => 'Tehtävä #%d on suljettu', + 'Sub-task updated' => 'Alitehtävä päivitetty', + 'Title:' => 'Otsikko:', + 'Status:' => 'Tila:', + 'Assignee:' => 'Vastaanottaja:', + 'Time tracking:' => 'Ajan seuranta:', + 'New sub-task' => 'Uusi alitehtävä', + 'New attachment added "%s"' => 'Uusi liite lisätty "%s"', + 'New comment posted by %s' => '%s lisäsi uuden kommentin', + 'New comment' => 'Uusi kommentti', + 'Comment updated' => 'Kommentti päivitetty', + 'New subtask' => 'Uusi alitehtävä', + 'I only want to receive notifications for these projects:' => 'Haluan vastaanottaa ilmoituksia ainoastaan näistä projekteista:', + 'view the task on Kanboard' => 'katso tehtävää Kanboardissa', + 'Public access' => 'Julkinen käyttöoikeus', + 'Disable public access' => 'Poista käytöstä julkinen käyttöoikeus', + 'Enable public access' => 'Ota käyttöön ', + 'Public access disabled' => 'Julkinen käyttöoikeus ei ole käytössä', + 'Move the task to another project' => 'Siirrä tehtävä toiseen projektiin', + 'Move to project' => 'Siirrä toiseen projektiin', + 'Do you really want to duplicate this task?' => 'Haluatko varmasti kahdentaa tämän tehtävän?', + 'Duplicate a task' => 'Kahdenna tehtävä', + 'External accounts' => 'Muut tilit', + 'Account type' => 'Tilin tyyppi', + 'Local' => 'Paikallinen', + 'Remote' => 'Etä', + 'Enabled' => 'Käytössä', + 'Disabled' => 'Pois käytöstä', + 'Login:' => 'Käyttäjänimi:', + 'Full Name:' => 'Nimi:', + 'Email:' => 'Sähköpostiosoite:', + 'Notifications:' => 'Ilmoitukset:', + 'Notifications' => 'Ilmoitukset', + 'Account type:' => 'Tilin tyyppi:', + 'Edit profile' => 'Muokkaa profiilia', + 'Change password' => 'Vaihda salasana', + 'Password modification' => 'Salasanan vaihto', + 'External authentications' => 'Muut tunnistautumistavat', + 'Never connected.' => 'Ei koskaan liitetty.', + 'No external authentication enabled.' => 'Muita tunnistautumistapoja ei ole otettu käyttöön.', + 'Password modified successfully.' => 'Salasana vaihdettu onnistuneesti.', + 'Unable to change the password.' => 'Salasanan vaihto epäonnistui.', + 'Change category' => 'Vaihda kategoria', + '%s updated the task %s' => '%s päivitti tehtävän %s', + '%s opened the task %s' => '%s avasi tehtävän %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s siirsi tehtävän %s %d. sarakkeessa "%s"', + '%s moved the task %s to the column "%s"' => '%s siirsi tehtävän %s sarakkeeseen "%s"', + '%s created the task %s' => '%s loi tehtävän %s', + '%s closed the task %s' => '%s sulki tehtävän %s', + '%s created a subtask for the task %s' => '%s loi alitehtävän tehtävälle %s', + '%s updated a subtask for the task %s' => '%s päivitti tehtävän %s alitehtävää', + 'Assigned to %s with an estimate of %s/%sh' => 'Annettu henkilölle %s arviolla %s/%sh', + 'Not assigned, estimate of %sh' => 'Ei annettu kenellekään, arvio %sh', + '%s updated a comment on the task %s' => '%s päivitti kommentia tehtävässä %s', + '%s commented the task %s' => '%s kommentoi tehtävää %s', + '%s\'s activity' => 'Henkilön %s toiminta', + 'RSS feed' => 'RSS-syöte', + '%s updated a comment on the task #%d' => '%s päivitti kommenttia tehtävässä #%d', + '%s commented on the task #%d' => '%s kommentoi tehtävää #%d', + '%s updated a subtask for the task #%d' => '%s päivitti tehtävän #%d alitehtävää', + '%s created a subtask for the task #%d' => '%s loi alitehtävän tehtävälle #%d', + '%s updated the task #%d' => '%s päivitti tehtävää #%d', + '%s created the task #%d' => '%s loi tehtävän #%d', + '%s closed the task #%d' => '%s sulki tehtävän #%d', + '%s opened the task #%d' => '%s avasi tehtävän #%d', + 'Activity' => 'Toiminta', + 'Default values are "%s"' => 'Oletusarvot ovat "%s"', + 'Default columns for new projects (Comma-separated)' => 'Oletussarakkeet uusille projekteille', + 'Task assignee change' => 'Tehtävän saajan vaihto', + '%s changed the assignee of the task #%d to %s' => '%s vaihtoi tehtävän #%d saajaksi %s', + '%s changed the assignee of the task %s to %s' => '%s vaihtoi tehtävän %s saajaksi %s', + 'New password for the user "%s"' => 'Uusi salasana käyttäjälle "%s"', + 'Choose an event' => 'Valitse toiminta', + 'Create a task from an external provider' => 'Luo tehtävä ulkoiselta tarjoajalta', + 'Change the assignee based on an external username' => 'Vaihda tehtävän saajaa perustuen ulkoiseen käyttäjänimeen', + 'Change the category based on an external label' => 'Vaihda kategoriaa perustuen ulkoiseen labeliin', + 'Reference' => 'Viite', + 'Label' => 'Label', + 'Database' => 'Tietokanta', + 'About' => 'Tietoja', + 'Database driver:' => 'Tietokantaohjelmisto:', + 'Board settings' => 'Taulun asetukset', + 'Webhook settings' => 'Webhookin asetukset', + 'Reset token' => 'Vaihda token', + 'API endpoint:' => 'API päätepiste:', + 'Refresh interval for personal board' => 'Päivitystiheys yksityisille tauluille', + 'Refresh interval for public board' => 'Päivitystiheys julkisille tauluille', + 'Task highlight period' => 'Tehtävän korostusaika', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Aika (sekunteina) kuinka kauan tehtävä voidaan katsoa äskettäin muokatuksi (0 poistaa toiminnon käytöstä, oletuksena 2 päivää)', + 'Frequency in second (60 seconds by default)' => 'Päivitystiheys sekunteina (60 sekuntia oletuksena)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Päivitystiheys sekunteina (0 poistaa toiminnon käytöstä, oletuksena 10 sekuntia)', + 'Application URL' => 'Sovelluksen URL', + 'Token regenerated.' => 'Token uudelleenluotu.', + 'Date format' => 'Päiväyksen muoto', + 'ISO format is always accepted, example: "%s" and "%s"' => 'ISO-muoto on aina hyväksytty, esimerkiksi %s ja %s', + 'New personal project' => 'Uusi henkilökohtainen projekti', + 'This project is personal' => 'Tämä projekti on henkilökohtainen', + 'Add' => 'Lisää', + 'Start date' => 'Aloituspäivä', + 'Time estimated' => 'Arvioitu aika', + 'There is nothing assigned to you.' => 'Ei tehtäviä, joihin sinut olisi merkitty tekijäksi.', + 'My tasks' => 'Minun tehtävät', + 'Activity stream' => 'Toiminta', + 'Dashboard' => 'Työpöytä', + 'Confirmation' => 'Vahvistus', + 'Webhooks' => 'Webhookit', + 'API' => 'API', + 'Create a comment from an external provider' => 'Luo kommentti ulkoiselta palveluntarjoajalta', + 'Project management' => 'Projektin hallinta', + 'Columns' => 'Sarakkeet', + 'Task' => 'Tehtävät', + 'Percentage' => 'Prosentti', + 'Number of tasks' => 'Tehtävien määrä', + 'Task distribution' => 'Tehtävien jakauma', + 'Analytics' => 'Analytiikka', + 'Subtask' => 'Alitehtävä', + 'User repartition' => 'Käyttäjien jakauma', + 'Clone this project' => 'Kahdenna projekti', + 'Column removed successfully.' => 'Sarake poistettu onnstuneesti.', + 'Not enough data to show the graph.' => 'Ei riittävästi dataa graafin näyttämiseksi.', + 'Previous' => 'Edellinen', + 'The id must be an integer' => 'ID:n on oltava kokonaisluku', + 'The project id must be an integer' => 'Projektin ID:n on oltava kokonaisluku', + 'The status must be an integer' => 'Tilan on oltava kokonaisluku', + 'The subtask id is required' => 'Alitehtävän ID vaaditaan', + 'The subtask id must be an integer' => 'Alitehtävän ID:ntulee olla kokonaisluku', + 'The task id is required' => 'Tehtävän ID vaaditaan', + 'The task id must be an integer' => 'Tehtävän ID on oltava kokonaisluku', + 'The user id must be an integer' => 'Käyttäjän ID on oltava kokonaisluku', + 'This value is required' => 'Tämä arvo on pakollinen', + 'This value must be numeric' => 'Tämän arvon tulee olla numeerinen', + 'Unable to create this task.' => 'Tehtävän luonti epäonnistui', + 'Cumulative flow diagram' => 'Kumulatiivinen vuokaavio', + 'Daily project summary' => 'Päivittäinen yhteenveto', + 'Daily project summary export' => 'Päivittäisen yhteenvedon vienti', + 'Exports' => 'Viennit', + 'This export contains the number of tasks per column grouped per day.' => 'Tämä tiedosto sisältää tehtäviä sarakkeisiin päiväkohtaisesti ryhmilteltyinä', + 'Active swimlanes' => 'Aktiiviset kaistat', + 'Add a new swimlane' => 'Lisää uusi kaista', + 'Default swimlane' => 'Oletuskaista', + 'Do you really want to remove this swimlane: "%s"?' => 'Haluatko varmasti poistaa tämän kaistan: "%s"?', + 'Inactive swimlanes' => 'Passiiviset kaistat', + 'Remove a swimlane' => 'Poista kaista', + 'Swimlane modification for the project "%s"' => 'Kaistamuutos projektille "%s"', + 'Swimlane removed successfully.' => 'Kaista poistettu onnistuneesti.', + 'Swimlanes' => 'Kaistat', + 'Swimlane updated successfully.' => 'Kaista päivitetty onnistuneesti.', + 'Unable to remove this swimlane.' => 'Kaistan poisto epäonnistui.', + 'Unable to update this swimlane.' => 'Kaistan päivittäminen epäonnistui.', + 'Your swimlane has been created successfully.' => 'Kaista luotu onnistuneesti.', + 'Example: "Bug, Feature Request, Improvement"' => 'Esimerkiksi: "Bugit, Ominaisuuspyynnöt, Parannukset"', + 'Default categories for new projects (Comma-separated)' => 'Oletuskategoriat uusille projekteille (pilkuin eroteltu)', + 'Integrations' => 'Integraatiot', + 'Integration with third-party services' => 'Integrointi kolmannen osapuolen palveluihin', + 'Subtask Id' => 'Alitehtävän tunnus', + 'Subtasks' => 'Alitehtävät', + 'Subtasks Export' => 'Alitehtävien vienti', + 'Task Title' => 'Tehtävän otsikko', + 'Untitled' => 'Nimetön', + 'Application default' => 'Sovelluksen oletus', + 'Language:' => 'Kieli:', + 'Timezone:' => 'Aikavyöhyke:', + 'All columns' => 'Kaikki sarakkeet', + 'Next' => 'Seuraava', + '#%d' => '#%d', + 'All swimlanes' => 'Kaikki kaistat', + 'All colors' => 'Kaikki värit', + 'Moved to column %s' => 'Siirretty sarakkeeseen %s', + 'User dashboard' => 'Käyttäjän hallintapaneeli', + 'Allow only one subtask in progress at the same time for a user' => 'Salli käyttäjälle vain yksi alitehtävä kerrallaan kesken', + 'Edit column "%s"' => 'Muokkaa saraketta "%s"', + 'Select the new status of the subtask: "%s"' => 'Valitse alitehtävän uusi tila: "%s"', + 'Subtask timesheet' => 'Alitehtävien tuntilista', + 'There is nothing to show.' => 'Ei näytettävää.', + 'Time Tracking' => 'Ajankäytön seuranta', + 'You already have one subtask in progress' => 'Sinulla on jo yksi alitehtävä kesken', + 'Which parts of the project do you want to duplicate?' => 'Mitkä osat projektista haluat kopioida?', + 'Disallow login form' => 'Estä kirjautumislomake', + 'Start' => 'Alku', + 'End' => 'Loppu', + 'Task age in days' => 'Tehtävän ikä päivinä', + 'Days in this column' => 'Päivää tässä sarakkeessa', + '%dd' => '%dp', + 'Add a new link' => 'Lisää uusi linkki', + 'Do you really want to remove this link: "%s"?' => 'Haluatko todella poistaa tämän linkin: "%s"?', + 'Do you really want to remove this link with task #%d?' => 'Haluatko todella poistaa tämän linkin tehtävään #%d?', + 'Field required' => 'Kenttä on pakollinen', + 'Link added successfully.' => 'Linkki lisättiin onnistuneesti.', + 'Link updated successfully.' => 'Linkki päivitettiin onnistuneesti.', + 'Link removed successfully.' => 'Linkki poistettiin onnistuneesti.', + 'Link labels' => 'Linkkien selitteet', + 'Link modification' => 'Linkin muokkaus', + 'Opposite label' => 'Vastakkainen selite', + 'Remove a link' => 'Poista linkki', + 'The labels must be different' => 'Selitteiden on oltava erilaisia', + 'There is no link.' => 'Ei linkkejä.', + 'This label must be unique' => 'Tämän selitteen on oltava yksilöllinen', + 'Unable to create your link.' => 'Linkin luominen epäonnistui.', + 'Unable to update your link.' => 'Linkin päivittäminen epäonnistui.', + 'Unable to remove this link.' => 'Linkin poistaminen epäonnistui.', + 'relates to' => 'liittyy', + 'blocks' => 'estää', + 'is blocked by' => 'estää', + 'duplicates' => 'kopioi', + 'is duplicated by' => 'on kopioitu', + 'is a child of' => 'on alitehtävä', + 'is a parent of' => 'on ylätason tehtävä', + 'targets milestone' => 'kohdistuu välitavoitteeseen', + 'is a milestone of' => 'on välitavoite', + 'fixes' => 'korjaa', + 'is fixed by' => 'korjaa', + 'This task' => 'Tämä tehtävä', + '<1h' => '<1h', + '%dh' => '%dh', + 'Expand tasks' => 'Laajenna tehtävät', + 'Collapse tasks' => 'Kutista tehtävät', + 'Expand/collapse tasks' => 'Laajenna/kutista tehtävät', + 'Close dialog box' => 'Sulje valintaikkuna', + 'Submit a form' => 'Lähetä lomake', + 'Board view' => 'Taulunäkymä', + 'Keyboard shortcuts' => 'Pikanäppäimet', + 'Open board switcher' => 'Avaa taulun valitsin', + 'Application' => 'Sovellus', + 'Compact view' => 'Tiivis näkymä', + 'Horizontal scrolling' => 'Vaakavieritys', + 'Compact/wide view' => 'Tiivis/leveä näkymä', + 'Currency' => 'Valuutta', + 'Personal project' => 'Henkilökohtainen projekti', + 'AUD - Australian Dollar' => 'AUD - Australian dollari', + 'CAD - Canadian Dollar' => 'CAD - Kanadan dollari', + 'CHF - Swiss Francs' => 'CHF - Sveitsin frangi', + 'Custom Stylesheet' => 'Mukautettu tyylitiedosto', + 'EUR - Euro' => 'EUR - Euro', + 'GBP - British Pound' => 'GBP - Englannin punta', + 'INR - Indian Rupee' => 'INR - Intian rupia', + 'JPY - Japanese Yen' => 'JPY - Japanin jeni', + 'NZD - New Zealand Dollar' => 'NZD - Uuden-Seelannin dollari', + 'PEN - Peruvian Sol' => 'Perun sol', + 'RSD - Serbian dinar' => 'RSD - Serbian dinaari', + 'CNY - Chinese Yuan' => 'CNY - Kiinan yuan', + 'USD - US Dollar' => 'USD - Yhdysvaltain dollari', + 'VES - Venezuelan Bolívar' => 'VES - Venezuelan bolívar', + 'Destination column' => 'Kohdesarake', + 'Move the task to another column when assigned to a user' => 'Siirrä tehtävä toiseen sarakkeeseen, kun se on määritetty käyttäjälle', + 'Move the task to another column when assignee is cleared' => 'Siirrä tehtävä toiseen sarakkeeseen, kun määräys on poistettu', + 'Source column' => 'Lähdesarake', + 'Transitions' => 'Siirtymät', + 'Executer' => 'Suorittaja', + 'Time spent in the column' => 'Aika, joka on käytetty sarakkeessa', + 'Task transitions' => 'Tehtävän siirtymät', + 'Task transitions export' => 'Tehtävän siirtymien vienti', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Tämä raportti sisältää kaikki sarakkeiden siirtymät kullekin tehtävälle päivämäärän, käyttäjän ja kunkin siirtymän käytetyn ajan kanssa.', + 'Currency rates' => 'Valuuttakurssit', + 'Rate' => 'Kurssi', + 'Change reference currency' => 'Vaihda vertailuvaluutta', + 'Reference currency' => 'Vertailuvaluutta', + 'The currency rate has been added successfully.' => 'Valuuttakurssi lisättiin onnistuneesti.', + 'Unable to add this currency rate.' => 'Valuuttakurssia ei voitu lisätä.', + 'Webhook URL' => 'Webhook URL', + '%s removed the assignee of the task %s' => '%s poisti tehtävän %s määräyksen', + 'Information' => 'Tietoja', + 'Check two factor authentication code' => 'Tarkista kaksivaiheisen tunnistautumisen koodi', + 'The two factor authentication code is not valid.' => 'Kaksivaiheisen tunnistautumisen koodi ei ole kelvollinen.', + 'The two factor authentication code is valid.' => 'Kaksivaiheisen tunnistautumisen koodi on kelvollinen.', + 'Code' => 'Koodi', + 'Two factor authentication' => 'Kaksivaiheinen tunnistautuminen', + 'This QR code contains the key URI: ' => 'Tämä QR-koodi sisältää avaimen URI:n:', + 'Check my code' => 'Tarkista koodini', + 'Secret key: ' => 'Salainen avain:', + 'Test your device' => 'Testaa laitettasi', + 'Assign a color when the task is moved to a specific column' => 'Määritä väri, kun tehtävä siirretään tiettyyn sarakkeeseen', + '%s via Kanboard' => '%s Kanboardin kautta', + 'Burndown chart' => 'Burndown-kaavio', + 'This chart show the task complexity over the time (Work Remaining).' => 'Tämä kaavio näyttää tehtävän monimutkaisuuden ajan myötä (Jäljellä oleva työ).', + 'Screenshot taken %s' => 'Kuvakaappaus otettu %s', + 'Add a screenshot' => 'Lisää kuvakaappaus', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Ota kuvakaappaus ja paina CTRL+V tai ⌘+V liittääksesi sen tähän.', + 'Screenshot uploaded successfully.' => 'Kuvakaappaus ladattiin onnistuneesti.', + 'SEK - Swedish Krona' => 'SEK - Ruotsin kruunu', + 'Identifier' => 'Tunnus', + 'Disable two factor authentication' => 'Poista kaksivaiheinen tunnistautuminen käytöstä', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Haluatko todella poistaa kaksivaiheisen tunnistautumisen käytöstä tälle käyttäjälle: "%s"?', + 'Edit link' => 'Muokkaa linkkiä', + 'Start to type task title...' => 'Aloita tehtävän otsikon kirjoittaminen...', + 'A task cannot be linked to itself' => 'Tehtävää ei voida linkittää itseensä', + 'The exact same link already exists' => 'Täsmälleen sama linkki on jo olemassa', + 'Recurrent task is scheduled to be generated' => 'Toistuva tehtävä on ajoitettu luotavaksi', + 'Score' => 'Pisteet', + 'The identifier must be unique' => 'Tunnuksen on oltava yksilöllinen', + 'This linked task id doesn\'t exists' => 'Tätä linkitettyä tehtävän tunnusta ei ole olemassa', + 'This value must be alphanumeric' => 'Tämän arvon on oltava aakkosnumeerinen', + 'Edit recurrence' => 'Muokkaa toistuvuutta', + 'Generate recurrent task' => 'Luo toistuva tehtävä', + 'Trigger to generate recurrent task' => 'Laukaisin toistuvan tehtävän luomiseksi', + 'Factor to calculate new due date' => 'Tekijä uuden määräpäivän laskemiseksi', + 'Timeframe to calculate new due date' => 'Aikataulu uuden määräpäivän laskemiseksi', + 'Base date to calculate new due date' => 'Peruspäivämäärä uuden määräpäivän laskemiseksi', + 'Action date' => 'Toiminnon päivämäärä', + 'Base date to calculate new due date: ' => 'Peruspäivämäärä uuden määräpäivän laskemiseksi:', + 'This task has created this child task: ' => 'Tämä tehtävä on luonut tämän alitehtävän:', + 'Day(s)' => 'Päivä(t)', + 'Existing due date' => 'Olemassa oleva määräpäivä', + 'Factor to calculate new due date: ' => 'Tekijä uuden määräpäivän laskemiseksi:', + 'Month(s)' => 'Kuukausi(t)', + 'This task has been created by: ' => 'Tämän tehtävän on luonut:', + 'Recurrent task has been generated:' => 'Toistuva tehtävä on luotu:', + 'Timeframe to calculate new due date: ' => 'Aikataulu uuden määräpäivän laskemiseksi:', + 'Trigger to generate recurrent task: ' => 'Laukaisin toistuvan tehtävän luomiseksi:', + 'When task is closed' => 'Kun tehtävä suljetaan', + 'When task is moved from first column' => 'Kun tehtävä siirretään ensimmäisestä sarakkeesta', + 'When task is moved to last column' => 'Kun tehtävä siirretään viimeiseen sarakkeeseen', + 'Year(s)' => 'Vuosi(t)', + 'Project settings' => 'Projektin asetukset', + 'Automatically update the start date' => 'Päivitä aloituspäivämäärä automaattisesti', + 'iCal feed' => 'iCal-syöte', + 'Preferences' => 'Asetukset', + 'Security' => 'Turvallisuus', + 'Two factor authentication disabled' => 'Kaksivaiheinen tunnistautuminen poistettu käytöstä', + 'Two factor authentication enabled' => 'Kaksivaiheinen tunnistautuminen käytössä', + 'Unable to update this user.' => 'Tämän käyttäjän päivittäminen epäonnistui.', + 'There is no user management for personal projects.' => 'Henkilökohtaisissa projekteissa ei ole käyttäjien hallintaa.', + 'User that will receive the email' => 'Käyttäjä, joka vastaanottaa sähköpostin', + 'Email subject' => 'Sähköpostin aihe', + 'Date' => 'Päivämäärä', + 'Add a comment log when moving the task between columns' => 'Lisää kommenttiloki, kun tehtävää siirretään sarakkeiden välillä', + 'Move the task to another column when the category is changed' => 'Siirrä tehtävä toiseen sarakkeeseen, kun luokkaa muutetaan', + 'Send a task by email to someone' => 'Lähetä tehtävä sähköpostitse jollekin', + 'Reopen a task' => 'Avaa tehtävä uudelleen', + 'Notification' => 'Ilmoitus', + '%s moved the task #%d to the first swimlane' => '%s siirsi tehtävän #%d ensimmäiseen kaistaan', + 'Swimlane' => 'Kaista', + '%s moved the task %s to the first swimlane' => '%s siirsi tehtävän %s ensimmäiseen kaistaan', + '%s moved the task %s to the swimlane "%s"' => '%s siirsi tehtävän %s kaistaan "%s"', + 'This report contains all subtasks information for the given date range.' => 'Tämä raportti sisältää kaikki alitehtävien tiedot annetulta aikaväliltä.', + 'This report contains all tasks information for the given date range.' => 'Tämä raportti sisältää kaikki tehtävien tiedot annetulta aikaväliltä.', + 'Project activities for %s' => 'Projektin aktiviteetit käyttäjälle %s', + 'view the board on Kanboard' => 'katso taulua Kanboardissa', + 'The task has been moved to the first swimlane' => 'Tehtävä on siirretty ensimmäiseen kaistaan', + 'The task has been moved to another swimlane:' => 'Tehtävä on siirretty toiseen kaistaan:', + 'New title: %s' => 'Uusi otsikko: %s', + 'The task is not assigned anymore' => 'Tehtävää ei ole enää määritetty', + 'New assignee: %s' => 'Uusi määrätty: %s', + 'There is no category now' => 'Ei luokkaa nyt', + 'New category: %s' => 'Uusi luokka: %s', + 'New color: %s' => 'Uusi väri: %s', + 'New complexity: %d' => 'Uusi monimutkaisuus: %d', + 'The due date has been removed' => 'Määräpäivä on poistettu', + 'There is no description anymore' => 'Kuvausta ei ole enää', + 'Recurrence settings has been modified' => 'Toistuvuuden asetuksia on muutettu', + 'Time spent changed: %sh' => 'Käytetty aika muuttui: %sh', + 'Time estimated changed: %sh' => 'Arvioitu aika muuttui: %sh', + 'The field "%s" has been updated' => 'Kenttä "%s" on päivitetty', + 'The description has been modified:' => 'Kuvaus on muokattu:', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Haluatko todella sulkea tehtävän "%s" ja kaikki alitehtävät?', + 'I want to receive notifications for:' => 'Haluan vastaanottaa ilmoituksia:', + 'All tasks' => 'Kaikki tehtävät', + 'Only for tasks assigned to me' => 'Vain minulle määritetyistä tehtävistä', + 'Only for tasks created by me' => 'Vain minun luomistani tehtävistä', + 'Only for tasks created by me and tasks assigned to me' => 'Vain minun luomistani tehtävistä ja minulle määritetyistä tehtävistä', + '%%Y-%%m-%%d' => '%%Y-%%m-%%d', + 'Total for all columns' => 'Yhteensä kaikille sarakkeille', + 'You need at least 2 days of data to show the chart.' => 'Tarvitset vähintään 2 päivän tiedot kaavion näyttämiseksi.', + '<15m' => '<15m', + '<30m' => '<30m', + 'Stop timer' => 'Pysäytä ajastin', + 'Start timer' => 'Käynnistä ajastin', + 'My activity stream' => 'Oma aktiviteettivirta', + 'Search tasks' => 'Hae tehtäviä', + 'Reset filters' => 'Nollaa suodattimet', + 'My tasks due tomorrow' => 'Tehtäväni, jotka ovat huomenna määräpäivä', + 'Tasks due today' => 'Tehtävät, jotka ovat tänään määräpäivä', + 'Tasks due tomorrow' => 'Tehtävät, jotka ovat huomenna määräpäivä', + 'Tasks due yesterday' => 'Tehtävät, jotka olivat eilen määräpäivä', + 'Closed tasks' => 'Suljetut tehtävät', + 'Open tasks' => 'Avoimet tehtävät', + 'Not assigned' => 'Ei määritetty', + 'View advanced search syntax' => 'Näytä tarkennetun haun syntaksi', + 'Overview' => 'Yleiskatsaus', + 'Board/Calendar/List view' => 'Taulu/Kalenteri/Lista-näkymä', + 'Switch to the board view' => 'Vaihda taulunäkymään', + 'Switch to the list view' => 'Vaihda listanäkymään', + 'Go to the search/filter box' => 'Siirry haku/suodatin-kenttään', + 'There is no activity yet.' => 'Ei vielä toimintaa.', + 'No tasks found.' => 'Tehtäviä ei löytynyt.', + 'Keyboard shortcut: "%s"' => 'Pikanäppäin: "%s"', + 'List' => 'Lista', + 'Filter' => 'Suodatin', + 'Advanced search' => 'Tarkennettu haku', + 'Example of query: ' => 'Esimerkki kyselystä:', + 'Search by project: ' => 'Hae projektin mukaan:', + 'Search by column: ' => 'Hae sarakkeen mukaan:', + 'Search by assignee: ' => 'Hae määrätyn mukaan:', + 'Search by color: ' => 'Hae värin mukaan:', + 'Search by category: ' => 'Hae luokan mukaan:', + 'Search by description: ' => 'Hae kuvauksen mukaan:', + 'Search by due date: ' => 'Hae määräpäivän mukaan:', + 'Average time spent in each column' => 'Keskimääräinen aika, joka on käytetty kussakin sarakkeessa', + 'Average time spent' => 'Keskimääräinen käytetty aika', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Tämä kaavio näyttää keskimääräisen ajan, joka on käytetty kussakin sarakkeessa viimeisimmän %d tehtävän osalta.', + 'Average Lead and Cycle time' => 'Keskimääräinen läpimeno- ja sykliaika', + 'Average lead time: ' => 'Keskimääräinen läpimenoaika:', + 'Average cycle time: ' => 'Keskimääräinen sykliaika:', + 'Cycle Time' => 'Sykliaika', + 'Lead Time' => 'Läpimenoaika', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Tämä kaavio näyttää keskimääräisen läpimeno- ja sykliajan viimeisimmän %d tehtävän osalta ajan myötä.', + 'Average time into each column' => 'Keskimääräinen aika kuhunkin sarakkeeseen', + 'Lead and cycle time' => 'Läpimeno- ja sykliaika', + 'Lead time: ' => 'Läpimenoaika:', + 'Cycle time: ' => 'Sykliaika:', + 'Time spent in each column' => 'Aika, joka on käytetty kussakin sarakkeessa', + 'The lead time is the duration between the task creation and the completion.' => 'Läpimenoaika on tehtävän luomisen ja valmistumisen välinen kesto.', + 'The cycle time is the duration between the start date and the completion.' => 'Sykliaika on aloituspäivämäärän ja valmistumisen välinen kesto.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Jos tehtävää ei ole suljettu, käytetään nykyistä aikaa valmistumispäivämäärän sijaan.', + 'Set the start date automatically' => 'Aseta aloituspäivämäärä automaattisesti', + 'Edit Authentication' => 'Muokkaa tunnistautumista', + 'Remote user' => 'Etäkäyttäjä', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Etäkäyttäjät eivät tallenna salasanaansa Kanboard-tietokantaan, esimerkiksi: LDAP-, Google- ja Github-tilit.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Jos valitset ruudun "Älä salli kirjautumislomaketta", kirjautumislomakkeeseen syötetyt tunnukset ohitetaan.', + 'Default task color' => 'Tehtävän oletusväri', + 'This feature does not work with all browsers.' => 'Tämä ominaisuus ei toimi kaikilla selaimilla.', + 'There is no destination project available.' => 'Ei käytettävissä olevaa kohdeprojektia.', + 'Trigger automatically subtask time tracking' => 'Käynnistä automaattisesti alitehtävän ajankäytön seuranta', + 'Include closed tasks in the cumulative flow diagram' => 'Sisällytä suljetut tehtävät kumulatiiviseen virtauskaavioon', + 'Current swimlane: %s' => 'Nykyinen kaista: %s', + 'Current column: %s' => 'Nykyinen sarake: %s', + 'Current category: %s' => 'Nykyinen luokka: %s', + 'no category' => 'ei luokkaa', + 'Current assignee: %s' => 'Nykyinen määrätty: %s', + 'not assigned' => 'ei määritetty', + 'Author:' => 'Tekijä:', + 'contributors' => 'avustajat', + 'License:' => 'Lisenssi:', + 'License' => 'Lisenssi', + 'Enter the text below' => 'Syötä teksti alle', + 'Start date:' => 'Aloituspäivämäärä:', + 'Due date:' => 'Määräpäivä:', + 'People who are project managers' => 'Henkilöt, jotka ovat projektipäälliköitä', + 'People who are project members' => 'Henkilöt, jotka ovat projektin jäseniä', + 'NOK - Norwegian Krone' => 'NOK - Norjan kruunu', + 'Show this column' => 'Näytä tämä sarake', + 'Hide this column' => 'Piilota tämä sarake', + 'End date' => 'Lopetuspäivämäärä', + 'Users overview' => 'Käyttäjien yleiskatsaus', + 'Members' => 'Jäsenet', + 'Shared project' => 'Jaettu projekti', + 'Project managers' => 'Projektipäälliköt', + 'Projects list' => 'Projektilista', + 'End date:' => 'Lopetuspäivämäärä:', + 'Change task color when using a specific task link' => 'Muuta tehtävän väriä, kun käytetään tiettyä tehtävälinkkiä', + 'Task link creation or modification' => 'Tehtävälinkin luominen tai muokkaus', + 'Milestone' => 'Välitavoite', + 'Reset the search/filter box' => 'Nollaa haku/suodatin-kenttä', + 'Documentation' => 'Dokumentaatio', + 'Author' => 'Tekijä', + 'Version' => 'Versio', + 'Plugins' => 'Lisäosat', + 'There is no plugin loaded.' => 'Lisäosia ei ole ladattu.', + 'My notifications' => 'Omat ilmoitukset', + 'Custom filters' => 'Mukautetut suodattimet', + 'Your custom filter has been created successfully.' => 'Mukautettu suodatin on luotu onnistuneesti.', + 'Unable to create your custom filter.' => 'Mukautettua suodatinta ei voitu luoda.', + 'Custom filter removed successfully.' => 'Mukautettu suodatin poistettiin onnistuneesti.', + 'Unable to remove this custom filter.' => 'Mukautetun suodattimen poistaminen epäonnistui.', + 'Edit custom filter' => 'Muokkaa mukautettua suodatinta', + 'Your custom filter has been updated successfully.' => 'Mukautettu suodatin on päivitetty onnistuneesti.', + 'Unable to update custom filter.' => 'Mukautetun suodattimen päivittäminen epäonnistui.', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Uusi liite tehtävässä #%d: %s', + 'New comment on task #%d' => 'Uusi kommentti tehtävässä #%d', + 'Comment updated on task #%d' => 'Kommentti päivitetty tehtävässä #%d', + 'New subtask on task #%d' => 'Uusi alitehtävä tehtävässä #%d', + 'Subtask updated on task #%d' => 'Alitehtävä päivitetty tehtävässä #%d', + 'New task #%d: %s' => 'Uusi tehtävä #%d: %s', + 'Task updated #%d' => 'Tehtävä päivitetty #%d', + 'Task #%d closed' => 'Tehtävä #%d suljettu', + 'Task #%d opened' => 'Tehtävä #%d avattu', + 'Column changed for task #%d' => 'Sarake muutettu tehtävälle #%d', + 'New position for task #%d' => 'Uusi sijainti tehtävälle #%d', + 'Swimlane changed for task #%d' => 'Kaista muutettu tehtävälle #%d', + 'Assignee changed on task #%d' => 'Määräys muutettu tehtävässä #%d', + '%d overdue tasks' => '%d myöhässä olevaa tehtävää', + 'No notification.' => 'Ei ilmoituksia.', + 'Mark all as read' => 'Merkitse kaikki luetuksi', + 'Mark as read' => 'Merkitse luetuksi', + 'Total number of tasks in this column across all swimlanes' => 'Tehtävien kokonaismäärä tässä sarakkeessa kaikilla kaistoilla', + 'Collapse swimlane' => 'Kutista kaista', + 'Expand swimlane' => 'Laajenna kaista', + 'Add a new filter' => 'Lisää uusi suodatin', + 'Share with all project members' => 'Jaa kaikille projektin jäsenille', + 'Shared' => 'Jaettu', + 'Owner' => 'Omistaja', + 'Unread notifications' => 'Lukemattomat ilmoitukset', + 'Notification methods:' => 'Ilmoitusmenetelmät:', + 'Unable to read your file' => 'Tiedostoa ei voitu lukea', + '%d task(s) have been imported successfully.' => '%d tehtävä(t) on tuotu onnistuneesti.', + 'Nothing has been imported!' => 'Mitään ei ole tuotu!', + 'Import users from CSV file' => 'Tuo käyttäjät CSV-tiedostosta', + '%d user(s) have been imported successfully.' => '%d käyttäjä(t) on tuotu onnistuneesti.', + 'Comma' => 'Pilkku', + 'Semi-colon' => 'Puolipiste', + 'Tab' => 'Sarkain', + 'Vertical bar' => 'Pystyviiva', + 'Double Quote' => 'Kaksoishevonkenkä', + 'Single Quote' => 'Yksinkertainen hevonkenkä', + '%s attached a file to the task #%d' => '%s liitti tiedoston tehtävään #%d', + 'There is no column or swimlane activated in your project!' => 'Projektissasi ei ole aktivoitu sarakkeita tai kaistoja!', + 'Append filter (instead of replacement)' => 'Lisää suodatin (korvaamisen sijaan)', + 'Append/Replace' => 'Lisää/Korvaa', + 'Append' => 'Lisää', + 'Replace' => 'Korvaa', + 'Import' => 'Tuo', + 'Change sorting' => 'Muuta lajittelua', + 'Tasks Importation' => 'Tehtävien tuonti', + 'Delimiter' => 'Erotin', + 'Enclosure' => 'Sulkeet', + 'CSV File' => 'CSV-tiedosto', + 'Instructions' => 'Ohjeet', + 'Your file must use the predefined CSV format' => 'Tiedostosi on käytettävä es määriteltyä CSV-muotoa', + 'Your file must be encoded in UTF-8' => 'Tiedostosi on oltava koodattu UTF-8:lla', + 'The first row must be the header' => 'Ensimmäisen rivin on oltava otsikkorivi', + 'Duplicates are not verified for you' => 'Kopioita ei tarkisteta puolestasi', + 'The due date must use the ISO format: YYYY-MM-DD' => 'Määräpäivän on käytettävä ISO-muotoa: VVVV-KK-PV', + 'Download CSV template' => 'Lataa CSV-mallitiedosto', + 'No external integration registered.' => 'Ei ulkoisia integraatioita rekisteröity.', + 'Duplicates are not imported' => 'Kopioita ei tuoda', + 'Usernames must be lowercase and unique' => 'Käyttäjänimien on oltava pienikirjaimisia ja yksilöllisiä', + 'Passwords will be encrypted if present' => 'Salasanat salataan, jos niitä on', + '%s attached a new file to the task %s' => '%s liitti uuden tiedoston tehtävään %s', + 'Link type' => 'Linkin tyyppi', + 'Assign automatically a category based on a link' => 'Määritä automaattisesti luokka linkin perusteella', + 'BAM - Konvertible Mark' => 'BAM - Konvertibilna Marka', + 'Assignee Username' => 'Määrätyn käyttäjänimi', + 'Assignee Name' => 'Määrätyn nimi', + 'Groups' => 'Ryhmät', + 'Members of %s' => 'Ryhmän %s jäsenet', + 'New group' => 'Uusi ryhmä', + 'Group created successfully.' => 'Ryhmä luotiin onnistuneesti.', + 'Unable to create your group.' => 'Ryhmää ei voitu luoda.', + 'Edit group' => 'Muokkaa ryhmää', + 'Group updated successfully.' => 'Ryhmä päivitettiin onnistuneesti.', + 'Unable to update your group.' => 'Ryhmän päivittäminen epäonnistui.', + 'Add group member to "%s"' => 'Lisää ryhmän jäsen ryhmään "%s"', + 'Group member added successfully.' => 'Ryhmän jäsen lisättiin onnistuneesti.', + 'Unable to add group member.' => 'Ryhmän jäsentä ei voitu lisätä.', + 'Remove user from group "%s"' => 'Poista käyttäjä ryhmästä "%s"', + 'User removed successfully from this group.' => 'Käyttäjä poistettiin onnistuneesti tästä ryhmästä.', + 'Unable to remove this user from the group.' => 'Tämän käyttäjän poistaminen ryhmästä epäonnistui.', + 'Remove group' => 'Poista ryhmä', + 'Group removed successfully.' => 'Ryhmä poistettiin onnistuneesti.', + 'Unable to remove this group.' => 'Ryhmän poistaminen epäonnistui.', + 'Project Permissions' => 'Projektin käyttöoikeudet', + 'Manager' => 'Päällikkö', + 'Project Manager' => 'Projektipäällikkö', + 'Project Member' => 'Projektin jäsen', + 'Project Viewer' => 'Projektin katselija', + 'Your account is locked for %d minutes' => 'Tilisi on lukittu %d minuutiksi', + 'Invalid captcha' => 'Virheellinen captcha', + 'The name must be unique' => 'Nimen on oltava yksilöllinen', + 'View all groups' => 'Näytä kaikki ryhmät', + 'There is no user available.' => 'Käyttäjiä ei ole saatavilla.', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Haluatko todella poistaa käyttäjän "%s" ryhmästä "%s"?', + 'There is no group.' => 'Ei ryhmiä.', + 'Add group member' => 'Lisää ryhmän jäsen', + 'Do you really want to remove this group: "%s"?' => 'Haluatko todella poistaa tämän ryhmän: "%s"?', + 'There is no user in this group.' => 'Ei käyttäjiä tässä ryhmässä.', + 'Permissions' => 'Käyttöoikeudet', + 'Allowed Users' => 'Sallitut käyttäjät', + 'No specific user has been allowed.' => 'Mitään tiettyä käyttäjää ei ole sallittu.', + 'Role' => 'Rooli', + 'Enter user name...' => 'Syötä käyttäjänimi...', + 'Allowed Groups' => 'Sallitut ryhmät', + 'No group has been allowed.' => 'Mitään ryhmää ei ole sallittu.', + 'Group' => 'Ryhmä', + 'Group Name' => 'Ryhmän nimi', + 'Enter group name...' => 'Syötä ryhmän nimi...', + 'Role:' => 'Rooli:', + 'Project members' => 'Projektin jäsenet', + '%s mentioned you in the task #%d' => '%s mainitsi sinut tehtävässä #%d', + '%s mentioned you in a comment on the task #%d' => '%s mainitsi sinut kommentissa tehtävässä #%d', + 'You were mentioned in the task #%d' => 'Sinut mainittiin tehtävässä #%d', + 'You were mentioned in a comment on the task #%d' => 'Sinut mainittiin kommentissa tehtävässä #%d', + 'Estimated hours: ' => 'Arvioidut tunnit:', + 'Actual hours: ' => 'Todelliset tunnit:', + 'Hours Spent' => 'Käytetyt tunnit', + 'Hours Estimated' => 'Arvioidut tunnit', + 'Estimated Time' => 'Arvioitu aika', + 'Actual Time' => 'Todellinen aika', + 'Estimated vs actual time' => 'Arvioitu vs todellinen aika', + 'RUB - Russian Ruble' => 'RUB - Venäjän rupla', + 'Assign the task to the person who does the action when the column is changed' => 'Määritä tehtävä henkilölle, joka suorittaa toimenpiteen saraketta vaihdettaessa', + 'Close a task in a specific column' => 'Sulje tehtävä tietyssä sarakkeessa', + 'Time-based One-time Password Algorithm' => 'Aikaan perustuva kertaluonteinen salasana-algoritmi', + 'Two-Factor Provider: ' => 'Kaksivaiheisen tunnistautumisen tarjoaja:', + 'Disable two-factor authentication' => 'Poista kaksivaiheinen tunnistautuminen käytöstä', + 'Enable two-factor authentication' => 'Ota kaksivaiheinen tunnistautuminen käyttöön', + 'There is no integration registered at the moment.' => 'Tällä hetkellä ei ole rekisteröity integraatioita.', + 'Password Reset for Kanboard' => 'Kanboardin salasanan palautus', + 'Forgot password?' => 'Unohtuiko salasana?', + 'Enable "Forget Password"' => 'Ota "Unohdin salasanan" käyttöön', + 'Password Reset' => 'Salasanan palautus', + 'New password' => 'Uusi salasana', + 'Change Password' => 'Vaihda salasana', + 'To reset your password click on this link:' => 'Palauttaaksesi salasanasi klikkaa tätä linkkiä:', + 'Last Password Reset' => 'Viimeisin salasanan palautus', + 'The password has never been reinitialized.' => 'Salasanaa ei ole koskaan alustettu uudelleen.', + 'Creation' => 'Luonti', + 'Expiration' => 'Vanheneminen', + 'Password reset history' => 'Salasanan palautushistoria', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Kaikki sarakkeen "%s" ja kaistan "%s" tehtävät on suljettu onnistuneesti.', + 'Do you really want to close all tasks of this column?' => 'Haluatko todella sulkea kaikki tämän sarakkeen tehtävät?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d tehtävä(t) sarakkeessa "%s" ja kaistalla "%s" suljetaan.', + 'Close all tasks in this column and this swimlane' => 'Sulje kaikki tehtävät tässä sarakkeessa ja tällä kaistalla', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Yksikään lisäosa ei ole rekisteröinyt projektin ilmoitusmenetelmää. Voit edelleen määrittää yksittäisiä ilmoituksia käyttäjäprofiilissasi.', + 'My dashboard' => 'Oma hallintapaneelini', + 'My profile' => 'Oma profiilini', + 'Project owner: ' => 'Projektin omistaja:', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'Projektin tunniste on valinnainen ja sen on oltava aakkosnumeerinen, esimerkiksi: MYPROJECT.', + 'Project owner' => 'Projektin omistaja', + 'Personal projects do not have users and groups management.' => 'Henkilökohtaisissa projekteissa ei ole käyttäjä- ja ryhmähallintaa.', + 'There is no project member.' => 'Ei projektin jäseniä.', + 'Priority' => 'Prioriteetti', + 'Task priority' => 'Tehtävän prioriteetti', + 'General' => 'Yleiset', + 'Dates' => 'Päivämäärät', + 'Default priority' => 'Oletusprioriteetti', + 'Lowest priority' => 'Alin prioriteetti', + 'Highest priority' => 'Korkein prioriteetti', + 'Close a task when there is no activity' => 'Sulje tehtävä, kun siinä ei ole toimintaa', + 'Duration in days' => 'Kesto päivinä', + 'Send email when there is no activity on a task' => 'Lähetä sähköposti, kun tehtävässä ei ole toimintaa', + 'Unable to fetch link information.' => 'Linkin tietojen haku epäonnistui.', + 'Daily background job for tasks' => 'Päivittäinen taustatyö tehtäville', + 'Auto' => 'Auto', + 'Related' => 'Liittyvät', + 'Attachment' => 'Liite', + 'Web Link' => 'Web-linkki', + 'External links' => 'Ulkoiset linkit', + 'Add external link' => 'Lisää ulkoinen linkki', + 'Type' => 'Tyyppi', + 'Dependency' => 'Riippuvuus', + 'Add internal link' => 'Lisää sisäinen linkki', + 'Add a new external link' => 'Lisää uusi ulkoinen linkki', + 'Edit external link' => 'Muokkaa ulkoista linkkiä', + 'External link' => 'Ulkoinen linkki', + 'Copy and paste your link here...' => 'Kopioi ja liitä linkkisi tähän...', + 'URL' => 'URL', + 'Internal links' => 'Sisäiset linkit', + 'Assign to me' => 'Määritä minulle', + 'Me' => 'Minä', + 'Do not duplicate anything' => 'Älä kopioi mitään', + 'Projects management' => 'Projektien hallinta', + 'Users management' => 'Käyttäjien hallinta', + 'Groups management' => 'Ryhmien hallinta', + 'Create from another project' => 'Luo toisesta projektista', + 'open' => 'avoinna', + 'closed' => 'suljettu', + 'Priority:' => 'Prioriteetti:', + 'Reference:' => 'Viite:', + 'Complexity:' => 'Monimutkaisuus:', + 'Swimlane:' => 'Kaista:', + 'Column:' => 'Sarake:', + 'Position:' => 'Sijainti:', + 'Creator:' => 'Luonut:', + 'Time estimated:' => 'Arvioitu aika:', + '%s hours' => '%s tuntia', + 'Time spent:' => 'Käytetty aika:', + 'Created:' => 'Luotu:', + 'Modified:' => 'Muokattu:', + 'Completed:' => 'Valmistunut:', + 'Started:' => 'Aloitettu:', + 'Moved:' => 'Siirretty:', + 'Task #%d' => 'Tehtävä #%d', + 'Time format' => 'Ajan muoto', + 'Start date: ' => 'Aloituspäivämäärä:', + 'End date: ' => 'Lopetuspäivämäärä:', + 'New due date: ' => 'Uusi määräpäivä:', + 'Start date changed: ' => 'Aloituspäivämäärä muutettu:', + 'Disable personal projects' => 'Poista henkilökohtaiset projektit käytöstä', + 'Do you really want to remove this custom filter: "%s"?' => 'Haluatko todella poistaa tämän mukautetun suodattimen: "%s"?', + 'Remove a custom filter' => 'Poista mukautettu suodatin', + 'User activated successfully.' => 'Käyttäjä aktivoitu onnistuneesti.', + 'Unable to enable this user.' => 'Tämän käyttäjän aktivointi epäonnistui.', + 'User disabled successfully.' => 'Käyttäjä poistettu käytöstä onnistuneesti.', + 'Unable to disable this user.' => 'Tämän käyttäjän käytöstä poistaminen epäonnistui.', + 'All files have been uploaded successfully.' => 'Kaikki tiedostot on ladattu onnistuneesti.', + 'The maximum allowed file size is %sB.' => 'Suurin sallittu tiedostokoko on %sB.', + 'Drag and drop your files here' => 'Vedä ja pudota tiedostosi tähän', + 'choose files' => 'valitse tiedostot', + 'View profile' => 'Näytä profiili', + 'Two Factor' => 'Kaksivaiheinen', + 'Disable user' => 'Poista käyttäjä käytöstä', + 'Do you really want to disable this user: "%s"?' => 'Haluatko todella poistaa tämän käyttäjän käytöstä: "%s"?', + 'Enable user' => 'Ota käyttäjä käyttöön', + 'Do you really want to enable this user: "%s"?' => 'Haluatko todella ottaa tämän käyttäjän käyttöön: "%s"?', + 'Download' => 'Lataa', + 'Uploaded: %s' => 'Ladattu: %s', + 'Size: %s' => 'Koko: %s', + 'Uploaded by %s' => 'Ladannut %s', + 'Filename' => 'Tiedostonimi', + 'Size' => 'Koko', + 'Column created successfully.' => 'Sarake luotiin onnistuneesti.', + 'Another column with the same name exists in the project' => 'Projektissa on jo toinen sarake samalla nimellä', + 'Default filters' => 'Oletussuodattimet', + 'Your board doesn\'t have any columns!' => 'Taulussasi ei ole yhtään saraketta!', + 'Change column position' => 'Muuta sarakkeen sijaintia', + 'Switch to the project overview' => 'Vaihda projektin yleiskatsaukseen', + 'User filters' => 'Käyttäjäsuodattimet', + 'Category filters' => 'Luokkusuodattimet', + 'Upload a file' => 'Lataa tiedosto', + 'View file' => 'Näytä tiedosto', + 'Last activity' => 'Viimeisin aktiviteetti', + 'Change subtask position' => 'Muuta alitehtävän sijaintia', + 'This value must be greater than %d' => 'Tämän arvon on oltava suurempi kuin %d', + 'Another swimlane with the same name exists in the project' => 'Projektissa on jo toinen kaista samalla nimellä', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Esimerkki: https://example.kanboard.org/ (käytetään absoluuttisten URL-osoitteiden luomiseen)', + 'Actions duplicated successfully.' => 'Toiminnot kopioitu onnistuneesti.', + 'Unable to duplicate actions.' => 'Toimintoja ei voitu kopioida.', + 'Add a new action' => 'Lisää uusi toiminto', + 'Import from another project' => 'Tuo toisesta projektista', + 'There is no action at the moment.' => 'Tällä hetkellä ei ole toimintoja.', + 'Import actions from another project' => 'Tuo toiminnot toisesta projektista', + 'There is no available project.' => 'Ei käytettävissä olevia projekteja.', + 'Local File' => 'Paikallinen tiedosto', + 'Configuration' => 'Konfiguraatio', + 'PHP version:' => 'PHP-versio:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'Käyttöjärjestelmän versio:', + 'Database version:' => 'Tietokannan versio:', + 'Browser:' => 'Selain:', + 'Task view' => 'Tehtävänäkymä', + 'Edit task' => 'Muokkaa tehtävää', + 'Edit description' => 'Muokkaa kuvausta', + 'New internal link' => 'Uusi sisäinen linkki', + 'Display list of keyboard shortcuts' => 'Näytä lista pikanäppäimistä', + 'Avatar' => 'Avatar', + 'Upload my avatar image' => 'Lataa avatar-kuvani', + 'Remove my image' => 'Poista kuvani', + 'The OAuth2 state parameter is invalid' => 'OAuth2 state -parametri on virheellinen', + 'User not found.' => 'Käyttäjää ei löydy.', + 'Search in activity stream' => 'Hae aktiviteettivirrasta', + 'My activities' => 'Omat aktiviteetit', + 'Activity until yesterday' => 'Aktiviteetti eiliseen asti', + 'Activity until today' => 'Aktiviteetti tähän päivään asti', + 'Search by creator: ' => 'Hae luojan mukaan:', + 'Search by creation date: ' => 'Hae luontipäivämäärän mukaan:', + 'Search by task status: ' => 'Hae tehtävän tilan mukaan:', + 'Search by task title: ' => 'Hae tehtävän otsikon mukaan:', + 'Activity stream search' => 'Aktiviteettivirran haku', + 'Projects where "%s" is manager' => 'Projektit, joissa "%s" on päällikkö', + 'Projects where "%s" is member' => 'Projektit, joissa "%s" on jäsen', + 'Open tasks assigned to "%s"' => 'Avoimet tehtävät, jotka on määritetty käyttäjälle "%s"', + 'Closed tasks assigned to "%s"' => 'Suljetut tehtävät, jotka on määritetty käyttäjälle "%s"', + 'Assign automatically a color based on a priority' => 'Määritä automaattisesti väri prioriteetin perusteella', + 'Overdue tasks for the project(s) "%s"' => 'Myöhässä olevat tehtävät projektille/projekteille "%s"', + 'Upload files' => 'Lataa tiedostoja', + 'Installed Plugins' => 'Asennetut lisäosat', + 'Plugin Directory' => 'Lisäosahakemisto', + 'Plugin installed successfully.' => 'Lisäosa asennettiin onnistuneesti.', + 'Plugin updated successfully.' => 'Lisäosa päivitettiin onnistuneesti.', + 'Plugin removed successfully.' => 'Lisäosa poistettiin onnistuneesti.', + 'Subtask converted to task successfully.' => 'Alitehtävä muunnettiin tehtäväksi onnistuneesti.', + 'Unable to convert the subtask.' => 'Alitehtävää ei voitu muuntaa.', + 'Unable to extract plugin archive.' => 'Lisäosan arkistoa ei voitu purkaa.', + 'Plugin not found.' => 'Lisäosaa ei löydy.', + 'You don\'t have the permission to remove this plugin.' => 'Sinulla ei ole oikeutta poistaa tätä lisäosaa.', + 'Unable to download plugin archive.' => 'Lisäosan arkiston lataaminen epäonnistui.', + 'Unable to write temporary file for plugin.' => 'Lisäosan väliaikaista tiedostoa ei voitu kirjoittaa.', + 'Unable to open plugin archive.' => 'Lisäosan arkistoa ei voitu avata.', + 'There is no file in the plugin archive.' => 'Lisäosan arkistossa ei ole tiedostoa.', + 'Create tasks in bulk' => 'Luo tehtäviä joukkona', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Kanboard-instanssiasi ei ole määritetty asentamaan lisäosia käyttöliittymän kautta.', + 'There is no plugin available.' => 'Lisäosia ei ole saatavilla.', + 'Install' => 'Asenna', + 'Update' => 'Päivitä', + 'Up to date' => 'Ajantasalla', + 'Not available' => 'Ei saatavilla', + 'Remove plugin' => 'Poista lisäosa', + 'Do you really want to remove this plugin: "%s"?' => 'Haluatko todella poistaa tämän lisäosan: "%s"?', + 'Uninstall' => 'Poista asennus', + 'Listing' => 'Listaus', + 'Metadata' => 'Metatiedot', + 'Manage projects' => 'Hallinnoi projekteja', + 'Convert to task' => 'Muunna tehtäväksi', + 'Convert sub-task to task' => 'Muunna alitehtävä tehtäväksi', + 'Do you really want to convert this sub-task to a task?' => 'Haluatko todella muuntaa tämän alitehtävän tehtäväksi?', + 'My task title' => 'Oman tehtävän otsikko', + 'Enter one task by line.' => 'Syötä yksi tehtävä riviä kohden.', + 'Number of failed login:' => 'Epäonnistuneiden kirjautumisten määrä:', + 'Account locked until:' => 'Tili lukittu:', + 'Email settings' => 'Sähköpostiasetukset', + 'Email sender address' => 'Sähköpostin lähettäjän osoite', + 'Email transport' => 'Sähköpostin siirto', + 'Webhook token' => 'Webhook-tunnus', + 'Project tags management' => 'Projektin tunnisteiden hallinta', + 'Tag created successfully.' => 'Tunniste luotiin onnistuneesti.', + 'Unable to create this tag.' => 'Tätä tunnistetta ei voitu luoda.', + 'Tag updated successfully.' => 'Tunniste päivitettiin onnistuneesti.', + 'Unable to update this tag.' => 'Tätä tunnistetta ei voitu päivittää.', + 'Tag removed successfully.' => 'Tunniste poistettiin onnistuneesti.', + 'Unable to remove this tag.' => 'Tämän tunnisteen poistaminen epäonnistui.', + 'Global tags management' => 'Globaalien tunnisteiden hallinta', + 'Tags' => 'Tunnisteet', + 'Tags management' => 'Tunnisteiden hallinta', + 'Add new tag' => 'Lisää uusi tunniste', + 'Edit a tag' => 'Muokkaa tunnistetta', + 'Project tags' => 'Projektin tunnisteet', + 'There is no specific tag for this project at the moment.' => 'Tällä hetkellä projektille ei ole erityistä tunnistetta.', + 'Tag' => 'Tunniste', + 'Remove a tag' => 'Poista tunniste', + 'Do you really want to remove this tag: "%s"?' => 'Haluatko todella poistaa tämän tunnisteen: "%s"?', + 'Global tags' => 'Globaalit tunnisteet', + 'There is no global tag at the moment.' => 'Tällä hetkellä ei ole globaalia tunnistetta.', + 'This field cannot be empty' => 'Tämä kenttä ei saa olla tyhjä', + 'Close a task when there is no activity in a specific column' => 'Sulje tehtävä, kun tietyssä sarakkeessa ei ole toimintaa', + '%s removed a subtask for the task #%d' => '%s poisti alitehtävän tehtävälle #%d', + '%s removed a comment on the task #%d' => '%s poisti kommentin tehtävästä #%d', + 'Comment removed on task #%d' => 'Kommentti poistettu tehtävästä #%d', + 'Subtask removed on task #%d' => 'Alitehtävä poistettu tehtävästä #%d', + 'Hide tasks in this column in the dashboard' => 'Piilota tehtävät tässä sarakkeessa hallintapaneelissa', + '%s removed a comment on the task %s' => '%s poisti kommentin tehtävästä %s', + '%s removed a subtask for the task %s' => '%s poisti alitehtävän tehtävästä %s', + 'Comment removed' => 'Kommentti poistettu', + 'Subtask removed' => 'Alitehtävä poistettu', + '%s set a new internal link for the task #%d' => '%s asetti uuden sisäisen linkin tehtävälle #%d', + '%s removed an internal link for the task #%d' => '%s poisti sisäisen linkin tehtävälle #%d', + 'A new internal link for the task #%d has been defined' => 'Uusi sisäinen linkki tehtävälle #%d on määritetty', + 'Internal link removed for the task #%d' => 'Sisäinen linkki poistettu tehtävälle #%d', + '%s set a new internal link for the task %s' => '%s asetti uuden sisäisen linkin tehtävälle %s', + '%s removed an internal link for the task %s' => '%s poisti sisäisen linkin tehtävälle %s', + 'Automatically set the due date on task creation' => 'Aseta automaattisesti määräpäivä tehtävän luonnin yhteydessä', + 'Move the task to another column when closed' => 'Siirrä tehtävä toiseen sarakkeeseen, kun se suljetaan', + 'Move the task to another column when not moved during a given period' => 'Siirrä tehtävä toiseen sarakkeeseen, kun sitä ei ole siirretty tietyn ajanjakson aikana', + 'Dashboard for %s' => 'Hallintapaneeli käyttäjälle %s', + 'Tasks overview for %s' => 'Tehtävien yleiskatsaus käyttäjälle %s', + 'Subtasks overview for %s' => 'Alitehtävien yleiskatsaus käyttäjälle %s', + 'Projects overview for %s' => 'Projektien yleiskatsaus käyttäjälle %s', + 'Activity stream for %s' => 'Aktiviteettivirta käyttäjälle %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Määritä väri, kun tehtävä siirretään tiettyyn kaistaan', + 'Assign a priority when the task is moved to a specific swimlane' => 'Määritä prioriteetti, kun tehtävä siirretään tiettyyn kaistaan', + 'User unlocked successfully.' => 'Käyttäjä avattiin onnistuneesti.', + 'Unable to unlock the user.' => 'Käyttäjää ei voitu avata.', + 'Move a task to another swimlane' => 'Siirrä tehtävä toiseen kaistaan', + 'Creator Name' => 'Luojan nimi', + 'Time spent and estimated' => 'Käytetty ja arvioitu aika', + 'Move position' => 'Siirrä sijaintia', + 'Move task to another position on the board' => 'Siirrä tehtävä toiseen sijaintiin taululla', + 'Insert before this task' => 'Lisää ennen tätä tehtävää', + 'Insert after this task' => 'Lisää tämän tehtävän jälkeen', + 'Unlock this user' => 'Avaa tämä käyttäjä', + 'Custom Project Roles' => 'Mukautetut projektiroolit', + 'Add a new custom role' => 'Lisää uusi mukautettu rooli', + 'Restrictions for the role "%s"' => 'Rajoitukset roolille "%s"', + 'Add a new project restriction' => 'Lisää uusi projektirajoitus', + 'Add a new drag and drop restriction' => 'Lisää uusi vedä ja pudota -rajoitus', + 'Add a new column restriction' => 'Lisää uusi sarakkeiden rajoitus', + 'Edit this role' => 'Muokkaa tätä roolia', + 'Remove this role' => 'Poista tämä rooli', + 'There is no restriction for this role.' => 'Tälle roolille ei ole rajoituksia.', + 'Only moving task between those columns is permitted' => 'Tehtävän siirtäminen on sallittua vain näiden sarakkeiden välillä', + 'Close a task in a specific column when not moved during a given period' => 'Sulje tehtävä tietyssä sarakkeessa, kun sitä ei ole siirretty tietyn ajanjakson aikana', + 'Edit columns' => 'Muokkaa sarakkeita', + 'The column restriction has been created successfully.' => 'Sarakkeiden rajoitus luotiin onnistuneesti.', + 'Unable to create this column restriction.' => 'Tätä sarakkeiden rajoitusta ei voitu luoda.', + 'Column restriction removed successfully.' => 'Sarakkeiden rajoitus poistettiin onnistuneesti.', + 'Unable to remove this restriction.' => 'Tämän rajoituksen poistaminen epäonnistui.', + 'Your custom project role has been created successfully.' => 'Mukautettu projektiroolisi luotiin onnistuneesti.', + 'Unable to create custom project role.' => 'Mukautettua projektiroolia ei voitu luoda.', + 'Your custom project role has been updated successfully.' => 'Mukautettu projektiroolisi on päivitetty onnistuneesti.', + 'Unable to update custom project role.' => 'Mukautetun projektiroolin päivittäminen epäonnistui.', + 'Custom project role removed successfully.' => 'Mukautettu projektirooli poistettiin onnistuneesti.', + 'Unable to remove this project role.' => 'Tämän projektiroolin poistaminen epäonnistui.', + 'The project restriction has been created successfully.' => 'Projektirajoitus luotiin onnistuneesti.', + 'Unable to create this project restriction.' => 'Tätä projektirajoitusta ei voitu luoda.', + 'Project restriction removed successfully.' => 'Projektirajoitus poistettiin onnistuneesti.', + 'You cannot create tasks in this column.' => 'Et voi luoda tehtäviä tähän sarakkeeseen.', + 'Task creation is permitted for this column' => 'Tehtävien luominen on sallittu tähän sarakkeeseen', + 'Closing or opening a task is permitted for this column' => 'Tehtävän sulkeminen tai avaaminen on sallittu tähän sarakkeeseen', + 'Task creation is blocked for this column' => 'Tehtävien luominen on estetty tähän sarakkeeseen', + 'Closing or opening a task is blocked for this column' => 'Tehtävän sulkeminen tai avaaminen on estetty tähän sarakkeeseen', + 'Task creation is not permitted' => 'Tehtävien luominen ei ole sallittua', + 'Closing or opening a task is not permitted' => 'Tehtävän sulkeminen tai avaaminen ei ole sallittua', + 'New drag and drop restriction for the role "%s"' => 'Uusi vedä ja pudota -rajoitus roolille "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Tähän rooliin kuuluvat henkilöt voivat siirtää tehtäviä vain lähde- ja kohdesarakkeen välillä.', + 'Remove a column restriction' => 'Poista sarakkeiden rajoitus', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Haluatko todella poistaa tämän sarakkeiden rajoituksen: "%s" to "%s"?', + 'New column restriction for the role "%s"' => 'Uusi sarakkeiden rajoitus roolille "%s"', + 'Rule' => 'Sääntö', + 'Do you really want to remove this column restriction?' => 'Haluatko todella poistaa tämän sarakkeiden rajoituksen?', + 'Custom roles' => 'Mukautetut roolit', + 'New custom project role' => 'Uusi mukautettu projektirooli', + 'Edit custom project role' => 'Muokkaa mukautettua projektiroolia', + 'Remove a custom role' => 'Poista mukautettu rooli', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Haluatko todella poistaa tämän mukautetun roolin: "%s"? Kaikista tähän rooliin määritetyistä henkilöistä tulee projektin jäseniä.', + 'There is no custom role for this project.' => 'Tälle projektille ei ole mukautettua roolia.', + 'New project restriction for the role "%s"' => 'Uusi projektirajoitus roolille "%s"', + 'Restriction' => 'Rajoitus', + 'Remove a project restriction' => 'Poista projektirajoitus', + 'Do you really want to remove this project restriction: "%s"?' => 'Haluatko todella poistaa tämän projektirajoituksen: "%s"?', + 'Duplicate to multiple projects' => 'Kopioi useisiin projekteihin', + 'This field is required' => 'Tämä kenttä on pakollinen', + 'Moving a task is not permitted' => 'Tehtävän siirtäminen ei ole sallittua', + 'This value must be in the range %d to %d' => 'Tämän arvon on oltava välillä %d–%d', + 'You are not allowed to move this task.' => 'Sinulla ei ole oikeutta siirtää tätä tehtävää.', + 'API User Access' => 'API-käyttäjän käyttöoikeus', + 'Preview' => 'Esikatselu', + 'Write' => 'Kirjoita', + 'Write your text in Markdown' => 'Kirjoita teksti Markdown-muodossa', + 'No personal API access token registered.' => 'Henkilökohtaista API-käyttöoikeustunnusta ei ole rekisteröity.', + 'Your personal API access token is "%s"' => 'Henkilökohtainen API-käyttöoikeustunnuksesi on "%s"', + 'Remove your token' => 'Poista tunnuksesi', + 'Generate a new token' => 'Luo uusi tunnus', + 'Showing %d-%d of %d' => 'Näytetään %d–%d / %d', + 'Outgoing Emails' => 'Lähtevät sähköpostit', + 'Add or change currency rate' => 'Lisää tai muuta valuuttakurssi', + 'Reference currency: %s' => 'Vertailuvaluutta: %s', + 'Add custom filters' => 'Lisää mukautettuja suodattimia', + 'Export' => 'Vie', + 'Add link label' => 'Lisää linkin selite', + 'Incompatible Plugins' => 'Yhteensopimattomat lisäosat', + 'Compatibility' => 'Yhteensopivuus', + 'Permissions and ownership' => 'Oikeudet ja omistajuus', + 'Priorities' => 'Prioriteetit', + 'Close this window' => 'Sulje tämä ikkuna', + 'Unable to upload this file.' => 'Tiedoston lataaminen epäonnistui.', + 'Import tasks' => 'Tuo tehtäviä', + 'Choose a project' => 'Valitse projekti', + 'Profile' => 'Profiili', + 'Application role' => 'Sovelluksen rooli', + '%d invitations were sent.' => '%d kutsua lähetettiin.', + '%d invitation was sent.' => '%d kutsu lähetettiin.', + 'Unable to create this user.' => 'Tämän käyttäjän luominen epäonnistui.', + 'Kanboard Invitation' => 'Kanboard-kutsu', + 'Visible on dashboard' => 'Näkyvissä hallintapaneelissa', + 'Created at:' => 'Luotu:', + 'Updated at:' => 'Päivitetty:', + 'There is no custom filter.' => 'Mukautettuja suodattimia ei ole.', + 'New User' => 'Uusi käyttäjä', + 'Authentication' => 'Tunnistautuminen', + 'If checked, this user will use a third-party system for authentication.' => 'Jos valittu, tämä käyttäjä käyttää kolmannen osapuolen järjestelmää tunnistautumiseen.', + 'The password is necessary only for local users.' => 'Salasana on tarpeen vain paikallisille käyttäjille.', + 'You have been invited to register on Kanboard.' => 'Sinut on kutsuttu rekisteröitymään Kanboardiin.', + 'Click here to join your team' => 'Klikkaa tästä liittyäksesi tiimiisi', + 'Invite people' => 'Kutsu ihmisiä', + 'Emails' => 'Sähköpostit', + 'Enter one email address by line.' => 'Syötä yksi sähköpostiosoite riviä kohden.', + 'Add these people to this project' => 'Lisää nämä henkilöt tähän projektiin', + 'Add this person to this project' => 'Lisää tämä henkilö tähän projektiin', + 'Sign-up' => 'Rekisteröidy', + 'Credentials' => 'Tunnistetiedot', + 'New user' => 'Uusi käyttäjä', + 'This username is already taken' => 'Tämä käyttäjänimi on jo käytössä', + 'Your profile must have a valid email address.' => 'Profiilillasi on oltava kelvollinen sähköpostiosoite.', + 'TRL - Turkish Lira' => 'TRL - Turkin liira', + 'The project email is optional and could be used by several plugins.' => 'Projektin sähköposti on valinnainen ja sitä voivat käyttää useat lisäosat.', + 'The project email must be unique across all projects' => 'Projektin sähköpostin on oltava yksilöllinen kaikissa projekteissa', + 'The email configuration has been disabled by the administrator.' => 'Sähköpostiasetukset on poistettu käytöstä järjestelmänvalvojan toimesta.', + 'Close this project' => 'Sulje tämä projekti', + 'Open this project' => 'Avaa tämä projekti', + 'Close a project' => 'Sulje projekti', + 'Do you really want to close this project: "%s"?' => 'Haluatko todella sulkea tämän projektin: "%s"?', + 'Reopen a project' => 'Avaa projekti uudelleen', + 'Do you really want to reopen this project: "%s"?' => 'Haluatko todella avata tämän projektin uudelleen: "%s"?', + 'This project is open' => 'Tämä projekti on avoinna', + 'This project is closed' => 'Tämä projekti on suljettu', + 'Unable to upload files, check the permissions of your data folder.' => 'Tiedostojen lataaminen epäonnistui, tarkista datakansion oikeudet.', + 'Another category with the same name exists in this project' => 'Projektissa on jo toinen luokka samalla nimellä', + 'Comment sent by email successfully.' => 'Kommentti lähetettiin sähköpostitse onnistuneesti.', + 'Sent by email to "%s" (%s)' => 'Lähetetty sähköpostitse osoitteeseen "%s" (%s)', + 'Unable to read uploaded file.' => 'Ladattua tiedostoa ei voitu lukea.', + 'Database uploaded successfully.' => 'Tietokanta ladattiin onnistuneesti.', + 'Task sent by email successfully.' => 'Tehtävä lähetettiin sähköpostitse onnistuneesti.', + 'There is no category in this project.' => 'Projektissa ei ole luokkia.', + 'Send by email' => 'Lähetä sähköpostitse', + 'Create and send a comment by email' => 'Luo ja lähetä kommentti sähköpostitse', + 'Subject' => 'Aihe', + 'Upload the database' => 'Lataa tietokanta', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Voit ladata aiemmin ladatun Sqlite-tietokannan (Gzip-muodossa).', + 'Database file' => 'Tietokantatiedosto', + 'Upload' => 'Lataa', + 'Your project must have at least one active swimlane.' => 'Projektissasi on oltava vähintään yksi aktiivinen kaista.', + 'Project: %s' => 'Projekti: %s', + 'Automatic action not found: "%s"' => 'Automaattista toimintoa ei löytynyt: "%s"', + '%d projects' => '%d projektia', + '%d project' => '%d projekti', + 'There is no project.' => 'Projekteja ei ole.', + 'Sort' => 'Järjestys', + 'Project ID' => 'Projektin ID', + 'Project name' => 'Projektin nimi', + 'Public' => 'Julkinen', + 'Personal' => 'Henkilökohtainen', + '%d tasks' => '%d tehtävää', + '%d task' => '%d tehtävä', + 'Task ID' => 'Tehtävän ID', + 'Assign automatically a color when due date is expired' => 'Määritä automaattisesti väri, kun määräpäivä on ohitettu', + 'Total score in this column across all swimlanes' => 'Kokonaispistemäärä tässä sarakkeessa kaikilla kaistoilla', + 'HRK - Kuna' => 'HRK - Kroatian kuna', + 'ARS - Argentine Peso' => 'ARS - Argentiinan peso', + 'COP - Colombian Peso' => 'COP - Kolumbian peso', + '%d groups' => '%d ryhmää', + '%d group' => '%d ryhmä', + 'Group ID' => 'Ryhmän ID', + 'External ID' => 'Ulkoinen ID', + '%d users' => '%d käyttäjää', + '%d user' => '%d käyttäjä', + 'Hide subtasks' => 'Piilota alitehtävät', + 'Show subtasks' => 'Näytä alitehtävät', + 'Authentication Parameters' => 'Tunnistautumisparametrit', + 'API Access' => 'API-käyttöoikeus', + 'No users found.' => 'Käyttäjiä ei löytynyt.', + 'User ID' => 'Käyttäjän ID', + 'Notifications are activated' => 'Ilmoitukset ovat aktivoitu', + 'Notifications are disabled' => 'Ilmoitukset ovat poistettu käytöstä', + 'User disabled' => 'Käyttäjä poistettu käytöstä', + '%d notifications' => '%d ilmoitusta', + '%d notification' => '%d ilmoitus', + 'There is no external integration installed.' => 'Ulkoisia integraatioita ei ole asennettu.', + 'You are not allowed to update tasks assigned to someone else.' => 'Sinulla ei ole oikeutta päivittää muiden määrittämiä tehtäviä.', + 'You are not allowed to change the assignee.' => 'Sinulla ei ole oikeutta muuttaa määrättyä henkilöä.', + 'Task suppression is not permitted' => 'Tehtävien poistaminen ei ole sallittua', + 'Changing assignee is not permitted' => 'Määrätyn henkilön vaihtaminen ei ole sallittua', + 'Update only assigned tasks is permitted' => 'Vain määrättyjen tehtävien päivittäminen on sallittua', + 'Only for tasks assigned to the current user' => 'Vain tehtäviin, jotka on määritetty nykyiselle käyttäjälle', + 'My projects' => 'Omat projektini', + 'You are not a member of any project.' => 'Et ole minkään projektin jäsen.', + 'My subtasks' => 'Omat alitehtävät', + '%d subtasks' => '%d alitehtävää', + '%d subtask' => '%d alitehtävä', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Vain tehtävän siirtäminen näiden sarakkeiden välillä on sallittua nykyiselle käyttäjälle määritetyille tehtäville', + '[DUPLICATE]' => '[KOPIO]', + 'DKK - Danish Krona' => 'DKK - Tanskan kruunu', + 'Remove user from group' => 'Poista käyttäjä ryhmästä', + 'Assign the task to its creator' => 'Määritä tehtävä sen luojalle', + 'This task was sent by email to "%s" with subject "%s".' => 'Tämä tehtävä lähetettiin sähköpostitse "%s" aiheella "%s".', + 'Predefined Email Subjects' => 'Ennalta määritellyt sähköpostin aiheet', + 'Write one subject by line.' => 'Kirjoita yksi aihe riviä kohden.', + 'Create another link' => 'Luo toinen linkki', + 'BRL - Brazilian Real' => 'BRL - Brasilian real', + 'Add a new Kanboard task' => 'Lisää uusi Kanboard-tehtävä', + 'Subtask not started' => 'Alitehtävä ei aloitettu', + 'Subtask currently in progress' => 'Alitehtävä on parhaillaan kesken', + 'Subtask completed' => 'Alitehtävä valmis', + 'Subtask added successfully.' => 'Alitehtävä lisättiin onnistuneesti.', + '%d subtasks added successfully.' => '%d alitehtävää lisättiin onnistuneesti.', + 'Enter one subtask by line.' => 'Syötä yksi alitehtävä riviä kohden.', + 'Predefined Contents' => 'Ennalta määritellyt sisällöt', + 'Predefined contents' => 'Ennalta määritellyt sisällöt', + 'Predefined Task Description' => 'Ennalta määritelty tehtävän kuvaus', + 'Do you really want to remove this template? "%s"' => 'Haluatko todella poistaa tämän mallin? "%s"', + 'Add predefined task description' => 'Lisää ennalta määritelty tehtävän kuvaus', + 'Predefined Task Descriptions' => 'Ennalta määritellyt tehtävän kuvaukset', + 'Template created successfully.' => 'Malli luotiin onnistuneesti.', + 'Unable to create this template.' => 'Malliin luominen epäonnistui.', + 'Template updated successfully.' => 'Malli päivitettiin onnistuneesti.', + 'Unable to update this template.' => 'Mallin päivittäminen epäonnistui.', + 'Template removed successfully.' => 'Malli poistettiin onnistuneesti.', + 'Unable to remove this template.' => 'Mallin poistaminen epäonnistui.', + 'Template for the task description' => 'Malli tehtävän kuvaukseen', + 'The start date is greater than the end date' => 'Aloituspäivämäärä on suurempi kuin lopetuspäivämäärä', + 'Tags must be separated by a comma' => 'Tunnisteet on erotettava pilkulla', + 'Only the task title is required' => 'Vain tehtävän otsikko on pakollinen', + 'Creator Username' => 'Luojan käyttäjänimi', + 'Color Name' => 'Värin nimi', + 'Column Name' => 'Sarakkeen nimi', + 'Swimlane Name' => 'Kaistan nimi', + 'Time Estimated' => 'Arvioitu aika', + 'Time Spent' => 'Käytetty aika', + 'External Link' => 'Ulkoinen linkki', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Tämä ominaisuus mahdollistaa iCal-syötteen, RSS-syötteen ja julkisen taulunäkymän.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Pysäytä kaikkien alitehtävien ajastin, kun tehtävä siirretään toiseen sarakkeeseen', + 'Subtask Title' => 'Alitehtävän otsikko', + 'Add a subtask and activate the timer when moving a task to another column' => 'Lisää alitehtävä ja aktivoi ajastin, kun tehtävä siirretään toiseen sarakkeeseen', + 'days' => 'päivää', + 'minutes' => 'minuuttia', + 'seconds' => 'sekuntia', + 'Assign automatically a color when preset start date is reached' => 'Määritä automaattisesti väri, kun esiasetettu aloituspäivämäärä saavutetaan', + 'Move the task to another column once a predefined start date is reached' => 'Siirrä tehtävä toiseen sarakkeeseen, kun es määritelty aloituspäivämäärä on saavutettu', + 'This task is now linked to the task %s with the relation "%s"' => 'Tämä tehtävä on nyt linkitetty tehtävään %s suhteella "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'Linkki tehtävään %s suhteella "%s" on poistettu', + 'Custom Filter:' => 'Mukautettu suodatin:', + 'Unable to find this group.' => 'Ryhmää ei löytynyt.', + '%s moved the task #%d to the column "%s"' => '%s siirsi tehtävän #%d sarakkeeseen "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => '%s siirsi tehtävän #%d sarakkeeseen "%s" paikkaan %d', + '%s moved the task #%d to the swimlane "%s"' => '%s siirsi tehtävän #%d kaistaan "%s"', + '%sh spent' => '%sh käytetty', + '%sh estimated' => '%sh arvioitu', + 'Select All' => 'Valitse kaikki', + 'Unselect All' => 'Poista kaikkien valinta', + 'Apply action' => 'Suorita toiminto', + 'Move selected tasks to another column or swimlane' => 'Siirrä valitut tehtävät toiseen sarakkeeseen tai kaistaan', + 'Edit tasks in bulk' => 'Muokkaa tehtäviä joukkona', + 'Choose the properties that you would like to change for the selected tasks.' => 'Valitse ominaisuudet, jotka haluat muuttaa valituille tehtäville.', + 'Configure this project' => 'Määritä tämä projekti', + 'Start now' => 'Aloita nyt', + '%s removed a file from the task #%d' => '%s poisti tiedoston tehtävästä #%d', + 'Attachment removed from task #%d: %s' => 'Liite poistettu tehtävästä #%d: %s', + 'No color' => 'Ei väriä', + 'Attachment removed "%s"' => 'Liite poistettu "%s"', + '%s removed a file from the task %s' => '%s poisti tiedoston tehtävästä %s', + 'Move the task to another swimlane when assigned to a user' => 'Siirrä tehtävä toiseen kaistaan, kun se on määritetty käyttäjälle', + 'Destination swimlane' => 'Kohdekaista', + 'Assign a category when the task is moved to a specific swimlane' => 'Määritä luokka, kun tehtävä siirretään tiettyyn kaistaan', + 'Move the task to another swimlane when the category is changed' => 'Siirrä tehtävä toiseen kaistaan, kun luokkaa muutetaan', + 'Reorder this column by priority (ASC)' => 'Järjestä tämä sarake uudelleen prioriteetin mukaan (nouseva)', + 'Reorder this column by priority (DESC)' => 'Järjestä tämä sarake uudelleen prioriteetin mukaan (laskeva)', + 'Reorder this column by assignee and priority (ASC)' => 'Järjestä tämä sarake uudelleen määrätyn ja prioriteetin mukaan (nouseva)', + 'Reorder this column by assignee and priority (DESC)' => 'Järjestä tämä sarake uudelleen määrätyn ja prioriteetin mukaan (laskeva)', + 'Reorder this column by assignee (A-Z)' => 'Järjestä tämä sarake uudelleen määrätyn mukaan (A-Z)', + 'Reorder this column by assignee (Z-A)' => 'Järjestä tämä sarake uudelleen määrätyn mukaan (Z-A)', + 'Reorder this column by due date (ASC)' => 'Järjestä tämä sarake uudelleen määräpäivän mukaan (nouseva)', + 'Reorder this column by due date (DESC)' => 'Järjestä tämä sarake uudelleen määräpäivän mukaan (laskeva)', + 'Reorder this column by id (ASC)' => 'Järjestä tämä sarake uudelleen ID:n mukaan (nouseva)', + 'Reorder this column by id (DESC)' => 'Järjestä tämä sarake uudelleen ID:n mukaan (laskeva)', + '%s moved the task #%d "%s" to the project "%s"' => '%s siirsi tehtävän #%d "%s" projektiin "%s"', + 'Task #%d "%s" has been moved to the project "%s"' => 'Tehtävä #%d "%s" on siirretty projektiin "%s"', + 'Move the task to another column when the due date is less than a certain number of days' => 'Siirrä tehtävä toiseen sarakkeeseen, kun määräpäivään on vähemmän kuin tietty määrä päiviä', + 'Automatically update the start date when the task is moved away from a specific column' => 'Päivitä aloituspäivämäärä automaattisesti, kun tehtävä siirretään pois tietystä sarakkeesta', + 'HTTP Client:' => 'HTTP-asiakas:', + 'Assigned' => 'Määritetty', + 'Task limits apply to each swimlane individually' => 'Tehtävärajoitukset koskevat kutakin kaistaa erikseen', + 'Column task limits apply to each swimlane individually' => 'Sarakkeen tehtävärajoitukset koskevat kutakin kaistaa erikseen', + 'Column task limits are applied to each swimlane individually' => 'Sarakkeen tehtävärajoitukset koskevat kutakin kaistaa erikseen', + 'Column task limits are applied across swimlanes' => 'Sarakkeen tehtävärajoitukset koskevat kaikkia kaistoja', + 'Task limit: ' => 'Tehtävärajoitus:', + 'Change to global tag' => 'Vaihda globaaliksi tunnisteeksi', + 'Do you really want to make the tag "%s" global?' => 'Haluatko todella tehdä tunnisteesta "%s" globaalin?', + 'Enable global tags for this project' => 'Ota globaalit tunnisteet käyttöön tässä projektissa', + 'Group membership(s):' => 'Ryhmäjäsenyys (jäsenyydet):', + '%s is a member of the following group(s): %s' => '%s on jäsen seuraavissa ryhmissä: %s', + '%d/%d group(s) shown' => '%d/%d ryhmä(t) näytetty', + 'Subtask creation or modification' => 'Alitehtävän luominen tai muokkaaminen', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Määritä tehtävä tietylle käyttäjälle, kun tehtävä siirretään tiettyyn kaistaan', + 'Comment' => 'Kommentti', + 'Collapse vertically' => 'Kutista pystysuunnassa', + 'Expand vertically' => 'Laajenna pystysuunnassa', + 'MXN - Mexican Peso' => 'MXN - Meksikon peso', + 'Estimated vs actual time per column' => 'Arvioitu vs. todellinen aika saraketta kohden', + 'HUF - Hungarian Forint' => 'HUF - Unkarin forintti', + 'XBT - Bitcoin' => 'XBT - Bitcoin', + 'You must select a file to upload as your avatar!' => 'Sinun on valittava tiedosto ladattavaksi avatariksesi!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'Lataamasi tiedosto ei ole kelvollinen kuva! (Vain *.gif, *.jpg, *.jpeg ja *.png on sallittu!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Aseta automaattisesti määräpäivä, kun tehtävä siirretään pois tietystä sarakkeesta', + 'No other projects found.' => 'Muita projekteja ei löytynyt.', + 'Tasks copied successfully.' => 'Tehtävät kopioitu onnistuneesti.', + 'Unable to copy tasks.' => 'Tehtäviä ei voitu kopioida.', + 'Theme' => 'Teema', + 'Theme:' => 'Teema:', + 'Light theme' => 'Vaalea teema', + 'Dark theme' => 'Tumma teema', + 'Automatic theme - Sync with system' => 'Automaattinen teema - Synkronoi järjestelmän kanssa', + 'Application managers or more' => 'Sovelluksen hallitsijat tai enemmän', + 'Administrators' => 'Ylläpitäjät', + 'Visibility:' => 'Näkyvyys:', + 'Standard users' => 'Standardikäyttäjät', + 'Visibility is required' => 'Näkyvyys on pakollinen', + 'The visibility should be an app role' => 'Näkyvyyden tulisi olla sovellusrooli', + 'Reply' => 'Vastaa', + '%s wrote: ' => '%s kirjoitti:', + 'Number of visible tasks in this column and swimlane' => 'Tässä sarakkeessa ja kaistassa näkyvien tehtävien määrä', + 'Number of tasks in this swimlane' => 'Tehtävien määrä tässä kaistassa', + 'Unable to find another subtask in progress, you can close this window.' => 'Toista kesken olevaa alitehtävää ei löytynyt, voit sulkea tämän ikkunan.', + 'This theme is invalid' => 'Tämä teema on virheellinen', + 'This role is invalid' => 'Tämä rooli on virheellinen', + 'This timezone is invalid' => 'Tämä aikavyöhyke on virheellinen', + 'This language is invalid' => 'Tämä kieli on virheellinen', + 'This URL is invalid' => 'Tämä URL on virheellinen', + 'Date format invalid' => 'Päivämäärämuoto virheellinen', + 'Time format invalid' => 'Ajan muoto virheellinen', + 'Invalid Mail transport' => 'Virheellinen sähköpostin siirto', + 'Color invalid' => 'Väri virheellinen', + 'This value must be greater or equal to %d' => 'Tämän arvon on oltava suurempi tai yhtä suuri kuin %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Lisää BOM tiedoston alkuun (vaaditaan Microsoft Excelille)', + 'Just add these tag(s)' => 'Lisää vain nämä tunnisteet', + 'Remove internal link(s)' => 'Poista sisäinen linkki/linkit', + 'Import tasks from another project' => 'Tuo tehtäviä toisesta projektista', + 'Select the project to copy tasks from' => 'Valitse projekti, josta kopioida tehtäviä', + 'The total maximum allowed attachments size is %sB.' => 'Sallittu liitteiden kokonaiskoko on %sB.', + 'Add attachments' => 'Lisää liitteitä', + 'Task #%d "%s" is overdue' => 'Tehtävä #%d "%s" on myöhässä', + 'Enable notifications by default for all new users' => 'Ota ilmoitukset käyttöön oletuksena kaikille uusille käyttäjille', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Määritä tehtävä sen luojalle tietyissä sarakkeissa, jos vastuuhenkilöä ei ole asetettu manuaalisesti', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Määritä tehtävä kirjautuneelle käyttäjälle siirrettäessä määritettyyn sarakkeeseen, jos käyttäjää ei ole määritetty', +]; diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php new file mode 100644 index 0000000..5b289c5 --- /dev/null +++ b/app/Locale/fr_FR/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => ' ', + 'None' => 'Aucun', + 'Edit' => 'Modifier', + 'Remove' => 'Supprimer', + 'Yes' => 'Oui', + 'No' => 'Non', + 'cancel' => 'annuler', + 'or' => 'ou', + 'Yellow' => 'Jaune', + 'Blue' => 'Bleu', + 'Green' => 'Vert', + 'Purple' => 'Violet', + 'Red' => 'Rouge', + 'Orange' => 'Orange', + 'Grey' => 'Gris', + 'Brown' => 'Marron', + 'Deep Orange' => 'Orange foncé', + 'Dark Grey' => 'Gris foncé', + 'Pink' => 'Rose', + 'Teal' => 'Turquoise', + 'Cyan' => 'Bleu intense', + 'Lime' => 'Vert citron', + 'Light Green' => 'Vert clair', + 'Amber' => 'Ambre', + 'Save' => 'Enregistrer', + 'Login' => 'Connexion', + 'Official website:' => 'Site web officiel :', + 'Unassigned' => 'Non assigné', + 'View this task' => 'Voir cette tâche', + 'Remove user' => 'Supprimer un utilisateur', + 'Do you really want to remove this user: "%s"?' => 'Voulez-vous vraiment supprimer cet utilisateur : « %s » ?', + 'All users' => 'Tous les utilisateurs', + 'Username' => 'Identifiant', + 'Password' => 'Mot de passe', + 'Administrator' => 'Administrateur', + 'Sign in' => 'Connexion', + 'Users' => 'Utilisateurs', + 'Forbidden' => 'Accès interdit', + 'Access Forbidden' => 'Accès interdit', + 'Edit user' => 'Modifier un utilisateur', + 'Logout' => 'Déconnexion', + 'Bad username or password' => 'Identifiant ou mot de passe incorrect', + 'Edit project' => 'Modifier le projet', + 'Name' => 'Nom', + 'Projects' => 'Projets', + 'No project' => 'Aucun projet', + 'Project' => 'Projet', + 'Status' => 'État', + 'Tasks' => 'Tâches', + 'Board' => 'Tableau', + 'Actions' => 'Actions', + 'Inactive' => 'Inactif', + 'Active' => 'Actif', + 'Unable to update this board.' => 'Impossible de mettre à jour ce tableau.', + 'Disable' => 'Désactiver', + 'Enable' => 'Activer', + 'New project' => 'Nouveau projet', + 'Do you really want to remove this project: "%s"?' => 'Voulez-vous vraiment supprimer ce projet : « %s » ?', + 'Remove project' => 'Supprimer le projet', + 'Edit the board for "%s"' => 'Modifier le tableau pour « %s »', + 'Add a new column' => 'Ajouter une nouvelle colonne', + 'Title' => 'Titre', + 'Assigned to %s' => 'Assigné à %s', + 'Remove a column' => 'Supprimer une colonne', + 'Unable to remove this column.' => 'Impossible de supprimer cette colonne.', + 'Do you really want to remove this column: "%s"?' => 'Voulez vraiment supprimer cette colonne : « %s » ?', + 'Settings' => 'Préférences', + 'Application settings' => 'Paramètres de l\'application', + 'Language' => 'Langue', + 'Webhook token:' => 'Jeton de sécurité pour les webhooks :', + 'API token:' => 'Jeton de sécurité pour l\'API :', + 'Database size:' => 'Taille de la base de données :', + 'Download the database' => 'Télécharger la base de données', + 'Optimize the database' => 'Optimiser la base de données', + '(VACUUM command)' => '(Commande VACUUM)', + '(Gzip compressed Sqlite file)' => '(Fichier SQLite compressé en Gzip)', + 'Close a task' => 'Fermer une tâche', + 'Column' => 'Colonne', + 'Color' => 'Couleur', + 'Assignee' => 'Personne assignée', + 'Create another task' => 'Créer une autre tâche', + 'New task' => 'Nouvelle tâche', + 'Open a task' => 'Ouvrir une tâche', + 'Do you really want to open this task: "%s"?' => 'Voulez-vous vraiment ouvrir cette tâche : « %s » ?', + 'Back to the board' => 'Retour au tableau', + 'There is nobody assigned' => 'Il n\'y a personne d\'assigné à cette tâche', + 'Column on the board:' => 'Colonne sur le tableau : ', + 'Close this task' => 'Fermer cette tâche', + 'Open this task' => 'Ouvrir cette tâche', + 'There is no description.' => 'Il n\'y a pas de description.', + 'Add a new task' => 'Ajouter une nouvelle tâche', + 'The username is required' => 'Le nom d\'utilisateur est obligatoire', + 'The maximum length is %d characters' => 'La longueur maximale est de %d caractères', + 'The minimum length is %d characters' => 'La longueur minimale est de %d caractères', + 'The password is required' => 'Le mot de passe est obligatoire', + 'This value must be an integer' => 'Cette valeur doit être un entier', + 'The username must be unique' => 'Le nom d\'utilisateur doit être unique', + 'The user id is required' => 'L\'id de l\'utilisateur est obligatoire', + 'Passwords don\'t match' => 'Les mots de passe ne correspondent pas', + 'The confirmation is required' => 'La confirmation est requise', + 'The project is required' => 'Le projet est obligatoire', + 'The id is required' => 'L\'identifiant est obligatoire', + 'The project id is required' => 'L\'identifiant du projet est obligatoire', + 'The project name is required' => 'Le nom du projet est obligatoire', + 'The title is required' => 'Le titre est obligatoire', + 'Settings saved successfully.' => 'Paramètres sauvegardés avec succès.', + 'Unable to save your settings.' => 'Impossible de sauvegarder vos réglages.', + 'Database optimization done.' => 'Optimisation de la base de données terminée.', + 'Your project has been created successfully.' => 'Votre projet a été créé avec succès.', + 'Unable to create your project.' => 'Impossible de créer un projet.', + 'Project updated successfully.' => 'Votre projet a été mis à jour avec succès.', + 'Unable to update this project.' => 'Impossible de mettre à jour ce projet.', + 'Unable to remove this project.' => 'Impossible de supprimer ce projet.', + 'Project removed successfully.' => 'Votre projet a été supprimé avec succès.', + 'Project activated successfully.' => 'Votre projet a été activé avec succès.', + 'Unable to activate this project.' => 'Impossible d\'activer ce projet.', + 'Project disabled successfully.' => 'Votre projet a été désactivé avec succès.', + 'Unable to disable this project.' => 'Impossible de désactiver ce projet.', + 'Unable to open this task.' => 'Impossible d\'ouvrir cette tâche.', + 'Task opened successfully.' => 'Tâche ouverte avec succès.', + 'Unable to close this task.' => 'Impossible de fermer cette tâche.', + 'Task closed successfully.' => 'Tâche fermée avec succès.', + 'Unable to update your task.' => 'Impossible de modifier cette tâche.', + 'Task updated successfully.' => 'Tâche mise à jour avec succès.', + 'Unable to create your task.' => 'Impossible de créer cette tâche.', + 'Task created successfully.' => 'Tâche créée avec succès.', + 'User created successfully.' => 'Utilisateur créé avec succès.', + 'Unable to create your user.' => 'Impossible de créer cet utilisateur.', + 'User updated successfully.' => 'Utilisateur mis à jour avec succès.', + 'User removed successfully.' => 'Utilisateur supprimé avec succès.', + 'Unable to remove this user.' => 'Impossible de supprimer cet utilisateur.', + 'Board updated successfully.' => 'Tableau mis à jour avec succès.', + 'Ready' => 'Prêt', + 'Backlog' => 'En attente', + 'Work in progress' => 'En cours', + 'Done' => 'Terminé', + 'Application version:' => 'Version de l\'application :', + 'Id' => 'Id.', + 'Public link' => 'Lien public', + 'Timezone' => 'Fuseau horaire', + 'Sorry, I didn\'t find this information in my database!' => 'Désolé, je n\'ai pas trouvé cette information dans ma base de données !', + 'Page not found' => 'Page introuvable', + 'Complexity' => 'Complexité', + 'Task limit' => 'Tâches Max.', + 'Task count' => 'Nombre de tâches', + 'User' => 'Utilisateur', + 'Comments' => 'Commentaires', + 'Comment is required' => 'Le commentaire est obligatoire', + 'Comment added successfully.' => 'Commentaire ajouté avec succès.', + 'Unable to create your comment.' => 'Impossible de sauvegarder votre commentaire.', + 'Due Date' => 'Date d\'échéance', + 'Invalid date' => 'Date invalide', + 'Automatic actions' => 'Actions automatisées', + 'Your automatic action has been created successfully.' => 'Votre action automatisée a été ajoutée avec succès.', + 'Unable to create your automatic action.' => 'Impossible de créer votre action automatisée.', + 'Remove an action' => 'Supprimer une action', + 'Unable to remove this action.' => 'Impossible de supprimer cette action', + 'Action removed successfully.' => 'Action supprimée avec succès.', + 'Automatic actions for the project "%s"' => 'Actions automatisées pour le projet « %s »', + 'Add an action' => 'Ajouter une action', + 'Event name' => 'Nom de l\'évènement', + 'Action' => 'Action', + 'Event' => 'Évènement', + 'When the selected event occurs execute the corresponding action.' => 'Lorsque l\'évènement sélectionné se déclenche, exécuter l\'action correspondante.', + 'Next step' => 'Étape suivante', + 'Define action parameters' => 'Définition des paramètres de l\'action', + 'Do you really want to remove this action: "%s"?' => 'Voulez-vous vraiment supprimer cette action « %s » ?', + 'Remove an automatic action' => 'Supprimer une action automatisée', + 'Assign the task to a specific user' => 'Assigner la tâche à un utilisateur spécifique', + 'Assign the task to the person who does the action' => 'Assigner la tâche à la personne qui fait l\'action', + 'Duplicate the task to another project' => 'Dupliquer la tâche vers un autre projet', + 'Move a task to another column' => 'Déplacement d\'une tâche vers une autre colonne', + 'Task modification' => 'Modification d\'une tâche', + 'Task creation' => 'Création d\'une tâche', + 'Closing a task' => 'Fermeture d\'une tâche', + 'Assign a color to a specific user' => 'Assigner une couleur à un utilisateur', + 'Position' => 'Position', + 'Duplicate to project' => 'Dupliquer dans un autre projet', + 'Duplicate' => 'Dupliquer', + 'Link' => 'Lien', + 'Comment updated successfully.' => 'Commentaire mis à jour avec succès.', + 'Unable to update your comment.' => 'Impossible de supprimer votre commentaire.', + 'Remove a comment' => 'Supprimer un commentaire', + 'Comment removed successfully.' => 'Commentaire supprimé avec succès.', + 'Unable to remove this comment.' => 'Impossible de supprimer ce commentaire.', + 'Do you really want to remove this comment?' => 'Voulez-vous vraiment supprimer ce commentaire ?', + 'Current password for the user "%s"' => 'Mot de passe actuel pour l\'utilisateur « %s »', + 'The current password is required' => 'Le mot de passe actuel est obligatoire', + 'Wrong password' => 'Mot de passe invalide', + 'Unknown' => 'Inconnu', + 'Last logins' => 'Dernières connexions', + 'Login date' => 'Date de connexion', + 'Authentication method' => 'Méthode d\'authentification', + 'IP address' => 'Adresse IP', + 'User agent' => 'Agent utilisateur', + 'Persistent connections' => 'Connexions persistantes', + 'No session.' => 'Aucune session.', + 'Expiration date' => 'Date d\'expiration', + 'Remember Me' => 'Connexion automatique', + 'Creation date' => 'Date de création', + 'Everybody' => 'Tout le monde', + 'Open' => 'Ouvert', + 'Closed' => 'Fermé', + 'Search' => 'Rechercher', + 'Nothing found.' => 'Rien trouvé.', + 'Due date' => 'Date d\'échéance', + 'Description' => 'Description', + '%d comments' => '%d commentaires', + '%d comment' => '%d commentaire', + 'Email address invalid' => 'Adresse email invalide', + 'Your external account is not linked anymore to your profile.' => 'Votre compte externe n\'est plus relié à votre profil.', + 'Unable to unlink your external account.' => 'Impossible de supprimer votre compte externe.', + 'External authentication failed' => 'L’authentification externe a échoué', + 'Your external account is linked to your profile successfully.' => 'Votre compte externe est désormais lié à votre profil.', + 'Email' => 'Email', + 'Task removed successfully.' => 'Tâche supprimée avec succès.', + 'Unable to remove this task.' => 'Impossible de supprimer cette tâche.', + 'Remove a task' => 'Supprimer une tâche', + 'Do you really want to remove this task: "%s"?' => 'Voulez-vous vraiment supprimer cette tâche « %s » ?', + 'Assign automatically a color based on a category' => 'Assigner automatiquement une couleur par rapport à une catégorie définie', + 'Assign automatically a category based on a color' => 'Assigner automatiquement une catégorie par rapport à une couleur définie', + 'Task creation or modification' => 'Création ou modification d\'une tâche', + 'Category' => 'Catégorie', + 'Category:' => 'Catégorie :', + 'Categories' => 'Catégories', + 'Your category has been created successfully.' => 'Votre catégorie a été créée avec succès.', + 'This category has been updated successfully.' => 'Cette catégorie a été mise à jour avec succès.', + 'Unable to update this category.' => 'Impossible de mettre à jour cette catégorie.', + 'Remove a category' => 'Supprimer une catégorie', + 'Category removed successfully.' => 'Catégorie supprimée avec succès.', + 'Unable to remove this category.' => 'Impossible de supprimer cette catégorie.', + 'Category modification for the project "%s"' => 'Modification d\'une catégorie pour le projet « %s »', + 'Category Name' => 'Nom de la catégorie', + 'Add a new category' => 'Ajouter une nouvelle catégorie', + 'Do you really want to remove this category: "%s"?' => 'Voulez-vous vraiment supprimer cette catégorie « %s » ?', + 'All categories' => 'Toutes les catégories', + 'No category' => 'Aucune catégorie', + 'The name is required' => 'Le nom est requis', + 'Remove a file' => 'Supprimer un fichier', + 'Unable to remove this file.' => 'Impossible de supprimer ce fichier.', + 'File removed successfully.' => 'Fichier supprimé avec succès.', + 'Attach a document' => 'Joindre un document', + 'Do you really want to remove this file: "%s"?' => 'Voulez-vous vraiment supprimer ce fichier « %s » ?', + 'Attachments' => 'Pièces-jointes', + 'Edit the task' => 'Modifier la tâche', + 'Add a comment' => 'Ajouter un commentaire', + 'Edit a comment' => 'Modifier un commentaire', + 'Summary' => 'Résumé', + 'Time tracking' => 'Suivi du temps', + 'Estimate:' => 'Estimation :', + 'Spent:' => 'Passé :', + 'Do you really want to remove this sub-task?' => 'Voulez-vous vraiment supprimer cette sous-tâche ?', + 'Remaining:' => 'Restant :', + 'hours' => 'heures', + 'estimated' => 'estimé', + 'Sub-Tasks' => 'Sous-tâches', + 'Add a sub-task' => 'Ajouter une sous-tâche', + 'Original estimate' => 'Estimation originale', + 'Create another sub-task' => 'Créer une autre sous-tâche', + 'Time spent' => 'Temps passé', + 'Edit a sub-task' => 'Modifier une sous-tâche', + 'Remove a sub-task' => 'Supprimer une sous-tâche', + 'The time must be a numeric value' => 'Le temps doit-être une valeur numérique', + 'Todo' => 'À faire', + 'In progress' => 'En cours', + 'Sub-task removed successfully.' => 'Sous-tâche supprimée avec succès.', + 'Unable to remove this sub-task.' => 'Impossible de supprimer cette sous-tâche.', + 'Sub-task updated successfully.' => 'Sous-tâche mise à jour avec succès.', + 'Unable to update your sub-task.' => 'Impossible de mettre à jour votre sous-tâche.', + 'Unable to create your sub-task.' => 'Impossible de créer votre sous-tâche.', + 'Maximum size: ' => 'Taille maximum : ', + 'Display another project' => 'Afficher un autre projet', + 'Created by %s' => 'Créé par %s', + 'Tasks Export' => 'Exportation des tâches', + 'Start Date' => 'Date de début', + 'Execute' => 'Exécuter', + 'Task Id' => 'Identifiant de la tâche', + 'Creator' => 'Créateur', + 'Modification date' => 'Date de modification', + 'Completion date' => 'Date de complétion', + 'Clone' => 'Clone', + 'Project cloned successfully.' => 'Projet cloné avec succès.', + 'Unable to clone this project.' => 'Impossible de cloner ce projet.', + 'Enable email notifications' => 'Activer les notifications par email', + 'Task position:' => 'Position de la tâche :', + 'The task #%d has been opened.' => 'La tâche #%d a été ouverte.', + 'The task #%d has been closed.' => 'La tâche #%d a été fermée.', + 'Sub-task updated' => 'Sous-tâche mise à jour', + 'Title:' => 'Titre :', + 'Status:' => 'État :', + 'Assignee:' => 'Assigné :', + 'Time tracking:' => 'Gestion du temps :', + 'New sub-task' => 'Nouvelle sous-tâche', + 'New attachment added "%s"' => 'Nouvelle pièce-jointe ajoutée « %s »', + 'New comment posted by %s' => 'Nouveau commentaire ajouté par « %s »', + 'New comment' => 'Nouveau commentaire', + 'Comment updated' => 'Commentaire mis à jour', + 'New subtask' => 'Nouvelle sous-tâche', + 'I only want to receive notifications for these projects:' => 'Je souhaite recevoir les notifications uniquement pour les projets sélectionnés :', + 'view the task on Kanboard' => 'voir la tâche sur Kanboard', + 'Public access' => 'Accès public', + 'Disable public access' => 'Désactiver l\'accès public', + 'Enable public access' => 'Activer l\'accès public', + 'Public access disabled' => 'Accès public désactivé', + 'Move the task to another project' => 'Déplacer la tâche vers un autre projet', + 'Move to project' => 'Déplacer vers un autre projet', + 'Do you really want to duplicate this task?' => 'Voulez-vous vraiment dupliquer cette tâche ?', + 'Duplicate a task' => 'Dupliquer une tâche', + 'External accounts' => 'Comptes externes', + 'Account type' => 'Type de compte', + 'Local' => 'Local', + 'Remote' => 'Distant', + 'Enabled' => 'Activé', + 'Disabled' => 'Désactivé', + 'Login:' => 'Nom d\'utilisateur :', + 'Full Name:' => 'Nom :', + 'Email:' => 'Email :', + 'Notifications:' => 'Notifications :', + 'Notifications' => 'Notifications', + 'Account type:' => 'Type de compte :', + 'Edit profile' => 'Modifier le profil', + 'Change password' => 'Changer le mot de passe', + 'Password modification' => 'Changement de mot de passe', + 'External authentications' => 'Authentifications externes', + 'Never connected.' => 'Jamais connecté.', + 'No external authentication enabled.' => 'Aucune authentification externe activée.', + 'Password modified successfully.' => 'Mot de passe changé avec succès.', + 'Unable to change the password.' => 'Impossible de changer le mot de passe.', + 'Change category' => 'Changer de catégorie', + '%s updated the task %s' => '%s a mis à jour la tâche %s', + '%s opened the task %s' => '%s a ouvert la tâche %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s a déplacé la tâche %s à la position n°%d dans la colonne « %s »', + '%s moved the task %s to the column "%s"' => '%s a déplacé la tâche %s dans la colonne « %s »', + '%s created the task %s' => '%s a créé la tâche %s', + '%s closed the task %s' => '%s a fermé la tâche %s', + '%s created a subtask for the task %s' => '%s a créé une sous-tâche pour la tâche %s', + '%s updated a subtask for the task %s' => '%s a mis à jour une sous-tâche appartenant à la tâche %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Assigné à %s avec un estimé de %s/%s h', + 'Not assigned, estimate of %sh' => 'Personne assigné, estimé de %s h', + '%s updated a comment on the task %s' => '%s a mis à jour un commentaire appartenant à la tâche %s', + '%s commented the task %s' => '%s a ajouté un commentaire sur la tâche %s', + '%s\'s activity' => 'Activité du projet %s', + 'RSS feed' => 'Flux RSS', + '%s updated a comment on the task #%d' => '%s a mis à jour un commentaire sur la tâche n°%d', + '%s commented on the task #%d' => '%s a ajouté un commentaire sur la tâche n°%d', + '%s updated a subtask for the task #%d' => '%s a mis à jour une sous-tâche appartenant à la tâche n°%d', + '%s created a subtask for the task #%d' => '%s a créé une sous-tâche pour la tâche n°%d', + '%s updated the task #%d' => '%s a mis à jour la tâche n°%d', + '%s created the task #%d' => '%s a créé la tâche n°%d', + '%s closed the task #%d' => '%s a fermé la tâche n°%d', + '%s opened the task #%d' => '%s a ouvert la tâche n°%d', + 'Activity' => 'Activité', + 'Default values are "%s"' => 'Les valeurs par défaut sont « %s »', + 'Default columns for new projects (Comma-separated)' => 'Colonnes par défaut pour les nouveaux projets (séparation par des virgules)', + 'Task assignee change' => 'Modification de la personne assignée à une tâche', + '%s changed the assignee of the task #%d to %s' => '%s a changé la personne assignée à la tâche n˚%d pour %s', + '%s changed the assignee of the task %s to %s' => '%s a changé la personne assignée à la tâche %s pour %s', + 'New password for the user "%s"' => 'Nouveau mot de passe pour l\'utilisateur « %s »', + 'Choose an event' => 'Choisir un évènement', + 'Create a task from an external provider' => 'Créer une tâche depuis un fournisseur externe', + 'Change the assignee based on an external username' => 'Changer l\'assigné en fonction d\'un utilisateur externe', + 'Change the category based on an external label' => 'Changer la catégorie en fonction d\'un libellé externe', + 'Reference' => 'Référence', + 'Label' => 'Libellé', + 'Database' => 'Base de données', + 'About' => 'À propos', + 'Database driver:' => 'Type de base de données :', + 'Board settings' => 'Paramètres du tableau', + 'Webhook settings' => 'Paramètres pour les webhooks', + 'Reset token' => 'Regénérer le jeton de sécurité', + 'API endpoint:' => 'URL de l\'API :', + 'Refresh interval for personal board' => 'Intervalle pour rafraichir un tableau personnel', + 'Refresh interval for public board' => 'Intervalle pour rafraichir un tableau public', + 'Task highlight period' => 'Durée pour mettre une tâche en évidence', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Durée en seconde pour considérer une tâche comme récemment modifiée (0 pour désactiver, 2 jours par défaut)', + 'Frequency in second (60 seconds by default)' => 'Fréquence en seconde (60 secondes par défaut)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Fréquence en seconde (0 pour désactiver, 10 secondes par défaut)', + 'Application URL' => 'URL de l\'application', + 'Token regenerated.' => 'Jeton de sécurité regénéré.', + 'Date format' => 'Format des dates', + 'ISO format is always accepted, example: "%s" and "%s"' => 'Le format ISO est toujours accepté, exemple : « %s » et « %s »', + 'New personal project' => 'Nouveau projet personnel', + 'This project is personal' => 'Ce projet est personnel', + 'Add' => 'Ajouter', + 'Start date' => 'Date de début', + 'Time estimated' => 'Temps estimé', + 'There is nothing assigned to you.' => 'Il n\'y a rien d\'assigné pour vous.', + 'My tasks' => 'Mes tâches', + 'Activity stream' => 'Flux d\'activité', + 'Dashboard' => 'Tableau de bord', + 'Confirmation' => 'Confirmation', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Create a comment from an external provider' => 'Créer un commentaire depuis un fournisseur externe', + 'Project management' => 'Gestion des projets', + 'Columns' => 'Colonnes', + 'Task' => 'Tâche', + 'Percentage' => 'Pourcentage', + 'Number of tasks' => 'Nombre de tâches', + 'Task distribution' => 'Répartition des tâches', + 'Analytics' => 'Analytique', + 'Subtask' => 'Sous-tâche', + 'User repartition' => 'Répartition des utilisateurs', + 'Clone this project' => 'Cloner ce projet', + 'Column removed successfully.' => 'Colonne supprimée avec succès.', + 'Not enough data to show the graph.' => 'Pas assez de données pour afficher le graphique.', + 'Previous' => 'Précédent', + 'The id must be an integer' => 'L\'id doit être un entier', + 'The project id must be an integer' => 'L\'id du projet doit être un entier', + 'The status must be an integer' => 'Le statut doit être un entier', + 'The subtask id is required' => 'L\'id de la sous-tâche est obligatoire', + 'The subtask id must be an integer' => 'L\'id de la sous-tâche doit être un entier', + 'The task id is required' => 'L\'id de la tâche est obligatoire', + 'The task id must be an integer' => 'L\'id de la tâche doit être un entier', + 'The user id must be an integer' => 'L\'id de l\'utilisateur doit être un entier', + 'This value is required' => 'Cette valeur est obligatoire', + 'This value must be numeric' => 'Cette valeur doit être numérique', + 'Unable to create this task.' => 'Impossible de créer cette tâche', + 'Cumulative flow diagram' => 'Diagramme de flux cumulé', + 'Daily project summary' => 'Résumé journalier du projet', + 'Daily project summary export' => 'Export du résumé journalier du projet', + 'Exports' => 'Exports', + 'This export contains the number of tasks per column grouped per day.' => 'Cet export contient le nombre de tâches par colonne groupé par jour.', + 'Active swimlanes' => 'Swimlanes actives', + 'Add a new swimlane' => 'Ajouter une nouvelle swimlane', + 'Default swimlane' => 'Swimlane par défaut', + 'Do you really want to remove this swimlane: "%s"?' => 'Voulez-vous vraiment supprimer cette swimlane : « %s » ?', + 'Inactive swimlanes' => 'Swimlanes inactives', + 'Remove a swimlane' => 'Supprimer une swimlane', + 'Swimlane modification for the project "%s"' => 'Modification d\'une swimlane pour le projet « %s »', + 'Swimlane removed successfully.' => 'Swimlane supprimée avec succès.', + 'Swimlanes' => 'Swimlanes', + 'Swimlane updated successfully.' => 'Swimlane mise à jour avec succès.', + 'Unable to remove this swimlane.' => 'Impossible de supprimer cette swimlane.', + 'Unable to update this swimlane.' => 'Impossible de mettre à jour cette swimlane.', + 'Your swimlane has been created successfully.' => 'Votre swimlane a été créée avec succès.', + 'Example: "Bug, Feature Request, Improvement"' => 'Exemple : « Incident, Demande de fonctionnalité, Amélioration »', + 'Default categories for new projects (Comma-separated)' => 'Catégories par défaut pour les nouveaux projets (séparation par des virgules)', + 'Integrations' => 'Intégrations', + 'Integration with third-party services' => 'Intégration avec des services externes', + 'Subtask Id' => 'Identifiant de la sous-tâche', + 'Subtasks' => 'Sous-tâches', + 'Subtasks Export' => 'Exportation des sous-tâches', + 'Task Title' => 'Titre de la tâche', + 'Untitled' => 'Sans nom', + 'Application default' => 'Valeur par défaut de l\'application', + 'Language:' => 'Langue :', + 'Timezone:' => 'Fuseau horaire :', + 'All columns' => 'Toutes les colonnes', + 'Next' => 'Suivant', + '#%d' => 'n˚%d', + 'All swimlanes' => 'Toutes les swimlanes', + 'All colors' => 'Toutes les couleurs', + 'Moved to column %s' => 'Tâche déplacée à la colonne %s', + 'User dashboard' => 'Tableau de bord de l\'utilisateur', + 'Allow only one subtask in progress at the same time for a user' => 'Autoriser une seule sous-tâche en progrès en même temps pour un utilisateur', + 'Edit column "%s"' => 'Modifier la colonne « %s »', + 'Select the new status of the subtask: "%s"' => 'Sélectionnez le nouveau statut de la sous-tâche : « %s »', + 'Subtask timesheet' => 'Feuille de temps des sous-tâches', + 'There is nothing to show.' => 'Il n\'y a rien à montrer.', + 'Time Tracking' => 'Feuille de temps', + 'You already have one subtask in progress' => 'Vous avez déjà une sous-tâche en progrès', + 'Which parts of the project do you want to duplicate?' => 'Quelles parties du projet voulez-vous dupliquer ?', + 'Disallow login form' => 'Interdire le formulaire d\'authentification', + 'Start' => 'Début', + 'End' => 'Fin', + 'Task age in days' => 'Âge de la tâche en jours', + 'Days in this column' => 'Jours dans cette colonne', + '%dd' => '%d j', + 'Add a new link' => 'Ajouter un nouveau lien', + 'Do you really want to remove this link: "%s"?' => 'Voulez-vous vraiment supprimer ce lien : « %s » ?', + 'Do you really want to remove this link with task #%d?' => 'Voulez-vous vraiment supprimer ce lien avec la tâche n°%d ?', + 'Field required' => 'Champ obligatoire', + 'Link added successfully.' => 'Lien créé avec succès.', + 'Link updated successfully.' => 'Lien mis à jour avec succès.', + 'Link removed successfully.' => 'Lien supprimé avec succès.', + 'Link labels' => 'Libellé des liens', + 'Link modification' => 'Modification d\'un lien', + 'Opposite label' => 'Nom du libellé opposé', + 'Remove a link' => 'Supprimer un lien', + 'The labels must be different' => 'Les libellés doivent être différents', + 'There is no link.' => 'Il n\'y a aucun lien.', + 'This label must be unique' => 'Ce libellé doit être unique', + 'Unable to create your link.' => 'Impossible d\'ajouter ce lien.', + 'Unable to update your link.' => 'Impossible de mettre à jour ce lien.', + 'Unable to remove this link.' => 'Impossible de supprimer ce lien.', + 'relates to' => 'est liée à', + 'blocks' => 'bloque', + 'is blocked by' => 'est bloquée par', + 'duplicates' => 'duplique', + 'is duplicated by' => 'est dupliquée par', + 'is a child of' => 'est un enfant de', + 'is a parent of' => 'est un parent de', + 'targets milestone' => 'vise l\'étape importante', + 'is a milestone of' => 'est une étape importante incluant', + 'fixes' => 'corrige', + 'is fixed by' => 'est corrigée par', + 'This task' => 'Cette tâche', + '<1h' => '< 1 h', + '%dh' => '%d h', + 'Expand tasks' => 'Déplier les tâches', + 'Collapse tasks' => 'Replier les tâches', + 'Expand/collapse tasks' => 'Plier/déplier les tâches', + 'Close dialog box' => 'Fermer une boite de dialogue', + 'Submit a form' => 'Enregistrer un formulaire', + 'Board view' => 'Page du tableau', + 'Keyboard shortcuts' => 'Raccourcis clavier', + 'Open board switcher' => 'Ouvrir le sélecteur de tableau', + 'Application' => 'Application', + 'Compact view' => 'Vue compacte', + 'Horizontal scrolling' => 'Défilement horizontal', + 'Compact/wide view' => 'Basculer entre la vue compacte et étendue', + 'Currency' => 'Devise', + 'Personal project' => 'Projet personnel', + 'AUD - Australian Dollar' => 'AUD - Dollar australien', + 'CAD - Canadian Dollar' => 'CAD - Dollar canadien', + 'CHF - Swiss Francs' => 'CHF - Franc suisse', + 'Custom Stylesheet' => 'Feuille de style personnalisée', + 'EUR - Euro' => 'EUR - Euro', + 'GBP - British Pound' => 'GBP - Livre sterling', + 'INR - Indian Rupee' => 'INR - Roupie indienne', + 'JPY - Japanese Yen' => 'JPY - Yen', + 'NZD - New Zealand Dollar' => 'NZD - Dollar néo-zélandais', + 'PEN - Peruvian Sol' => 'PEN - Sol péruvien', + 'RSD - Serbian dinar' => 'RSD - Dinar serbe', + 'CNY - Chinese Yuan' => 'CNY - Yuan (Chine)', + 'USD - US Dollar' => 'USD - Dollar américain', + 'VES - Venezuelan Bolívar' => 'Bolivar vénézuélien', + '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', + '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', + 'Reference currency' => 'Devise de référence', + 'The currency rate has 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', + 'Webhook URL' => 'URL du webhook', + '%s removed the assignee of the task %s' => '%s a enlevé la personne assignée à la tâche %s', + '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', + 'This QR code contains the key URI: ' => 'Ce code QR contient l\'URL de la clé : ', + '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', + '%s via Kanboard' => '%s via Kanboard', + 'Burndown chart' => 'Graphique d\'avancement', + 'This chart show the task complexity over the time (Work Remaining).' => 'Ce graphique représente la complexité des tâches en fonction du temps (travail restant).', + 'Screenshot taken %s' => 'Capture d\'écran prise le %s', + 'Add a screenshot' => 'Ajouter une capture d\'écran', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Prenez une capture d\'écran et appuyez sur CTRL+V ou ⌘+V pour coller ici.', + 'Screenshot uploaded successfully.' => 'Capture d\'écran téléchargée avec succès.', + 'SEK - Swedish Krona' => 'SEK - Couronne suédoise', + 'Identifier' => 'Identificateur', + 'Disable two factor authentication' => 'Désactiver l\'authentification à deux facteurs', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Voulez-vous vraiment désactiver l\'authentification à deux facteurs pour cet utilisateur : « %s » ?', + 'Edit link' => 'Modifier un lien', + 'Start to type task title...' => 'Entrez le titre de la tâche…', + 'A task cannot be linked to itself' => 'Une tâche ne peut être liée à elle-même', + 'The exact same link already exists' => 'Un lien identique existe déjà', + 'Recurrent task is scheduled to be generated' => 'La tâche récurrente est programmée pour être créée', + 'Score' => 'Complexité', + 'The identifier must be unique' => 'L\'identifiant doit être unique', + 'This linked task id doesn\'t exists' => 'L\'identifiant de la tâche liée n\'existe pas', + 'This value must be alphanumeric' => 'Cette valeur doit être alphanumérique', + 'Edit recurrence' => 'Modifier la récurrence', + 'Generate recurrent task' => 'Générer une tâche récurrente', + 'Trigger to generate recurrent task' => 'Déclencheur pour générer la tâche récurrente', + 'Factor to calculate new due date' => 'Facteur pour calculer la nouvelle date d\'échéance', + 'Timeframe to calculate new due date' => 'Échelle de temps pour calculer la nouvelle date d\'échéance', + 'Base date to calculate new due date' => 'Date à utiliser pour calculer la nouvelle date d\'échéance', + 'Action date' => 'Date de l\'action', + 'Base date to calculate new due date: ' => 'Date utilisée pour calculer la nouvelle date d\'échéance : ', + 'This task has created this child task: ' => 'Cette tâche a créé la tâche enfant : ', + 'Day(s)' => 'Jour(s)', + 'Existing due date' => 'Date d\'échéance existante', + 'Factor to calculate new due date: ' => 'Facteur pour calculer la nouvelle date d\'échéance : ', + 'Month(s)' => 'Mois', + 'This task has been created by: ' => 'Cette tâche a été créée par :', + 'Recurrent task has been generated:' => 'Une tâche récurrente a été générée :', + 'Timeframe to calculate new due date: ' => 'Échelle de temps pour calculer la nouvelle date d\'échéance : ', + 'Trigger to generate recurrent task: ' => 'Déclencheur pour générer la tâche récurrente : ', + 'When task is closed' => 'Lorsque la tâche est fermée', + 'When task is moved from first column' => 'Lorsque la tâche est déplacée en dehors de la première colonne', + 'When task is moved to last column' => 'Lorsque la tâche est déplacée dans la dernière colonne', + 'Year(s)' => 'Année(s)', + 'Project settings' => 'Paramètres du projet', + 'Automatically update the start date' => 'Mettre à jour automatiquement la date de début', + 'iCal feed' => 'Abonnement iCal', + 'Preferences' => 'Préférences', + 'Security' => 'Sécurité', + 'Two factor authentication disabled' => 'Authentification à deux facteurs désactivée', + 'Two factor authentication enabled' => 'Authentification à deux facteurs activée', + 'Unable to update this user.' => 'Impossible de mettre à jour cet utilisateur.', + 'There is no user management for personal projects.' => 'Il n\'y a pas de gestion d\'utilisateurs pour les projets personnels.', + 'User that will receive the email' => 'Utilisateur qui va recevoir l\'email', + 'Email subject' => 'Sujet de l\'email', + 'Date' => 'Date', + 'Add a comment log when moving the task between columns' => 'Ajouter un commentaire d\'information lorsque une tâche est déplacée dans une autre colonne', + 'Move the task to another column when the category is changed' => 'Déplacer une tâche vers une autre colonne lorsque la catégorie a changé', + 'Send a task by email to someone' => 'Envoyer une tâche par email à quelqu\'un', + 'Reopen a task' => 'Rouvrir une tâche', + 'Notification' => 'Notification', + '%s moved the task #%d to the first swimlane' => '%s a déplacé la tâche n°%d dans la première swimlane', + 'Swimlane' => 'Swimlane', + '%s moved the task %s to the first swimlane' => '%s a déplacé la tâche %s dans la première swimlane', + '%s moved the task %s to the swimlane "%s"' => '%s a déplacé la tâche %s dans la swimlane « %s »', + 'This report contains all subtasks information for the given date range.' => 'Ce rapport contient les informations de toutes les sous-tâches pour la période sélectionnée.', + 'This report contains all tasks information for the given date range.' => 'Ce rapport contient les informations de toutes les tâches pour la période sélectionnée.', + 'Project activities for %s' => 'Activité des projets pour « %s »', + 'view the board on Kanboard' => 'voir le tableau sur Kanboard', + 'The task has been moved to the first swimlane' => 'La tâche a été déplacée dans la première swimlane', + 'The task has been moved to another swimlane:' => 'La tâche a été déplacée dans une autre swimlane :', + 'New title: %s' => 'Nouveau titre : %s', + 'The task is not assigned anymore' => 'La tâche n\'est plus assignée maintenant', + 'New assignee: %s' => 'Nouvel assigné : %s', + 'There is no category now' => 'Il n\'y a plus de catégorie maintenant', + 'New category: %s' => 'Nouvelle catégorie : %s', + 'New color: %s' => 'Nouvelle couleur : %s', + 'New complexity: %d' => 'Nouvelle complexité : %d', + 'The due date has been removed' => 'La date d\'échéance a été enlevée', + 'There is no description anymore' => 'Il n\'y a plus de description maintenant', + 'Recurrence settings has been modified' => 'Les réglages de la récurrence ont été modifiés', + 'Time spent changed: %sh' => 'Le temps passé a été changé : %s h', + 'Time estimated changed: %sh' => 'Le temps estimé a été changé : %s h', + 'The field "%s" has been updated' => 'Le champ « %s » a été mis à jour', + 'The description has been modified:' => 'La description a été modifiée', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Voulez-vous vraiment fermer la tâche « %s » ainsi que toutes ses sous-tâches ?', + 'I want to receive notifications for:' => 'Je veux recevoir les notifications pour :', + 'All tasks' => 'Toutes les Tâches', + 'Only for tasks assigned to me' => 'Seulement les tâches qui me sont assignées', + 'Only for tasks created by me' => 'Seulement les tâches que j\'ai créées', + 'Only for tasks created by me and tasks assigned to me' => 'Seulement les tâches créées par moi-même et celles qui me sont assignées', + '%%Y-%%m-%%d' => '%%d/%%m/%%Y', + 'Total for all columns' => 'Total pour toutes les colonnes', + 'You need at least 2 days of data to show the chart.' => 'Vous avez besoin d\'au minimum 2 jours de données pour afficher le graphique.', + '<15m' => '< 15 min', + '<30m' => '< 30 min', + 'Stop timer' => 'Stopper le chrono', + 'Start timer' => 'Démarrer le chrono', + 'My activity stream' => 'Mon flux d\'activité', + 'Search tasks' => 'Rechercher des tâches', + 'Reset filters' => 'Réinitialiser les filtres', + 'My tasks due tomorrow' => 'Mes tâches qui arrivent à échéance demain', + 'Tasks due today' => 'Tâches qui arrivent à échéance aujourd\'hui', + 'Tasks due tomorrow' => 'Tâches qui arrivent à échéance demain', + 'Tasks due yesterday' => 'Tâches qui sont arrivées à échéance hier', + 'Closed tasks' => 'Tâches fermées', + 'Open tasks' => 'Tâches ouvertes', + 'Not assigned' => 'Non assignées', + 'View advanced search syntax' => 'Voir la syntaxe pour la recherche avancée', + 'Overview' => 'Vue d\'ensemble', + 'Board/Calendar/List view' => 'Vue Tableau/Calendrier/Liste', + 'Switch to the board view' => 'Basculer vers le tableau', + 'Switch to the list view' => 'Basculer vers la vue en liste', + 'Go to the search/filter box' => 'Aller au champ de recherche', + 'There is no activity yet.' => 'Il n\'y a pas encore d\'activité.', + 'No tasks found.' => 'Aucune tâche trouvée.', + 'Keyboard shortcut: "%s"' => 'Raccourci clavier : « %s »', + 'List' => 'Liste', + 'Filter' => 'Filtre', + 'Advanced search' => 'Recherche avancée', + 'Example of query: ' => 'Exemple de requête : ', + 'Search by project: ' => 'Rechercher par projet : ', + 'Search by column: ' => 'Rechercher par colonne : ', + 'Search by assignee: ' => 'Rechercher par assigné : ', + 'Search by color: ' => 'Rechercher par couleur : ', + 'Search by category: ' => 'Rechercher par catégorie : ', + 'Search by description: ' => 'Rechercher par description : ', + 'Search by due date: ' => 'Rechercher par date d\'échéance : ', + 'Average time spent in each column' => 'Temps moyen passé dans chaque colonne', + 'Average time spent' => 'Temps moyen passé', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Ce graphique montre le temps passé moyen dans chaque colonne pour les %d dernières tâches.', + 'Average Lead and Cycle time' => 'Durée moyenne du lead et cycle time', + 'Average lead time: ' => 'Lead time moyen : ', + 'Average cycle time: ' => 'Cycle time moyen : ', + 'Cycle Time' => 'Cycle time', + 'Lead Time' => 'Lead time', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Ce graphique montre la durée moyenne du lead et cycle time pour les %d dernières tâches.', + 'Average time into each column' => 'Temps moyen dans chaque colonne', + 'Lead and cycle time' => 'Lead et cycle time', + 'Lead time: ' => 'Lead time : ', + 'Cycle time: ' => 'Temps de cycle : ', + 'Time spent in each column' => 'Temps passé dans chaque colonne', + 'The lead time is the duration between the task creation and the completion.' => 'Le lead time est la durée entre la création de la tâche et sa complétion.', + 'The cycle time is the duration between the start date and the completion.' => 'Le cycle time est la durée entre la date de début et la complétion.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Si la tâche n\'est pas fermée, l\'heure courante est utilisée à la place de la date de complétion.', + 'Set the start date automatically' => 'Définir automatiquement la date de début', + 'Edit Authentication' => 'Modifier l\'authentification', + 'Remote user' => 'Utilisateur distant', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Les utilisateurs distants ne stockent pas leur mot de passe dans la base de données de Kanboard, exemples : comptes LDAP, GitHub ou Google.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Si vous cochez la case « Interdire le formulaire d\'authentification », les identifiants entrés dans le formulaire d\'authentification seront ignorés.', + 'Default task color' => 'Couleur par défaut des tâches', + 'This feature does not work with all browsers.' => 'Cette fonctionnalité n\'est pas compatible avec tous les navigateurs', + 'There is no destination project available.' => 'Il n\'y a pas de projet de destination disponible.', + 'Trigger automatically subtask time tracking' => 'Déclencher automatiquement le suivi du temps pour les sous-tâches', + 'Include closed tasks in the cumulative flow diagram' => 'Inclure les tâches fermées dans le diagramme de flux cumulé', + 'Current swimlane: %s' => 'Swimlane actuelle : %s', + 'Current column: %s' => 'Colonne actuelle : %s', + 'Current category: %s' => 'Catégorie actuelle : %s', + 'no category' => 'aucune catégorie', + 'Current assignee: %s' => 'Assigné actuel : %s', + 'not assigned' => 'non assigné', + 'Author:' => 'Auteur :', + 'contributors' => 'contributeurs', + 'License:' => 'Licence :', + 'License' => 'Licence', + 'Enter the text below' => 'Entrez le texte ci-dessous', + 'Start date:' => 'Date de début :', + 'Due date:' => 'Date d\'échéance :', + 'People who are project managers' => 'Personnes qui sont gestionnaires de projet', + 'People who are project members' => 'Personnes qui sont membres de projet', + 'NOK - Norwegian Krone' => 'NOK - Couronne norvégienne', + 'Show this column' => 'Montrer cette colonne', + 'Hide this column' => 'Cacher cette colonne', + 'End date' => 'Date de fin', + 'Users overview' => 'Vue d\'ensemble des utilisateurs', + 'Members' => 'Membres', + 'Shared project' => 'Projet partagé', + 'Project managers' => 'Gestionnaires de projet', + 'Projects list' => 'Liste des projets', + 'End date:' => 'Date de fin :', + 'Change task color when using a specific task link' => 'Changer la couleur de la tâche lorsqu\'un lien spécifique est utilisé', + 'Task link creation or modification' => 'Création ou modification d\'un lien sur une tâche', + 'Milestone' => 'Étape importante', + 'Reset the search/filter box' => 'Réinitialiser le champ de recherche', + 'Documentation' => 'Documentation', + 'Author' => 'Auteur', + 'Version' => 'Version', + 'Plugins' => 'Extensions', + 'There is no plugin loaded.' => 'Il n\'y a aucune extension chargée.', + 'My notifications' => 'Mes notifications', + 'Custom filters' => 'Filtres personnalisés', + 'Your custom filter has been created successfully.' => 'Votre filtre personnalisé a été créé avec succès.', + 'Unable to create your custom filter.' => 'Impossible de créer votre filtre personnalisé.', + 'Custom filter removed successfully.' => 'Filtre personnalisé supprimé avec succès.', + 'Unable to remove this custom filter.' => 'Impossible de supprimer ce filtre personnalisé.', + 'Edit custom filter' => 'Modification d\'un filtre personnalisé', + 'Your custom filter has been updated successfully.' => 'Votre filtre personnalisé a été mis à jour avec succès.', + 'Unable to update custom filter.' => 'Impossible de mettre à jour votre filtre personnalisé.', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Nouveau fichier joint sur la tâche n°%d : %s', + 'New comment on task #%d' => 'Nouveau commentaire sur la tâche n°%d', + 'Comment updated on task #%d' => 'Commentaire mis à jour sur la tâche n°%d', + 'New subtask on task #%d' => 'Nouvelle sous-tâche sur la tâche n°%d', + 'Subtask updated on task #%d' => 'Sous-tâche mise à jour sur la tâche n°%d', + 'New task #%d: %s' => 'Nouvelle tâche n°%d : %s', + 'Task updated #%d' => 'Tâche n°%d mise à jour', + 'Task #%d closed' => 'Tâche n°%d fermée', + 'Task #%d opened' => 'Tâche n°%d ouverte', + 'Column changed for task #%d' => 'Changement de colonne pour la tâche n°%d', + 'New position for task #%d' => 'Nouvelle position pour la tâche n°%d', + 'Swimlane changed for task #%d' => 'Changement de swimlane pour la tâche n°%d', + 'Assignee changed on task #%d' => 'Changement de l\'assigné pour la tâche n°%d', + '%d overdue tasks' => '%d tâches en retard', + 'No notification.' => 'Aucune notification.', + 'Mark all as read' => 'Tout marquer comme lu', + 'Mark as read' => 'Marquer comme lu', + 'Total number of tasks in this column across all swimlanes' => 'Nombre total de tâches dans cette colonne pour toutes les swimlanes', + 'Collapse swimlane' => 'Replier la swimlane', + 'Expand swimlane' => 'Déplier la swimlane', + 'Add a new filter' => 'Ajouter un nouveau filtre', + 'Share with all project members' => 'Partager avec tous les membres du projet', + 'Shared' => 'Partagé', + 'Owner' => 'Propriétaire', + 'Unread notifications' => 'Notifications non lus', + 'Notification methods:' => 'Méthodes de notifications :', + 'Unable to read your file' => 'Impossible de lire votre fichier', + '%d task(s) have been imported successfully.' => '%d tâche(s) ont été importées avec succès.', + 'Nothing has been imported!' => 'Rien n\'a été importé', + 'Import users from CSV file' => 'Importer des utilisateurs depuis un fichier CSV', + '%d user(s) have been imported successfully.' => '%d utilisateur(s) ont été importés avec succès.', + 'Comma' => 'Virgule', + 'Semi-colon' => 'Point-virgule', + 'Tab' => 'Tabulation', + 'Vertical bar' => 'Barre verticale', + 'Double Quote' => 'Guillemet double', + 'Single Quote' => 'Guillemet simple', + '%s attached a file to the task #%d' => '%s a attaché un fichier sur la tâche n°%d', + 'There is no column or swimlane activated in your project!' => 'Il n\'y a aucune colonne ou swimlane dans votre projet !', + 'Append filter (instead of replacement)' => 'Ajouter le filtre au lieu de le remplacer', + 'Append/Replace' => 'Ajouter/Remplacer', + 'Append' => 'Ajouter', + 'Replace' => 'Remplacer', + 'Import' => 'Importation', + 'Change sorting' => 'Changer l\'ordre', + 'Tasks Importation' => 'Importation des tâches', + 'Delimiter' => 'Délimiteur', + 'Enclosure' => 'Caractère d\'encadrement', + 'CSV File' => 'Fichier CSV', + 'Instructions' => 'Instructions', + 'Your file must use the predefined CSV format' => 'Votre fichier doit utiliser le format CSV prédéfini', + 'Your file must be encoded in UTF-8' => 'Votre fichier doit être encodé en UTF-8', + 'The first row must be the header' => 'La première ligne doit être le titre des colonnes', + 'Duplicates are not verified for you' => 'Les doublons ne sont pas vérifiés pour vous', + 'The due date must use the ISO format: YYYY-MM-DD' => 'La date d\'échéance doit utiliser le format ISO : AAAA-MM-JJ', + 'Download CSV template' => 'Télécharger le modèle CSV', + 'No external integration registered.' => 'Aucune intégration externe enregistrée.', + 'Duplicates are not imported' => 'Les doublons ne sont pas importés', + 'Usernames must be lowercase and unique' => 'Les noms d\'utilisateurs doivent être en minuscule et unique', + 'Passwords will be encrypted if present' => 'Les mots de passe seront chiffrés si présent', + '%s attached a new file to the task %s' => '%s a attaché un nouveau fichier à la tâche %s', + 'Link type' => 'Type de lien', + 'Assign automatically a category based on a link' => 'Assigner automatiquement une catégorie en fonction d\'un lien', + 'BAM - Konvertible Mark' => 'BAM - Mark convertible', + 'Assignee Username' => 'Utilisateur assigné', + 'Assignee Name' => 'Nom de l\'assigné', + 'Groups' => 'Groupes', + 'Members of %s' => 'Membres de %s', + 'New group' => 'Nouveau groupe', + 'Group created successfully.' => 'Groupe créé avec succès.', + 'Unable to create your group.' => 'Impossible de créer votre groupe.', + 'Edit group' => 'Modifier le groupe', + 'Group updated successfully.' => 'Groupe mis à jour avec succès.', + 'Unable to update your group.' => 'Impossible de mettre à jour votre groupe.', + 'Add group member to "%s"' => 'Ajouter un membre au groupe « %s »', + 'Group member added successfully.' => 'Membre ajouté avec succès au groupe.', + 'Unable to add group member.' => 'Impossible d\'ajouter ce membre au groupe.', + 'Remove user from group "%s"' => 'Supprimer un utilisateur d\'un groupe « %s »', + 'User removed successfully from this group.' => 'Utilisateur supprimé avec succès de ce groupe.', + 'Unable to remove this user from the group.' => 'Impossible de supprimer cet utilisateur du groupe.', + 'Remove group' => 'Supprimer le groupe', + 'Group removed successfully.' => 'Groupe supprimé avec succès.', + 'Unable to remove this group.' => 'Impossible de supprimer ce groupe.', + 'Project Permissions' => 'Permissions du projet', + 'Manager' => 'Gestionnaire', + 'Project Manager' => 'Chef de projet', + 'Project Member' => 'Membre du projet', + 'Project Viewer' => 'Visualiseur de projet', + 'Your account is locked for %d minutes' => 'Votre compte est verrouillé pour %d minutes', + 'Invalid captcha' => 'Captcha invalide', + 'The name must be unique' => 'Le nom doit être unique', + 'View all groups' => 'Voir tous les groupes', + 'There is no user available.' => 'Il n\'y a aucun utilisateur disponible', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Voulez-vous vraiment supprimer l\'utilisateur « %s » du groupe « %s » ?', + 'There is no group.' => 'Il n\'y a aucun groupe.', + 'Add group member' => 'Ajouter un membre au groupe', + 'Do you really want to remove this group: "%s"?' => 'Voulez-vous vraiment supprimer ce groupe : « %s » ?', + 'There is no user in this group.' => 'Il n\'y a aucun utilisateur dans ce groupe', + 'Permissions' => 'Permissions', + 'Allowed Users' => 'Utilisateurs autorisés', + 'No specific user has been allowed.' => 'Aucun utilisateur a été autorisé spécifiquement.', + 'Role' => 'Rôle', + 'Enter user name...' => 'Entrez le nom de l\'utilisateur…', + 'Allowed Groups' => 'Groupes autorisés', + 'No group has been allowed.' => 'Aucun groupe a été autorisé spécifiquement.', + 'Group' => 'Groupe', + 'Group Name' => 'Nom du groupe', + 'Enter group name...' => 'Entrez le nom du groupe…', + 'Role:' => 'Rôle :', + 'Project members' => 'Membres du projet', + '%s mentioned you in the task #%d' => '%s vous a mentionné dans la tâche n°%d', + '%s mentioned you in a comment on the task #%d' => '%s vous a mentionné dans un commentaire de la tâche n°%d', + 'You were mentioned in the task #%d' => 'Vous avez été mentionné dans la tâche n°%d', + 'You were mentioned in a comment on the task #%d' => 'Vous avez été mentionné dans un commentaire de la tâche n°%d', + 'Estimated hours: ' => 'Heures estimées : ', + 'Actual hours: ' => 'Heures actuelles : ', + 'Hours Spent' => 'Heures passées', + 'Hours Estimated' => 'Heures estimées', + 'Estimated Time' => 'Temps estimé', + 'Actual Time' => 'Temps actuel', + 'Estimated vs actual time' => 'Temps estimé vs réel', + 'RUB - Russian Ruble' => 'RUB - Rouble russe', + 'Assign the task to the person who does the action when the column is changed' => 'Assigner la tâche à la personne qui fait l\'action lorsque la colonne est changée', + 'Close a task in a specific column' => 'Fermer une tâche dans une colonne spécifique', + 'Time-based One-time Password Algorithm' => 'Mot de passe à usage unique basé sur le temps', + 'Two-Factor Provider: ' => 'Fournisseur d\'authentification à deux facteurs : ', + 'Disable two-factor authentication' => 'Désactiver l\'authentification à deux-facteurs', + 'Enable two-factor authentication' => 'Activer l\'authentification à deux-facteurs', + 'There is no integration registered at the moment.' => 'Il n\'y a aucune intégration enregistrée pour le moment.', + 'Password Reset for Kanboard' => 'Réinitialisation du mot de passe pour Kanboard', + 'Forgot password?' => 'Mot de passe oublié ?', + 'Enable "Forget Password"' => 'Activer la fonctionnalité « Mot de passe oublié »', + 'Password Reset' => 'Réinitialisation du mot de passe', + 'New password' => 'Nouveau mot de passe', + 'Change Password' => 'Changer de mot de passe', + 'To reset your password click on this link:' => 'Pour réinitialiser votre mot de passe cliquer sur ce lien :', + 'Last Password Reset' => 'Dernières réinitialisations de mot de passe', + 'The password has never been reinitialized.' => 'Le mot de passe n\'a jamais été réinitialisé.', + 'Creation' => 'Création', + 'Expiration' => 'Expiration', + 'Password reset history' => 'Historique de la réinitialisation du mot de passe', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Toutes les tâches de la colonne « %s » et de la swimlane « %s » ont été fermées avec succès.', + 'Do you really want to close all tasks of this column?' => 'Voulez-vous vraiment fermer toutes les tâches de cette colonne ?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d tâche(s) dans la colonne « %s » et la swimlane « %s » seront fermées.', + 'Close all tasks in this column and this swimlane' => 'Fermer toutes les tâches dans cette colonne et cette swimlane', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Aucun plugin n\'a enregistré une méthode de notification de projet. Vous pouvez toujours configurer les notifications individuelles dans votre profil utilisateur.', + 'My dashboard' => 'Mon tableau de bord', + 'My profile' => 'Mon profil', + 'Project owner: ' => 'Responsable du projet : ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'L\'identifiant du projet est optionnel et doit être alphanumérique, exemple : MONPROJET.', + 'Project owner' => 'Responsable du projet', + 'Personal projects do not have users and groups management.' => 'Les projets personnels n\'ont pas de gestion d\'utilisateurs et de groupes.', + 'There is no project member.' => 'Il n\'y a aucun membre du projet.', + 'Priority' => 'Priorité', + 'Task priority' => 'Priorité des tâches', + 'General' => 'Général', + 'Dates' => 'Dates', + 'Default priority' => 'Priorité par défaut', + 'Lowest priority' => 'Priorité basse', + 'Highest priority' => 'Priorité haute', + 'Close a task when there is no activity' => 'Fermer une tâche sans activité', + 'Duration in days' => 'Durée en jours', + 'Send email when there is no activity on a task' => 'Envoyer un email lorsqu\'il n\'y a pas d\'activité sur une tâche', + 'Unable to fetch link information.' => 'Impossible de récupérer les informations sur le lien.', + 'Daily background job for tasks' => 'Tâche planifiée quotidienne pour les tâches', + 'Auto' => 'Auto', + 'Related' => 'Relié', + 'Attachment' => 'Pièce-jointe', + 'Web Link' => 'Lien web', + 'External links' => 'Liens externes', + 'Add external link' => 'Ajouter un lien externe', + 'Type' => 'Type', + 'Dependency' => 'Dépendance', + 'Add internal link' => 'Ajouter un lien interne', + 'Add a new external link' => 'Ajouter un nouveau lien externe', + 'Edit external link' => 'Modifier un lien externe', + 'External link' => 'Lien externe', + 'Copy and paste your link here...' => 'Copier-coller votre lien ici…', + 'URL' => 'URL', + 'Internal links' => 'Liens internes', + 'Assign to me' => 'Assigner à moi', + 'Me' => 'Moi', + 'Do not duplicate anything' => 'Ne rien dupliquer', + 'Projects management' => 'Gestion des projets', + 'Users management' => 'Gestion des utilisateurs', + 'Groups management' => 'Gestion des groupes', + 'Create from another project' => 'Créer depuis un autre projet', + 'open' => 'ouvert', + 'closed' => 'fermé', + 'Priority:' => 'Priorité :', + 'Reference:' => 'Référence :', + 'Complexity:' => 'Complexité :', + 'Swimlane:' => 'Swimlane :', + 'Column:' => 'Colonne :', + 'Position:' => 'Position :', + 'Creator:' => 'Créateur :', + 'Time estimated:' => 'Temps estimé :', + '%s hours' => '%s heures', + 'Time spent:' => 'Temps passé :', + 'Created:' => 'Créé le :', + 'Modified:' => 'Modifié le :', + 'Completed:' => 'Terminé le :', + 'Started:' => 'Commencé le :', + 'Moved:' => 'Déplacé le : ', + 'Task #%d' => 'Tâche n°%d', + 'Time format' => 'Format de l\'heure', + 'Start date: ' => 'Date de début : ', + 'End date: ' => 'Date de fin : ', + 'New due date: ' => 'Nouvelle date d\'échéance : ', + 'Start date changed: ' => 'Date de début modifiée : ', + 'Disable personal projects' => 'Désactiver les projets personnels', + 'Do you really want to remove this custom filter: "%s"?' => 'Voulez-vous vraiment supprimer ce filtre personnalisé : « %s » ?', + 'Remove a custom filter' => 'Supprimer un filtre personnalisé', + 'User activated successfully.' => 'Utilisateur activé avec succès.', + 'Unable to enable this user.' => 'Impossible d\'activer cet utilisateur.', + 'User disabled successfully.' => 'Utilisateur désactivé avec succès.', + 'Unable to disable this user.' => 'Impossible de désactiver cet utilisateur.', + 'All files have been uploaded successfully.' => 'Tous les fichiers ont été envoyés avec succès.', + 'The maximum allowed file size is %sB.' => 'La taille maximale autorisée pour les fichiers est de %s o.', + 'Drag and drop your files here' => 'Glissez-déposez vos fichiers ici', + 'choose files' => 'choisissez des fichiers', + 'View profile' => 'Voir le profil', + 'Two Factor' => 'Deux-Facteurs', + 'Disable user' => 'Désactiver l\'utilisateur', + 'Do you really want to disable this user: "%s"?' => 'Voulez-vous vraiment désactiver cet utilisateur : « %s » ?', + 'Enable user' => 'Activer un utilisateur', + 'Do you really want to enable this user: "%s"?' => 'Voulez-vous vraiment activer cet utilisateur : « %s » ?', + 'Download' => 'Télécharger', + 'Uploaded: %s' => 'Envoyé : %s', + 'Size: %s' => 'Taille : %s', + 'Uploaded by %s' => 'Envoyé par %s', + 'Filename' => 'Nom du fichier', + 'Size' => 'Taille', + 'Column created successfully.' => 'La colonne a été créée avec succès.', + 'Another column with the same name exists in the project' => 'Une autre colonne existe avec le même nom dans le projet', + 'Default filters' => 'Filtres par défaut', + 'Your board doesn\'t have any columns!' => 'Votre tableau n\'a aucune colonne', + 'Change column position' => 'Changer la position de la colonne', + 'Switch to the project overview' => 'Aller à l\'aperçu du projet', + 'User filters' => 'Filtres des utilisateurs', + 'Category filters' => 'Filtres des catégories', + 'Upload a file' => 'Envoyer un fichier', + 'View file' => 'Voir le fichier', + 'Last activity' => 'Dernières activités', + 'Change subtask position' => 'Changer la position de la sous-tâche', + 'This value must be greater than %d' => 'Cette valeur doit être plus grande que %d', + 'Another swimlane with the same name exists in the project' => 'Une autre swimlane existe avec le même nom dans le projet', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Exemple : https://exemple.kanboard.org/ (utilisé pour générer les URL absolues)', + 'Actions duplicated successfully.' => 'Actions dupliquées avec succès.', + 'Unable to duplicate actions.' => 'Impossible de dupliquer les actions.', + 'Add a new action' => 'Ajouter une nouvelle action', + 'Import from another project' => 'Importer depuis un autre projet', + 'There is no action at the moment.' => 'Il n\'y a aucune action pour le moment.', + 'Import actions from another project' => 'Importer les actions depuis un autre projet', + 'There is no available project.' => 'Il n\'y a pas de projet disponible.', + 'Local File' => 'Fichier local', + 'Configuration' => 'Configuration', + 'PHP version:' => 'Version de PHP :', + 'PHP SAPI:' => 'PHP SAPI :', + 'OS version:' => 'Version du système d\'exploitation :', + 'Database version:' => 'Version de la base de donnée :', + 'Browser:' => 'Navigateur web :', + 'Task view' => 'Vue détaillée d\'une tâche', + 'Edit task' => 'Modifier la tâche', + 'Edit description' => 'Modifier la description', + 'New internal link' => 'Nouveau lien interne', + 'Display list of keyboard shortcuts' => 'Afficher la liste des raccourcis claviers', + 'Avatar' => 'Avatar', + 'Upload my avatar image' => 'Uploader mon image d\'avatar', + 'Remove my image' => 'Supprimer mon image', + 'The OAuth2 state parameter is invalid' => 'Le paramètre « state » de OAuth2 est invalide', + 'User not found.' => 'Utilisateur introuvable.', + 'Search in activity stream' => 'Chercher dans le flux d\'activité', + 'My activities' => 'Mes activités', + 'Activity until yesterday' => 'Activités jusqu\'à hier', + 'Activity until today' => 'Activités jusqu\'à aujourd\'hui', + 'Search by creator: ' => 'Rechercher par créateur : ', + 'Search by creation date: ' => 'Rechercher par date de création : ', + 'Search by task status: ' => 'Rechercher par le statut des tâches : ', + 'Search by task title: ' => 'Rechercher par le titre des tâches : ', + 'Activity stream search' => 'Recherche dans le flux d\'activité', + 'Projects where "%s" is manager' => 'Projets où « %s » est gestionnaire', + 'Projects where "%s" is member' => 'Projets où « %s » est membre du projet', + 'Open tasks assigned to "%s"' => 'Tâches ouvertes assignées à « %s »', + 'Closed tasks assigned to "%s"' => 'Tâches fermées assignées à « %s »', + 'Assign automatically a color based on a priority' => 'Assigner automatiquement une couleur par rapport à une priorité', + 'Overdue tasks for the project(s) "%s"' => 'Tâches en retard pour le projet(s) « %s »', + 'Upload files' => 'Envoyer les fichiers', + 'Installed Plugins' => 'Extensions installées', + 'Plugin Directory' => 'Liste des extensions', + 'Plugin installed successfully.' => 'Extension installée avec succès.', + 'Plugin updated successfully.' => 'Extension mise à jour avec succès.', + 'Plugin removed successfully.' => 'Extension supprimée avec succès.', + 'Subtask converted to task successfully.' => 'Sous-tâche convertie en tâche avec succès.', + 'Unable to convert the subtask.' => 'Impossible de convertir cette sous-tâche.', + 'Unable to extract plugin archive.' => 'Impossible d\'extraire l\'archive de l\'extension.', + 'Plugin not found.' => 'Extension introuvable.', + 'You don\'t have the permission to remove this plugin.' => 'Vous n\'avez pas la permission de supprimer ce plugin.', + 'Unable to download plugin archive.' => 'Impossible de télécharger l\'archive du plugin.', + 'Unable to write temporary file for plugin.' => 'Impossible d\'écrire le fichier temporaire pour l\'extension.', + 'Unable to open plugin archive.' => 'Impossible d\'ouvrir l\'archive du plugin.', + 'There is no file in the plugin archive.' => 'Il n\'y a aucun fichier dans l\'archive du plugin.', + 'Create tasks in bulk' => 'Créer plusieurs tâches en même temps', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Votre instance de Kanboard n\'est pas configurée pour installer des extensions depuis l\'interface utilisateur.', + 'There is no plugin available.' => 'Il n\'a aucune extension disponible.', + 'Install' => 'Installer', + 'Update' => 'Mettre à jour', + 'Up to date' => 'À jour', + 'Not available' => 'Non disponible', + 'Remove plugin' => 'Supprimer l\'extension', + 'Do you really want to remove this plugin: "%s"?' => 'Voulez-vous vraiment supprimer cette extension : « %s » ?', + 'Uninstall' => 'Désinstaller', + 'Listing' => 'Listing', + 'Metadata' => 'Metadonnées', + 'Manage projects' => 'Gérer les projets', + 'Convert to task' => 'Convertir en tâche', + 'Convert sub-task to task' => 'Convertir une sous-tâche en tâche', + 'Do you really want to convert this sub-task to a task?' => 'Voulez-vous vraiment convertir cette sous-tâche en tâche ?', + 'My task title' => 'Mon titre pour la tâche', + 'Enter one task by line.' => 'Entrez une tâche par ligne.', + 'Number of failed login:' => 'Nombre de connexions échouées :', + 'Account locked until:' => 'Compte bloqué jusqu\'au :', + 'Email settings' => 'Paramètres des emails', + 'Email sender address' => 'Adresse email de l\'expéditeur', + 'Email transport' => 'Transport des emails', + 'Webhook token' => 'Jeton de sécurité des webhooks', + 'Project tags management' => 'Gestion des libellés pour le projet', + 'Tag created successfully.' => 'Libellé créé avec succès.', + 'Unable to create this tag.' => 'Impossible de créer ce libellé.', + 'Tag updated successfully.' => 'Libellé mis à jour avec succès.', + 'Unable to update this tag.' => 'Impossible de mettre à jour ce libellé.', + 'Tag removed successfully.' => 'Libellé supprimé avec succès.', + 'Unable to remove this tag.' => 'Impossible de supprimer ce libellé.', + 'Global tags management' => 'Gestion des libellés globaux', + 'Tags' => 'Libellés', + 'Tags management' => 'Gestion des libellés', + 'Add new tag' => 'Ajouter un nouveau libellé', + 'Edit a tag' => 'Mettre à jour un libellé', + 'Project tags' => 'Libellés du projet', + 'There is no specific tag for this project at the moment.' => 'Il n\'y a aucun libellé spécifique pour ce projet pour le moment.', + 'Tag' => 'Libellé', + 'Remove a tag' => 'Supprimer un libellé', + 'Do you really want to remove this tag: "%s"?' => 'Voulez-vous vraiment supprimer ce libellé : « %s » ?', + 'Global tags' => 'Libellés globaux', + 'There is no global tag at the moment.' => 'Il n\'y a aucun libellé global pour le moment.', + 'This field cannot be empty' => 'Ce champ ne peut être vide', + 'Close a task when there is no activity in a specific column' => 'Fermer une tâche lorsqu\'il n\'y a aucune activité dans une colonne spécifique', + '%s removed a subtask for the task #%d' => '%s a supprimé une sous-tâche de la tâche n°%d', + '%s removed a comment on the task #%d' => '%s a supprimé un commentaire de la tâche n°%d', + 'Comment removed on task #%d' => 'Commentaire supprimé sur la tâche n°%d', + 'Subtask removed on task #%d' => 'Sous-tâche supprimée sur la tâche n°%d', + 'Hide tasks in this column in the dashboard' => 'Cacher les tâches de cette colonne dans le tableau de bord', + '%s removed a comment on the task %s' => '%s a supprimé un commentaire de la tâche %s', + '%s removed a subtask for the task %s' => '%s a supprimé une sous-tâche de la tâche %s', + 'Comment removed' => 'Commentaire supprimé', + 'Subtask removed' => 'Sous-tâche supprimée', + '%s set a new internal link for the task #%d' => '%s a défini un nouveau lien interne pour la tâche n°%d', + '%s removed an internal link for the task #%d' => '%s a supprimé un lien interne pour la tâche n°%d', + 'A new internal link for the task #%d has been defined' => 'Un nouveau lien interne pour la tâche n°%d a été défini', + 'Internal link removed for the task #%d' => 'Lien interne supprimé pour la tâche n°%d', + '%s set a new internal link for the task %s' => '%s a défini un nouveau lien interne pour la tâche %s', + '%s removed an internal link for the task %s' => '%s a supprimé un lien interne pour la tâche %s', + 'Automatically set the due date on task creation' => 'Définir automatiquement la date d\'échéance lors de la création de la tâche', + 'Move the task to another column when closed' => 'Déplacer la tâche vers une autre colonne lorsque celle-ci est fermée', + 'Move the task to another column when not moved during a given period' => 'Déplacer la tâche vers une autre colonne lorsque celle-ci n\'a pas été bougée pendant une certaine période', + 'Dashboard for %s' => 'Tableau de bord pour %s', + 'Tasks overview for %s' => 'Aperçu des tâches pour %s', + 'Subtasks overview for %s' => 'Aperçu des sous-tâches pour %s', + 'Projects overview for %s' => 'Aperçu des projets pour %s', + 'Activity stream for %s' => 'Flux d\'activité pour %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Assigner une couleur lorsque une tâche est déplacée dans une swimlane spécifique', + 'Assign a priority when the task is moved to a specific swimlane' => 'Assigner une priorité lorsque une tâche est déplacée dans une swimlane spécifique', + 'User unlocked successfully.' => 'Utilisateur débloqué avec succès.', + 'Unable to unlock the user.' => 'Impossible de débloquer cet utilisateur.', + 'Move a task to another swimlane' => 'Déplacer une tâche dans une autre swimlane', + 'Creator Name' => 'Nom du créateur', + 'Time spent and estimated' => 'Temps passé et estimé', + 'Move position' => 'Changer la position', + 'Move task to another position on the board' => 'Déplacer la tâche vers une autre position sur le tableau', + 'Insert before this task' => 'Insérer avant cette tâche', + 'Insert after this task' => 'Insérer après cette tâche', + 'Unlock this user' => 'Débloquer cet utilisateur', + 'Custom Project Roles' => 'Rôles personnalisés du projet', + 'Add a new custom role' => 'Ajouter un nouveau rôle personnalisé', + 'Restrictions for the role "%s"' => 'Restrictions pour le rôle « %s »', + 'Add a new project restriction' => 'Ajouter une nouvelle restriction pour ce projet', + 'Add a new drag and drop restriction' => 'Ajouter une nouvelle restriction pour le glisser-déposer', + 'Add a new column restriction' => 'Ajouter une nouvelle restriction basé sur les colonnes', + 'Edit this role' => 'Modifier ce rôle', + 'Remove this role' => 'Supprimer ce rôle', + 'There is no restriction for this role.' => 'Il n\'y a aucune restriction pour ce rôle', + 'Only moving task between those columns is permitted' => 'La tâche ne peut être déplacée qu\'entre ces colonnes', + 'Close a task in a specific column when not moved during a given period' => 'Fermez une tâche dans une colonne spécifique lorsqu\'elle n\'est pas déplacée au cours d\'une période donnée', + 'Edit columns' => 'Modifier les colonnes', + 'The column restriction has been created successfully.' => 'La restriction sur les colonnes a été créée avec succès.', + 'Unable to create this column restriction.' => 'Impossible de créer cette restriction de colonne.', + 'Column restriction removed successfully.' => 'Restriction de colonne supprimée avec succès.', + 'Unable to remove this restriction.' => 'Impossible de supprimer cette restriction.', + 'Your custom project role has been created successfully.' => 'Votre rôle personnalisé a été créé avec succès.', + 'Unable to create custom project role.' => 'Impossible de créer ce rôle personnalisé.', + 'Your custom project role has been updated successfully.' => 'Votre rôle personnalisé a été mis à jour avec succès.', + 'Unable to update custom project role.' => 'Impossible de mettre à jour ce rôle personnalisé.', + 'Custom project role removed successfully.' => 'Rôle personnalisé supprimé avec succès.', + 'Unable to remove this project role.' => 'Impossible de supprimer ce rôle.', + 'The project restriction has been created successfully.' => 'La restriction de projet a été créée avec succès.', + 'Unable to create this project restriction.' => 'Impossible de créer cette restriction de projet.', + 'Project restriction removed successfully.' => 'Restriction de projet supprimée avec succès.', + 'You cannot create tasks in this column.' => 'Vous ne pouvez pas créer de tâche dans cette colonne.', + 'Task creation is permitted for this column' => 'La création de tâche est permise dans cette colonne', + 'Closing or opening a task is permitted for this column' => 'Fermer ou ouvrir une tâche est permis pour cette colonne', + 'Task creation is blocked for this column' => 'La création de tâche est bloquée pour cette colonne', + 'Closing or opening a task is blocked for this column' => 'Fermer ou ouvrir une tâche est interdit pour cette colonne', + 'Task creation is not permitted' => 'La création de tâche n\'est pas permise', + 'Closing or opening a task is not permitted' => 'Fermer ou ouvrir une tâche n\'est pas autorisé', + 'New drag and drop restriction for the role "%s"' => 'Nouvelle restriction de glisser-déposer pour le rôle « %s »', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Les gens qui font partie de ce rôle pourront seulement déplacer des tâches entre la colonne source et de destination.', + 'Remove a column restriction' => 'Supprimer une restriction de colonne', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Voulez-vous vraiment supprimer cette restriction de colonne : « %s » vers « %s » ?', + 'New column restriction for the role "%s"' => 'Nouvelle restriction de colonne pour le rôle « %s »', + 'Rule' => 'Règle', + 'Do you really want to remove this column restriction?' => 'Voulez-vous vraiment supprimer cette restriction de colonne ?', + 'Custom roles' => 'Rôles personnalisés', + 'New custom project role' => 'Nouveau rôle de projet personnalisé', + 'Edit custom project role' => 'Modifier un rôle de projet personnalisé', + 'Remove a custom role' => 'Supprimer un rôle personnalisé', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Voulez-vous vraiment supprimer ce rôle personnalisé « %s » ? Tous les gens assignés à ce rôle deviendront membre du projet.', + 'There is no custom role for this project.' => 'Il n\'y a aucun rôle personnalisé pour ce projet.', + 'New project restriction for the role "%s"' => 'Nouvelle restriction de projet pour le rôle « %s »', + 'Restriction' => 'Restriction', + 'Remove a project restriction' => 'Supprimer une restriction de projet', + 'Do you really want to remove this project restriction: "%s"?' => 'Voulez-vous vraiment supprimer cette restriction de projet : « %s » ?', + 'Duplicate to multiple projects' => 'Dupliquer vers plusieurs projets', + 'This field is required' => 'Ce champ est requis', + 'Moving a task is not permitted' => 'Déplacer une tâche n\'est pas autorisé', + 'This value must be in the range %d to %d' => 'Cette valeur doit être définie entre %d et %d', + 'You are not allowed to move this task.' => 'Vous n\'êtes pas autorisé à déplacer cette tâche.', + 'API User Access' => 'Accès utilisateur de l\'API', + 'Preview' => 'Aperçu', + 'Write' => 'Écrire', + 'Write your text in Markdown' => 'Écrivez votre texte en Markdown', + 'No personal API access token registered.' => 'Aucun jeton d\'accès personnel à l\'API enregistré.', + 'Your personal API access token is "%s"' => 'Votre jeton d\'accès personnel à l\'API est « %s »', + 'Remove your token' => 'Supprimer votre jeton', + 'Generate a new token' => 'Générer un nouveau jeton', + 'Showing %d-%d of %d' => 'Éléments %d à %d sur %d', + 'Outgoing Emails' => 'Emails sortants', + 'Add or change currency rate' => 'Ajouter ou changer le taux de change', + 'Reference currency: %s' => 'Monnaie de référence : %s', + 'Add custom filters' => 'Ajouter un filtre personnalisé', + 'Export' => 'Exporter', + 'Add link label' => 'Ajouter un libellé de lien', + 'Incompatible Plugins' => 'Extensions incompatibles', + 'Compatibility' => 'Compatibilité', + 'Permissions and ownership' => 'Permissions et propriétaire', + 'Priorities' => 'Priorités', + 'Close this window' => 'Fermer cette fenêtre', + 'Unable to upload this file.' => 'Impossible de téléverser ce fichier.', + 'Import tasks' => 'Importer des tâches', + 'Choose a project' => 'Choisir un projet', + 'Profile' => 'Profil', + 'Application role' => 'Rôle dans l\'application', + '%d invitations were sent.' => '%d invitations ont été envoyées.', + '%d invitation was sent.' => '%d invitation a été envoyée.', + 'Unable to create this user.' => 'Impossible de créer cet utilisateur.', + 'Kanboard Invitation' => 'Invitation pour Kanboard', + 'Visible on dashboard' => 'Visible sur le tableau de bord', + 'Created at:' => 'Créé le :', + 'Updated at:' => 'Mis à jour le :', + 'There is no custom filter.' => 'Il n\'y a aucun filtre personnalisé.', + 'New User' => 'Nouvel utilisateur', + 'Authentication' => 'Authentification', + 'If checked, this user will use a third-party system for authentication.' => 'Si coché, cet utilisateur va utiliser un système externe pour s\'authentifier.', + 'The password is necessary only for local users.' => 'Le mot de passe est nécessaire uniquement pour les utilisateurs locaux.', + 'You have been invited to register on Kanboard.' => 'Vous avez été invité à vous inscrire sur Kanboard.', + 'Click here to join your team' => 'Cliquez ici pour rejoindre votre équipe', + 'Invite people' => 'Inviter des gens', + 'Emails' => 'Emails', + 'Enter one email address by line.' => 'Entrez une adresse électronique par ligne.', + 'Add these people to this project' => 'Ajouter ces personnes à ce projet', + 'Add this person to this project' => 'Ajouter cet utilisateur à ce projet', + 'Sign-up' => 'Inscription', + 'Credentials' => 'Informations d\'identification', + 'New user' => 'Nouvel utilisateur', + 'This username is already taken' => 'Ce nom d\'utilisateur est déjà pris', + 'Your profile must have a valid email address.' => 'Votre profil doit avoir une adresse email valide.', + 'TRL - Turkish Lira' => 'TRL - Livre turque', + 'The project email is optional and could be used by several plugins.' => 'L\'adresse email d\'un projet est optionnelle et pourrait être utilisée par plusieurs extensions.', + 'The project email must be unique across all projects' => 'L\'adresse email d\'un projet doit être unique pour tous les projets', + 'The email configuration has been disabled by the administrator.' => 'La configuration des emails a été désactivée par l\'administrateur.', + 'Close this project' => 'Fermer ce projet', + 'Open this project' => 'Ouvrir ce projet', + 'Close a project' => 'Fermer un projet', + 'Do you really want to close this project: "%s"?' => 'Voulez-vous vraiment fermer ce projet : « %s » ?', + 'Reopen a project' => 'Rouvrir un projet', + 'Do you really want to reopen this project: "%s"?' => 'Voulez-vous vraiment rouvrir ce projet : « %s » ?', + 'This project is open' => 'Ce projet est actif', + 'This project is closed' => 'Ce projet est fermé', + 'Unable to upload files, check the permissions of your data folder.' => 'Impossible de transférer le ou les fichiers, vérifiez les permissions du répertoire de données.', + 'Another category with the same name exists in this project' => 'Une autre catégorie avec le même nom existe dans ce projet', + 'Comment sent by email successfully.' => 'Commentaire envoyé par email avec succès.', + 'Sent by email to "%s" (%s)' => 'Envoyé par email à « %s » (%s)', + 'Unable to read uploaded file.' => 'Impossible de lire le fichier téléversé.', + 'Database uploaded successfully.' => 'Base de données téléversée avec succès.', + 'Task sent by email successfully.' => 'Tâche envoyée par email avec succès.', + 'There is no category in this project.' => 'Il n\'y a aucune catégorie dans ce projet.', + 'Send by email' => 'Envoyer par email', + 'Create and send a comment by email' => 'Créer et envoyer un commentaire par email', + 'Subject' => 'Sujet', + 'Upload the database' => 'Téléverser la base de données', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Vous pouvez téléverser la base de données SQLite précédemment téléchargée au format Gzip.', + 'Database file' => 'Fichier de la base de données', + 'Upload' => 'Téléverser', + 'Your project must have at least one active swimlane.' => 'Votre projet doit avoir au moins une swimlane active.', + 'Project: %s' => 'Projet : %s', + 'Automatic action not found: "%s"' => 'Action automatique introuvable : « %s »', + '%d projects' => '%d projets', + '%d project' => '%d projet', + 'There is no project.' => 'Il n\'y a pas de projet.', + 'Sort' => 'Trier', + 'Project ID' => 'ID du projet', + 'Project name' => 'Nom du projet', + 'Public' => 'Public', + 'Personal' => 'Personnel', + '%d tasks' => '%d tâches', + '%d task' => '%d tâche', + 'Task ID' => 'ID de la tâche', + 'Assign automatically a color when due date is expired' => 'Assigner automatiquement une couleur lorsque la date d\'échéance expire', + 'Total score in this column across all swimlanes' => 'Score total dans cette colonne à travers toutes les swimlanes', + 'HRK - Kuna' => 'HRK - Kuna croate', + 'ARS - Argentine Peso' => 'ARS - Peso argentin', + 'COP - Colombian Peso' => 'COP - Peso colombien', + '%d groups' => '%d groupes', + '%d group' => '%d groupe', + 'Group ID' => 'ID du groupe', + 'External ID' => 'Identifiant externe', + '%d users' => '%d utilisateurs', + '%d user' => '%d utilisateur', + 'Hide subtasks' => 'Cacher les sous-tâches', + 'Show subtasks' => 'Montrer les sous-tâches', + 'Authentication Parameters' => 'Paramètres d\'authentification', + 'API Access' => 'Accès à l\'API', + 'No users found.' => 'Aucun utilisateur trouvé.', + 'User ID' => 'ID de l\'utilisateur', + 'Notifications are activated' => 'Notifications activées', + 'Notifications are disabled' => 'Notifications désactivées', + 'User disabled' => 'Utilisateur désactivé', + '%d notifications' => '%d notifications', + '%d notification' => '%d notification', + 'There is no external integration installed.' => 'Il n\'y a aucune intégration externe installée.', + 'You are not allowed to update tasks assigned to someone else.' => 'Vous n\'êtes pas autorisé à mettre à jour une tâche assignée à quelqu\'un d\'autre.', + 'You are not allowed to change the assignee.' => 'Vous n\'êtes pas autorisé à changer l\'assigné', + 'Task suppression is not permitted' => 'La suppression des tâches n\'est pas autorisée', + 'Changing assignee is not permitted' => 'Changement d\'assigné non autorisé', + 'Update only assigned tasks is permitted' => 'Seulement la mise à jour des tâches assignées est autorisé', + 'Only for tasks assigned to the current user' => 'Seulement pour les tâches assignées à l\'utilisateur courant', + 'My projects' => 'Mes projets', + 'You are not a member of any project.' => 'Vous n\'êtes membre d\'aucun projet', + 'My subtasks' => 'Mes sous-tâches', + '%d subtasks' => '%d sous-tâches', + '%d subtask' => '%d sous-tâche', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Seulement le déplacement des tâches entre ces colonnes est autorisé pour l\'utilisateur courant', + '[DUPLICATE]' => '[COPIE]', + 'DKK - Danish Krona' => 'DKK - Couronne danoise', + 'Remove user from group' => 'Supprimer cet utilisateur du groupe', + 'Assign the task to its creator' => 'Assigner une tâche à son créateur', + 'This task was sent by email to "%s" with subject "%s".' => 'Cette tâche a été envoyée par courrier électronique à « %s » avec le sujet « %s ».', + 'Predefined Email Subjects' => 'Sujets de courrier électronique prédéfinis', + 'Write one subject by line.' => 'Écrivez un sujet par ligne.', + 'Create another link' => 'Créer un autre lien', + 'BRL - Brazilian Real' => 'BRL - Real brésilien', + 'Add a new Kanboard task' => 'Ajouter une nouvelle tâche Kanboard', + 'Subtask not started' => 'Sous-tâche pas encore commencé', + 'Subtask currently in progress' => 'Sous-tâche actuellement en progrès', + 'Subtask completed' => 'Sous-tâche terminée', + 'Subtask added successfully.' => 'Sous-tâche ajoutée avec succès.', + '%d subtasks added successfully.' => '%d sous-tâches ajoutées avec succès.', + 'Enter one subtask by line.' => 'Entrez une sous-tâche par ligne.', + 'Predefined Contents' => 'Contenus prédéfinis', + 'Predefined contents' => 'Contenus prédéfinis', + 'Predefined Task Description' => 'Modèles de description de tâches', + 'Do you really want to remove this template? "%s"' => 'Voulez-vous vraiment supprimer ce modèle ? « %s »', + 'Add predefined task description' => 'Ajouter un modèle de description de tâche', + 'Predefined Task Descriptions' => 'Modèles de description de tâches', + 'Template created successfully.' => 'Modèle créé avec succès.', + 'Unable to create this template.' => 'Impossible de créer ce modèle.', + 'Template updated successfully.' => 'Modèle mis à jour avec succès.', + 'Unable to update this template.' => 'Impossible de mettre à jour ce modèle.', + 'Template removed successfully.' => 'Modèle supprimé avec succès.', + 'Unable to remove this template.' => 'Impossible de supprimer ce modèle.', + 'Template for the task description' => 'Modèle pour la description des tâches', + 'The start date is greater than the end date' => 'La date de début est plus grande que la date d\'échéance', + 'Tags must be separated by a comma' => 'Les libellés doivent être séparés par une virgule', + 'Only the task title is required' => 'Seulement le titre est obligatoire', + 'Creator Username' => 'Identifiant du créateur', + 'Color Name' => 'Nom de la couleur', + 'Column Name' => 'Nom de la colonne', + 'Swimlane Name' => 'Nom de la swimlane', + 'Time Estimated' => 'Durée estimée', + 'Time Spent' => 'Temps passé', + 'External Link' => 'Lien externe', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Cette fonctionnalité active l\'abonnement iCal, le flux RSS et la vue publique du tableau.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Arrêter le minuteur de toutes les sous-tâches lorsque la tâche est déplacée dans une autre colonne', + 'Subtask Title' => 'Titre de la sous-tâche', + 'Add a subtask and activate the timer when moving a task to another column' => 'Ajouter une sous-tâche et activer le minuteur lorsque la tâche est déplacée dans une autre colonne', + 'days' => 'jours', + 'minutes' => 'minutes', + 'seconds' => 'secondes', + 'Assign automatically a color when preset start date is reached' => 'Assigner automatiquement une couleur lorsque la date de début est passée', + 'Move the task to another column once a predefined start date is reached' => 'Déplacer la tâche vers une autre colonne lorsque la date de début est passée', + 'This task is now linked to the task %s with the relation "%s"' => 'Cette tâche est maintenant liée à la tâche %s avec la relation « %s »', + 'The link with the relation "%s" to the task %s has been removed' => 'Le lien avec la relation « %s » de la tâche %s a été supprimé', + 'Custom Filter:' => 'Filtre personnalisé :', + 'Unable to find this group.' => 'Impossible de trouver ce groupe.', + '%s moved the task #%d to the column "%s"' => '%s a déplacé la tâche #%d vers la colonne « %s »', + '%s moved the task #%d to the position %d in the column "%s"' => '%s a déplacé la tâche #%d à la position %d dans la colonne « %s »', + '%s moved the task #%d to the swimlane "%s"' => '%s a déplacé la tâche #%d vers la swimlane « %s »', + '%sh spent' => '%s h passé', + '%sh estimated' => '%s h estimé', + 'Select All' => 'Tout sélectionner', + 'Unselect All' => 'Tout désélectionner', + 'Apply action' => 'Appliquer une action', + 'Move selected tasks to another column or swimlane' => 'Déplacer les tâches sélectionnées vers une autre colonne ou swimlane', + 'Edit tasks in bulk' => 'Modifier les tâches en masse', + 'Choose the properties that you would like to change for the selected tasks.' => 'Choisissez les propriétés que vous souhaitez changer pour les tâches sélectionnées.', + 'Configure this project' => 'Configurer ce projet', + 'Start now' => 'Débuter maintenant', + '%s removed a file from the task #%d' => '%s a supprimé un fichier de la tâche #%d', + 'Attachment removed from task #%d: %s' => 'Pièce jointe supprimée de la tâche #%d : %s', + 'No color' => 'Aucune couleur', + 'Attachment removed "%s"' => 'Pièce jointe « %s » supprimée', + '%s removed a file from the task %s' => '%s a supprimé un fichier de la tâche %s', + 'Move the task to another swimlane when assigned to a user' => 'Déplacer la tâche dans une autre swimlane lorsqu\'un utilisateur est assigné', + 'Destination swimlane' => 'Swimlane de destination', + 'Assign a category when the task is moved to a specific swimlane' => 'Assigner une catégorie lorsque la tâche est déplacée dans une swimlane spécifique', + 'Move the task to another swimlane when the category is changed' => 'Déplacer la tâche dans une autre swimlane lorsque la catégorie est modifiée', + 'Reorder this column by priority (ASC)' => 'Réorganiser cette colonne par priorité (ASC)', + 'Reorder this column by priority (DESC)' => 'Réorganiser cette colonne par priorité (DESC)', + 'Reorder this column by assignee and priority (ASC)' => 'Réorganiser cette colonne par assigné et par priorité (ASC)', + 'Reorder this column by assignee and priority (DESC)' => 'Réorganiser cette colonne par assigné et par priorité (DESC)', + 'Reorder this column by assignee (A-Z)' => 'Réorganiser cette colonne par assigné (A-Z)', + 'Reorder this column by assignee (Z-A)' => 'Réorganiser cette colonne par assigné (Z-A)', + 'Reorder this column by due date (ASC)' => 'Réorganiser cette colonne par date d\'échéance (ASC)', + 'Reorder this column by due date (DESC)' => 'Réorganiser cette colonne par date d\'échéance (DESC)', + 'Reorder this column by id (ASC)' => 'Réorganiser cette colonne par ID (ASC)', + 'Reorder this column by id (DESC)' => 'Réorganiser cette colonne par ID (DESC)', + '%s moved the task #%d "%s" to the project "%s"' => '%s a déplacé la tâche #%d « %s » vers le projet « %s »', + 'Task #%d "%s" has been moved to the project "%s"' => 'La tâche #%d « %s » a été déplacée vers le projet « %s »', + 'Move the task to another column when the due date is less than a certain number of days' => 'Déplacer la tâche dans une autre colonne lorsque la date d\'échéance est inférieure à un certain nombre de jour', + 'Automatically update the start date when the task is moved away from a specific column' => 'Mettre à jour automatiquement la date de début lorsque la tâche change de colonne', + 'HTTP Client:' => 'Client HTTP :', + 'Assigned' => 'Assigné', + 'Task limits apply to each swimlane individually' => 'Les limites de tâches s\'appliquent à chaque swimlane individuellement', + 'Column task limits apply to each swimlane individually' => 'Les limites de tâches par colonne s\'appliquent à chaque swimlane individuellement', + 'Column task limits are applied to each swimlane individually' => 'Les limites de tâches par colonne sont appliquées à chaque swimlane individuellement', + 'Column task limits are applied across swimlanes' => 'Les limites des tâches par colonne sont appliquées entre les swimlanes', + 'Task limit: ' => 'Limite de tâche : ', + 'Change to global tag' => 'Changer le libellé à global', + 'Do you really want to make the tag "%s" global?' => 'Voulez-vous vraiment rendre le libellé « %s » global ?', + 'Enable global tags for this project' => 'Activer les libellés globaux pour ce projet', + 'Group membership(s):' => 'Membre des groupes :', + '%s is a member of the following group(s): %s' => '%s est membre des groupes suivants : %s', + '%d/%d group(s) shown' => '%d/%d groupe(s) affiché(s)', + 'Subtask creation or modification' => 'Création ou modification d\'une sous-tâche', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Assigner la tâche à un utilisateur lorsque la tâche est déplacée dans une swimelane spécifique', + 'Comment' => 'Commentaire', + 'Collapse vertically' => 'Réduire verticalement', + 'Expand vertically' => 'Agrandir verticalement', + 'MXN - Mexican Peso' => 'MXN - Peso mexicain', + 'Estimated vs actual time per column' => 'Temps estimé vs temps réel par colonne', + 'HUF - Hungarian Forint' => 'HUF - Forint hongrois', + 'XBT - Bitcoin' => 'XBT - Bitcoin', + 'You must select a file to upload as your avatar!' => 'Vous devez sélectionner un fichier à télécharger pour votre avatar !', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'Le fichier que vous avez téléchargé n\'est pas une image valide ! (Seuls * .gif, * .jpg, * .jpeg et * .png sont autorisés !)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Changer automatiquement la date d\'échéance lorsque la tâche est déplacée dans une colonne spécifique', + 'No other projects found.' => 'Aucun autre projet trouvé.', + 'Tasks copied successfully.' => 'Tâches copiées avec succès.', + 'Unable to copy tasks.' => 'Impossible de copier les tâches.', + 'Theme' => 'Thème', + 'Theme:' => 'Thème :', + 'Light theme' => 'Thème clair', + 'Dark theme' => 'Thème sombre', + 'Automatic theme - Sync with system' => 'Thème automatique - Se synchronize avec le système d\'exploitation', + 'Application managers or more' => 'Gestionnaires de l\'application ou plus', + 'Administrators' => 'Administrateurs', + 'Visibility:' => 'Visibilité :', + 'Standard users' => 'Utilisateurs standards', + 'Visibility is required' => 'La visibilité est obligatoire', + 'The visibility should be an app role' => 'La visibilité doit être un rôle de l\'application', + 'Reply' => 'Répondre', + '%s wrote: ' => '%s a écrit : ', + 'Number of visible tasks in this column and swimlane' => 'Nombre de tâches visibles dans cette colonne et cette swimlane', + 'Number of tasks in this swimlane' => 'Nombre de tâches dans cette swimlane', + 'Unable to find another subtask in progress, you can close this window.' => 'Impossible de trouver une autre sous-tâche en cours, vous pouvez fermer cette fenêtre.', + 'This theme is invalid' => 'Ce thème est invalide', + 'This role is invalid' => 'Ce rôle est invalide', + 'This timezone is invalid' => 'Ce fuseau horaire est invalide', + 'This language is invalid' => 'Cette langue est invalide', + 'This URL is invalid' => 'Cette URL est invalide', + 'Date format invalid' => 'Format de date invalide', + 'Time format invalid' => 'Format de l\'heure invalide', + 'Invalid Mail transport' => 'Transport de courrier invalide', + 'Color invalid' => 'Couleur invalide', + 'This value must be greater or equal to %d' => 'Cette valeur doit être supérieure ou égale à %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Ajouter un BOM au début du fichier (requis pour Microsoft Excel)', + 'Just add these tag(s)' => 'Ajouter seulement ces libellés', + 'Remove internal link(s)' => 'Enlever le(s) lien(s) interne(s)', + 'Import tasks from another project' => 'Importer les tâches d\'un autre projet', + 'Select the project to copy tasks from' => 'Sélectionner le projet dont vous souhaitez copier les tâches', + 'The total maximum allowed attachments size is %sB.' => 'La taille totale maximale autorisée pour les pièces jointes est de %sB.', + 'Add attachments' => 'Ajouter des pièces jointes', + 'Task #%d "%s" is overdue' => 'La tâche n°%d « %s » est en retard', + 'Enable notifications by default for all new users' => 'Activer les notifications par défaut pour tous les nouveaux utilisateurs', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Assigner la tâche à son créateur pour des colonnes spécifiques si aucun responsable n\'est défini manuellement', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Assigner une tâche à l\'utilisateur connecté lors du changement de colonne vers la colonne spécifiée si aucun utilisateur n\'est assigné', +]; diff --git a/app/Locale/hr_HR/translations.php b/app/Locale/hr_HR/translations.php new file mode 100644 index 0000000..ee8c8de --- /dev/null +++ b/app/Locale/hr_HR/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => '.', + 'None' => 'Nema', + 'Edit' => 'Uredi', + 'Remove' => 'Obriši', + 'Yes' => 'Da', + 'No' => 'Ne', + 'cancel' => 'odustani', + 'or' => 'ili', + 'Yellow' => 'Žuta', + 'Blue' => 'Plava', + 'Green' => 'Zelena', + 'Purple' => 'Ljubičasta', + 'Red' => 'Crvena', + 'Orange' => 'Narančasta', + 'Grey' => 'Siva', + 'Brown' => 'Smeđa', + 'Deep Orange' => 'Tamno narančasta', + 'Dark Grey' => 'Tamno siva', + 'Pink' => 'Roza', + 'Teal' => 'Tirkizna', + 'Cyan' => 'Cyan', + 'Lime' => 'Limun zelena', + 'Light Green' => 'Svijetlo zelena', + 'Amber' => 'Jantar', + 'Save' => 'Snimi', + 'Login' => 'Prijava', + 'Official website:' => 'Službena Web stranica:', + 'Unassigned' => 'Nedodijeljen', + 'View this task' => 'Pogledaj zadatak', + 'Remove user' => 'Makni korisnika', + 'Do you really want to remove this user: "%s"?' => 'Doista želiš maknuti korisnika: "%s"?', + 'All users' => 'Svi korisnici', + 'Username' => 'Korisnik', + 'Password' => 'Lozinka', + 'Administrator' => 'Administrator', + 'Sign in' => 'Prijava', + 'Users' => 'Korisnik', + 'Forbidden' => 'Zabranjeno', + 'Access Forbidden' => 'Zabranjen prostup', + 'Edit user' => 'Promijeni korisnika', + 'Logout' => 'Odjava', + 'Bad username or password' => 'Neispravno korisničko ime ili lozinka', + 'Edit project' => 'Promijeni projekt', + 'Name' => 'Naziv', + 'Projects' => 'Projekti', + 'No project' => 'Bez projekta', + 'Project' => 'Projekt', + 'Status' => 'Status', + 'Tasks' => 'Zadatak', + 'Board' => 'Ploča', + 'Actions' => 'Radnje', + 'Inactive' => 'Neaktivan', + 'Active' => 'Aktivan', + 'Unable to update this board.' => 'Nije moguće promijeniti ovu ploču.', + 'Disable' => 'Onemogući', + 'Enable' => 'Omogući', + 'New project' => 'Novi projekt', + 'Do you really want to remove this project: "%s"?' => 'Doista želiš maknuti ovaj projekt: "%s"?', + 'Remove project' => 'Makni projekt', + 'Edit the board for "%s"' => 'Promijeni ploču za "%s"', + 'Add a new column' => 'Dodaj novi stupac', + 'Title' => 'Naslov', + 'Assigned to %s' => 'Dodijeljen korisniku %s', + 'Remove a column' => 'Makni stupac', + 'Unable to remove this column.' => 'Ne mogu maknuti stupac.', + 'Do you really want to remove this column: "%s"?' => 'Doista želiš maknuti ovaj stupac: "%s"?', + 'Settings' => 'Postavke', + 'Application settings' => 'Postavke aplikacije', + 'Language' => 'Jezik', + 'Webhook token:' => 'Token :', + 'API token:' => 'Token za API', + 'Database size:' => 'Veličina baze :', + 'Download the database' => 'Preuzmi bazu', + 'Optimize the database' => 'Optimiziraj bazu', + '(VACUUM command)' => '(naredba VACUUM)', + '(Gzip compressed Sqlite file)' => '(Sqlite baza zapakirana Gzip-om)', + 'Close a task' => 'Zatvori zadatak', + 'Column' => 'Stupac', + 'Color' => 'Boja', + 'Assignee' => 'Dodijeli', + 'Create another task' => 'Dodaj idući zadatak', + 'New task' => 'Novi zadatak', + 'Open a task' => 'Otvori zadatak', + 'Do you really want to open this task: "%s"?' => 'Da li zaista želiš otvoriti zadatak: "%s"?', + 'Back to the board' => 'Natrag na ploču', + 'There is nobody assigned' => 'Nitko nije dodijeljen!', + 'Column on the board:' => 'Stupac na ploči:', + '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 cijeli broj', + 'The username must be unique' => 'Korisničko ime mora biti jedinstveno', + 'The user id is required' => 'Oznaka korisnika je obavezna', + 'Passwords don\'t match' => 'Lozinke se ne podudaraju', + 'The confirmation is required' => 'Potvrda je obavezna', + 'The project is required' => 'Projekt je obavezan', + 'The id is required' => 'Oznaka je obavezna', + 'The project id is required' => 'Oznaka projekta je obavezna', + 'The project name is required' => 'Naziv projekta je obavezan', + 'The title is required' => 'Naslov je obavezan', + 'Settings saved successfully.' => 'Postavke su uspješno snimljene.', + 'Unable to save your settings.' => 'Nije moguće snimanje postavki.', + 'Database optimization done.' => 'Optimizacija baze je završena.', + 'Your project has been created successfully.' => 'Projekt je uspješno kreiran.', + 'Unable to create your project.' => 'Nije moguće kreiranje projekta.', + 'Project updated successfully.' => 'Projekt je uspješno promijenjen.', + 'Unable to update this project.' => 'Nije moguća dopuna projekta.', + 'Unable to remove this project.' => 'Nije moguće uklanjanje projekta.', + 'Project removed successfully.' => 'Projekt uspješno maknut.', + 'Project activated successfully.' => 'Projekt je uspješno aktiviran.', + 'Unable to activate this project.' => 'Nije moguće aktiviranje projekta.', + 'Project disabled successfully.' => 'Projekt je uspješno deaktiviran.', + 'Unable to disable this project.' => 'Nije moguće deaktiviranje projekta.', + 'Unable to open this task.' => 'Nije moguće otvaranje zadatka.', + 'Task opened successfully.' => 'Zadatak je uspješno otvoren.', + 'Unable to close this task.' => 'Nije moguće zatvaranje ovog zadatka.', + 'Task closed successfully.' => 'Zadatak je uspješno zatvoren.', + 'Unable to update your task.' => 'Nije moguća dopuna zadatka.', + 'Task updated successfully.' => 'Zadatak je uspješno dopunjen.', + 'Unable to create your task.' => 'Nije moguće kreiranje zadatka.', + 'Task created successfully.' => 'Zadatak je uspješno kreiran.', + 'User created successfully.' => 'Korisnik je uspješno kreiran', + 'Unable to create your user.' => 'Nije uspjelo kreiranje korisnika.', + 'User updated successfully.' => 'Korisnik je uspješno dopunjen.', + 'User removed successfully.' => 'Korisnik je uspješno maknut.', + 'Unable to remove this user.' => 'Nije moguće uklanjanje korisnika.', + 'Board updated successfully.' => 'Ploča uspješno dopunjena.', + 'Ready' => 'Spreman', + 'Backlog' => 'Nadolazeće', + 'Work in progress' => 'U radu', + 'Done' => 'Gotovo', + 'Application version:' => 'Verzija aplikacije:', + 'Id' => 'Rb', + 'Public link' => 'Javni link', + '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', + 'Task limit' => 'Ograničenje zadatka', + 'Task count' => 'Broj zadataka', + 'User' => 'Korisnik', + 'Comments' => 'Komentari', + 'Comment is required' => 'Komentar je obavezan', + 'Comment added successfully.' => 'Komentar je uspješno dodan', + 'Unable to create your comment.' => 'Nije moguće kreiranje komentara', + 'Due Date' => 'Rok', + 'Invalid date' => 'Neispravan datum', + 'Automatic actions' => 'Automatske radnje', + 'Your automatic action has been created successfully.' => 'Uspješno kreirana automatska radnja', + 'Unable to create your automatic action.' => 'Nije moguće kreiranje automatske radnje', + 'Remove an action' => 'Obriši radnju', + 'Unable to remove this action.' => 'Nije moguće obrisati radnju', + 'Action removed successfully.' => 'Radnja obrisana', + 'Automatic actions for the project "%s"' => 'Radnje za automatizaciju projekta "%s"', + 'Add an action' => 'Dodaj radnju', + 'Event name' => 'Naziv događaja', + 'Action' => 'Radnja', + 'Event' => 'Događaj', + 'When the selected event occurs execute the corresponding action.' => 'Kad se dogodi odabrani događaj izvrši odgovarajuću radnju', + 'Next step' => 'Idući korak', + 'Define action parameters' => 'Definiraj parametre radnje', + 'Do you really want to remove this action: "%s"?' => 'Želite obrisati radnju "%s"?', + 'Remove an automatic action' => 'Obriši automatsku radnju', + 'Assign the task to a specific user' => 'Dodijeli zadatak određenom korisniku', + 'Assign the task to the person who does the action' => 'Dodijeli zadatak korisniku koji je napravio radnju', + 'Duplicate the task to another project' => 'Kopiraj zadatak u drugi projekt', + 'Move a task to another column' => 'Premjesti zadatak u drugi stupac', + 'Task modification' => 'Izman zadatka', + 'Task creation' => 'Kreiranje zadatka', + 'Closing a task' => 'Zatvaranja zadatka', + 'Assign a color to a specific user' => 'Dodijeli boju korisniku', + 'Position' => 'Pozicija', + 'Duplicate to project' => 'Kopiraj u drugi projekt', + 'Duplicate' => 'Napravi kopiju', + 'Link' => 'Poveznica', + 'Comment updated successfully.' => 'Komentar je uspješno dopunjen.', + 'Unable to update your comment.' => 'Nije moguće dopuniti komentar.', + 'Remove a comment' => 'Obriši komentar', + 'Comment removed successfully.' => 'Komentar je uspješno maknut.', + 'Unable to remove this comment.' => 'Nije uspjelo brisanje komentara.', + 'Do you really want to remove this comment?' => 'Da li da obrišem ovaj komentar?', + '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', + 'Unknown' => 'Nepoznat', + 'Last logins' => 'Zadnja prijava', + 'Login date' => 'Datum prijave', + 'Authentication method' => 'Metoda autorizacije', + 'IP address' => 'IP adresa', + 'User agent' => 'Browser', + 'Persistent connections' => 'Stalna konekcija', + 'No session.' => 'Nema sesije', + 'Expiration date' => 'Ističe', + 'Remember Me' => 'Zapamti me', + 'Creation date' => 'Datum kreiranja', + 'Everybody' => 'Svi', + 'Open' => 'Otvoreni', + 'Closed' => 'Zatvoreni', + 'Search' => 'Traži', + 'Nothing found.' => 'Ništa nije pronađeno', + 'Due date' => 'Rok', + 'Description' => 'Opis', + '%d comments' => '%d komentara', + '%d comment' => '%d komentar', + 'Email address invalid' => 'Pogrešan e-mail', + 'Your external account is not linked anymore to your profile.' => 'Vanjski korisnički račun više nije povezan sa vašim profilom.', + 'Unable to unlink your external account.' => 'Nije moguće maknuti vezu sa vanjskim korisničkim računom.', + 'External authentication failed' => 'Vanjska autorizacija nije uspjela', + 'Your external account is linked to your profile successfully.' => 'Vanjski korisnički račun je uspješno povezan sa vašim profilom.', + 'Email' => 'E-mail', + 'Task removed successfully.' => 'Zadatak uspješno maknut.', + 'Unable to remove this task.' => 'Nije moguće uklanjanje zadatka.', + 'Remove a task' => 'Makni 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 dodijeli boju po kategoriji', + 'Assign automatically a category based on a color' => 'Automatski dodijeli kategoriju po boji', + 'Task creation or modification' => 'Kreiranje ili promjena zadatka', + 'Category' => 'Kategorija', + 'Category:' => 'Kategorija:', + 'Categories' => 'Kategorije', + 'Your category has been created successfully.' => 'Uspješno kreirana kategorija.', + 'This category has been updated successfully.' => 'Kategorija uspješno dopunjena.', + 'Unable to update this category.' => 'Kategoriju nije moguće dopuniti.', + 'Remove a category' => 'Obriši kategoriju', + 'Category removed successfully.' => 'Kategorija je uspješno maknuta.', + 'Unable to remove this category.' => 'Nije moguće maknuti kategoriju.', + 'Category modification for the project "%s"' => 'Promjena kategorije za projekt "%s"', + 'Category Name' => 'Naziv kategorije', + 'Add a new category' => 'Dodaj novu kategoriju', + 'Do you really want to remove this category: "%s"?' => 'Doista želite maknuti kategoriju: "%s"?', + 'All categories' => 'Sve kategorije', + 'No category' => 'Bez kategorije', + 'The name is required' => 'Naziv je obavezan', + 'Remove a file' => 'Makni datoteku', + 'Unable to remove this file.' => 'Datoteku nije moguće maknuti.', + 'File removed successfully.' => 'Datoteka je uspješno maknuta.', + 'Attach a document' => 'Dodaj dokument', + 'Do you really want to remove this file: "%s"?' => 'Doista želite maknuti datoteku: "%s"?', + 'Attachments' => 'Prilozi', + 'Edit the task' => 'Promjena zadatka', + 'Add a comment' => 'Dodaj komentar', + 'Edit a comment' => 'Promjena komentara', + 'Summary' => 'Pregled', + 'Time tracking' => 'Praćenje vremena', + 'Estimate:' => 'Procjena:', + 'Spent:' => 'Potrošeno:', + 'Do you really want to remove this sub-task?' => 'Doista želite maknuti podzadatak?', + 'Remaining:' => 'Preostalo:', + 'hours' => 'sati', + 'estimated' => 'procjena', + 'Sub-Tasks' => 'Podzadaci', + 'Add a sub-task' => 'Dodaj podzadatak', + 'Original estimate' => 'Izvorna procjena', + 'Create another sub-task' => 'Dodaj novi podzadatak', + 'Time spent' => 'Potrošeno vremena', + 'Edit a sub-task' => 'Promijeni podzadatak', + 'Remove a sub-task' => 'Makni podzadatak', + 'The time must be a numeric value' => 'Vrijeme mora biti broj', + 'Todo' => 'Za rad', + 'In progress' => 'U radu', + 'Sub-task removed successfully.' => 'Podzadatak je uspješno maknut.', + 'Unable to remove this sub-task.' => 'Nije mouće maknuti ovaj podzadatak.', + 'Sub-task updated successfully.' => 'Podzadatak je uspješno dopunjen.', + 'Unable to update your sub-task.' => 'Nije moguće dopuniti ovaj podzadatak.', + 'Unable to create your sub-task.' => 'Nije moguće krerati ovaj podzadatak.', + 'Maximum size: ' => 'Maksimalna veličina: ', + 'Display another project' => 'Prikaži drugi projekt', + 'Created by %s' => 'Kreirao %s', + 'Tasks Export' => 'Izvoz zadataka', + 'Start Date' => 'Početni datum', + 'Execute' => 'Izvrši', + 'Task Id' => 'Identifikator Zadatka', + 'Creator' => 'Autor', + 'Modification date' => 'Datum promjene', + 'Completion date' => 'Datum kompletiranja', + 'Clone' => 'Iskopiraj', + 'Project cloned successfully.' => 'Projekt uspješno kopiran.', + 'Unable to clone this project.' => 'Nije moguće kopirati projekt.', + 'Enable email notifications' => 'Omogući obaveštenja e-mailom', + 'Task position:' => 'Pozicija zadatka:', + 'The task #%d has been opened.' => 'Zadatak #%d je otvoren.', + 'The task #%d has been closed.' => 'Zadatak #%d je zatvoren.', + 'Sub-task updated' => 'Podzadatak dopunjen', + 'Title:' => 'Naslov:', + 'Status:' => 'Status:', + 'Assignee:' => 'Dodijeljeno:', + 'Time tracking:' => 'Praćenje vremena: ', + 'New sub-task' => 'Novi Podzadatak', + 'New attachment added "%s"' => 'Novi prilog ubačen "%s"', + 'New comment posted by %s' => 'Novi komentar ostavio %s', + 'New comment' => 'Novi komentar', + 'Comment updated' => 'Komentar dopunjen', + 'New subtask' => 'Novi podzadatak', + 'I only want to receive notifications for these projects:' => 'Želim primati obavijesti samo za ove projekte:', + 'view the task on Kanboard' => 'Pregledaj zadatke', + 'Public access' => 'Javni pristup', + 'Disable public access' => 'Zabrani javni pristup', + 'Enable public access' => 'Dozvoli javni pristup', + 'Public access disabled' => 'Javni pristup onemogućen!', + 'Move the task to another project' => 'Premjesti zadatak u drugi projekt', + 'Move to project' => 'Premjesti u drugi projekt', + 'Do you really want to duplicate this task?' => 'Želite duplicirati ovaj zadatak?', + 'Duplicate a task' => 'Kopiraj zadatak', + 'External accounts' => 'Vanjski korisnički račun', + 'Account type' => 'Vrsta korisničkog računa', + 'Local' => 'Lokalno', + 'Remote' => 'Udaljeno', + 'Enabled' => 'Omogući', + 'Disabled' => 'Onemogući', + 'Login:' => 'Korisničko ime:', + 'Full Name:' => 'Ime i prezime:', + 'Email:' => 'E-mail:', + 'Notifications:' => 'Obavijesti:', + 'Notifications' => 'Obavijesti', + 'Account type:' => 'Vrsta korisničkog računa:', + 'Edit profile' => 'Promjena profila', + 'Change password' => 'Promjena lozinke', + 'Password modification' => 'Promjena lozinke', + 'External authentications' => 'Vanjske autorizacije', + 'Never connected.' => 'Bez spajanja.', + 'No external authentication enabled.' => 'Nisu omogućene vanjske autorizacije.', + 'Password modified successfully.' => 'Uspješna promjena lozinke.', + 'Unable to change the password.' => 'Nije moguće promijeniti lozinku.', + 'Change category' => 'Promijeni kategoriju', + '%s updated the task %s' => '%s dopunio zadatak %s', + '%s opened the task %s' => '%s otvorio zadatak %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s pomaknuo zadatak %s na poziciju #%d u stupcu "%s"', + '%s moved the task %s to the column "%s"' => '%s pomaknuo zadatak %s u stupac "%s"', + '%s created the task %s' => '%s je kreirao zadatak %s', + '%s closed the task %s' => '%s je zatvorio zadatak %s', + '%s created a subtask for the task %s' => '%s je kreirao podzadatak zadatka %s', + '%s updated a subtask for the task %s' => '%s je dopunio podzadatak zadatka %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Dodijeljen korisniku %s uz procjenu vremena %s/%sh', + 'Not assigned, estimate of %sh' => 'Nije dodijeljen, procijenjeno vrijeme %sh', + '%s updated a comment on the task %s' => '%s je dopunio komentar zadatka %s', + '%s commented the task %s' => '%s je komentirao zadatak %s', + '%s\'s activity' => 'Aktivnosti %s', + 'RSS feed' => 'RSS kanal', + '%s updated a comment on the task #%d' => '%s je dopunio komentar zadatka #%d', + '%s commented on the task #%d' => '%s je komentirao zadatak #%d', + '%s updated a subtask for the task #%d' => '%s je dopunio podzadatak zadatka #%d', + '%s created a subtask for the task #%d' => '%s je kreirao podzadatak zadatka #%d', + '%s updated the task #%d' => '%s je dopunio zadatak #%d', + '%s created the task #%d' => '%s je kreirao zadatak #%d', + '%s closed the task #%d' => '%s je zatvorio zadatak #%d', + '%s opened the task #%d' => '%s je otvorio zadatak #%d', + 'Activity' => 'Aktivnosti', + 'Default values are "%s"' => 'Inicijalne vrijednosti su: "%s"', + 'Default columns for new projects (Comma-separated)' => 'Inicijalni stupci za novi projekt (odvojeni zarezom)', + 'Task assignee change' => 'Promjena dodjele zadatka', + '%s changed the assignee of the task #%d to %s' => '%s promijenio kome je dodijeljen zadatak #%d na %s', + '%s changed the assignee of the task %s to %s' => '%s promijenio kome je dodijeljen zadatak %s na %s', + 'New password for the user "%s"' => 'Nova lozinka za korisnika "%s"', + 'Choose an event' => 'Odaberi događaj', + 'Create a task from an external provider' => 'Kreiraj zadatak iz vanjskog izvora', + 'Change the assignee based on an external username' => 'Dodijeli zadatak ovisno o vanjskom imenu korisnika', + 'Change the category based on an external label' => 'Dodijeli zadatak ovisno o vanjskoj oznaci', + 'Reference' => 'Referenca', + 'Label' => 'Oznaka', + 'Database' => 'Baza podataka', + 'About' => 'O programu', + 'Database driver:' => 'Upravljački program baze podataka:', + 'Board settings' => 'Podešavanje ploče', + 'Webhook settings' => 'Webhook postavke', + 'Reset token' => 'Resetiraj token', + 'API endpoint:' => 'API pristupna točka:', + 'Refresh interval for personal board' => 'Osvježavanje privatnih ploča', + 'Refresh interval for public board' => 'Osvježavanje javnih ploča', + 'Task highlight period' => 'Koliko dugo je zadatak istaknut', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Vrijeme (u sekundama) koliko se zadatak smatra nedavno mijenjanim (0 isključi, inicijalno 2 dana)', + 'Frequency in second (60 seconds by default)' => 'Učestalost u sekundama (inicijalno 60 sekundi)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Učestalost u sekundama (inicijalno 10 sekundi)', + 'Application URL' => 'URL adresa aplikacije', + 'Token regenerated.' => 'Token wygenerowany ponownie.', + 'Date format' => 'Oblik datuma', + 'ISO format is always accepted, example: "%s" and "%s"' => 'Format ISO je uvijek prihvatljiv, npr: "%s", "%s"', + 'New personal project' => 'Novi privatni projekt', + 'This project is personal' => 'Ovo je privatni projekt', + 'Add' => 'Dodaj', + 'Start date' => 'Datum početka', + 'Time estimated' => 'Procjena vremena', + 'There is nothing assigned to you.' => 'Ništa vam nije dodijeljeno', + 'My tasks' => 'Moji zadaci', + 'Activity stream' => 'Tijek aktivnosti', + 'Dashboard' => 'Nadzorna ploča', + 'Confirmation' => 'Potvrda', + 'Webhooks' => 'Webhook-ovi', + 'API' => 'API', + 'Create a comment from an external provider' => 'Kreirajte komentar za vanjskog pružatelja', + 'Project management' => 'Uređivanje projekata', + 'Columns' => 'Stupci', + 'Task' => 'Zadaci', + 'Percentage' => 'Postotak', + 'Number of tasks' => 'Broj zadataka', + 'Task distribution' => 'Podjela zadataka', + 'Analytics' => 'Analiza', + 'Subtask' => 'Podzadatak', + 'User repartition' => 'Zaduženja korisnika', + 'Clone this project' => 'Kopiraj projekt', + 'Column removed successfully.' => 'Stupac je uspješno maknut.', + 'Not enough data to show the graph.' => 'Nedovoljno podataka za grafikon.', + 'Previous' => 'Prethodni', + 'The id must be an integer' => 'RB mora biti cijeli broj', + 'The project id must be an integer' => 'RB projekta mora biti cijeli broj', + 'The status must be an integer' => 'Status mora biti cijeli broj', + 'The subtask id is required' => 'RB podzadatka je obavezan', + 'The subtask id must be an integer' => 'RB podzadatka mora biti cijeli broj', + 'The task id is required' => 'RB zadatka je obavezan', + 'The task id must be an integer' => 'RB zadatka mora biti cijeli broj', + 'The user id must be an integer' => 'RB korisnika mora biti cijeli broj', + 'This value is required' => 'Ova vrijednost je obavezna', + 'This value must be numeric' => 'Ova vrijednost mora biti broj', + 'Unable to create this task.' => 'Nije moguće kreirati zadatak.', + 'Cumulative flow diagram' => 'Kumulativni dijagram toka', + 'Daily project summary' => 'Dnevni sažetak projekta', + 'Daily project summary export' => 'Izvoz dnevnog sažetka projekta', + 'Exports' => 'Izvoz', + 'This export contains the number of tasks per column grouped per day.' => 'Ovaj izvoz sadrži broj zadataka po stupcu grupirano po danima.', + 'Active swimlanes' => 'Aktivne staze', + 'Add a new swimlane' => 'Dodaj novu stazu', + 'Default swimlane' => 'Inicijalna staza', + 'Do you really want to remove this swimlane: "%s"?' => 'Želite maknuti ovu stazu: "%s"?', + 'Inactive swimlanes' => 'Neaktivne staze', + 'Remove a swimlane' => 'Makni stazu', + 'Swimlane modification for the project "%s"' => 'Promjena staza za projekt "%s"', + 'Swimlane removed successfully.' => 'Staza uspješno maknuta.', + 'Swimlanes' => 'Staze', + 'Swimlane updated successfully.' => 'Staza je uspješno dopunjena.', + 'Unable to remove this swimlane.' => 'Nije moguće maknuti ovu stazu', + 'Unable to update this swimlane.' => 'Nije moguća dopuna ove staze', + 'Your swimlane has been created successfully.' => 'Vaša staza je uspješno kreirana.', + 'Example: "Bug, Feature Request, Improvement"' => 'Npr: "Greška, Zahtjev za promjenom, Poboljšanje"', + 'Default categories for new projects (Comma-separated)' => 'Inicijalne kategorije projekta', + 'Integrations' => 'Integracije', + 'Integration with third-party services' => 'Integracija sa uslugama vanjskih servisa', + 'Subtask Id' => 'RB podzadatka', + 'Subtasks' => 'Podzadaci', + 'Subtasks Export' => 'Izvoz podzadataka', + 'Task Title' => 'Naslov zadatka', + 'Untitled' => 'Bez naslova', + 'Application default' => 'Inicijalne postavke aplikacje', + 'Language:' => 'Jezik:', + 'Timezone:' => 'Vremenska zona:', + 'All columns' => 'Svi stupci', + 'Next' => 'Idući', + '#%d' => '#%d', + 'All swimlanes' => 'Sve staze', + 'All colors' => 'Sve boje', + 'Moved to column %s' => 'Pomaknut u stupac %s', + 'User dashboard' => 'Korisnička nadzorna ploča', + 'Allow only one subtask in progress at the same time for a user' => 'Dozvoli samo jedan podzadatak "u tijeku" po korisniku', + 'Edit column "%s"' => 'Uredi stupac "%s"', + 'Select the new status of the subtask: "%s"' => 'Izaberi novi status za podzadatak: "%s"', + 'Subtask timesheet' => 'Tabela vremena za podzadatke', + 'There is nothing to show.' => 'Nema podataka', + 'Time Tracking' => 'Praćenje vremena', + 'You already have one subtask in progress' => 'Već imate jedan podzadatak "u radu"', + 'Which parts of the project do you want to duplicate?' => 'Koje dijelove projekta želite kopirati?', + 'Disallow login form' => 'Zabrani formu prijave', + 'Start' => 'Početak', + 'End' => 'Kraj', + 'Task age in days' => 'Starost zadatka u danima', + 'Days in this column' => 'Dana u ovom stupcu', + '%dd' => '%dd', + 'Add a new link' => 'Dodaj novu vezu', + 'Do you really want to remove this link: "%s"?' => 'Doista želite maknuti ovu vezu: "%s"?', + 'Do you really want to remove this link with task #%d?' => 'Doista želite maknuti ovu vezu sa zadatkom #%d?', + 'Field required' => 'Polje je obavezno', + 'Link added successfully.' => 'Veza je uspješno dodana.', + 'Link updated successfully.' => 'Veza je uspješno dopunjena.', + 'Link removed successfully.' => 'Veza je uspješno maknuta.', + 'Link labels' => 'Oznake poveznica', + 'Link modification' => 'Promjena veze', + 'Opposite label' => 'Suprotna oznaka', + 'Remove a link' => 'Makni vezu', + 'The labels must be different' => 'Oznake moraju biti različite', + 'There is no link.' => 'Ne postoji veza', + 'This label must be unique' => 'Ova oznaka mora biti jedinstvena', + 'Unable to create your link.' => 'Nije moguće kreirati vezu.', + 'Unable to update your link.' => 'Nije moguće dopuniti vezu.', + 'Unable to remove this link.' => 'Nije moguće maknuti vezu.', + 'relates to' => 'povezan sa', + 'blocks' => 'blokira', + 'is blocked by' => 'je blokiran od', + 'duplicates' => 'duplicira', + 'is duplicated by' => 'je duplikat od', + 'is a child of' => 'je dijete od', + 'is a parent of' => 'je roditelj od', + 'targets milestone' => 'cilj ključne točke', + 'is a milestone of' => 'je ključna točka od', + 'fixes' => 'popravlja', + 'is fixed by' => 'je popravljen od', + 'This task' => 'Ovaj zadatak', + '<1h' => '<1s', + '%dh' => '%ds', + 'Expand tasks' => 'Proširi zadatke', + 'Collapse tasks' => 'Skupi zadatke', + 'Expand/collapse tasks' => 'Proširi/skupi zadatke', + 'Close dialog box' => 'Zatvori dijalog', + 'Submit a form' => 'Pošalji obrazac', + 'Board view' => 'Pregled ploče', + 'Keyboard shortcuts' => 'Prečice tipkovnice', + 'Open board switcher' => 'Otvori promjenu ploče', + 'Application' => 'Aplikacija', + 'Compact view' => 'Kompaktni prikaz', + 'Horizontal scrolling' => 'Horizontalno listanje', + 'Compact/wide view' => 'Kompaktni/široki pregled', + 'Currency' => 'Valuta', + 'Personal project' => 'Osobni projekt', + 'AUD - Australian Dollar' => 'AUD - Australski dolar', + 'CAD - Canadian Dollar' => 'CAD - Kanadski dolar', + 'CHF - Swiss Francs' => 'CHF - Švicarski franak', + 'Custom Stylesheet' => 'Prilagođeni CSS', + 'EUR - Euro' => 'EUR - Euro', + 'GBP - British Pound' => 'GBP - Britanska funta', + 'INR - Indian Rupee' => 'INR - Indijski rupi', + 'JPY - Japanese Yen' => 'JPY - Japanski yen', + 'NZD - New Zealand Dollar' => 'NZD - Novozelandski dolar', + 'PEN - Peruvian Sol' => 'PEN - Peruanski Sol', + 'RSD - Serbian dinar' => 'RSD - Srpski dinar', + 'CNY - Chinese Yuan' => 'CNY - Kineski jen', + 'USD - US Dollar' => 'USD - Američki dolar', + 'VES - Venezuelan Bolívar' => 'VES - Venecuelanski bolivar', + 'Destination column' => 'Odredišni stupac', + 'Move the task to another column when assigned to a user' => 'Pomakni zadatak u drugi stupac kada se dodijeli izvršitelju', + 'Move the task to another column when assignee is cleared' => 'Pomakni zadatak u drugi stupac kada se makne izvršitelj', + 'Source column' => 'Izvorni stupac', + 'Transitions' => 'Pomicanja', + 'Executer' => 'Izvršitelj', + 'Time spent in the column' => 'Vrijeme provedeno u stupcu', + 'Task transitions' => 'Pomicanja zadatka', + 'Task transitions export' => 'Izvezi prelaze zadataka', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Ovaj izvještaj sadrži sva pomicanja unutar stupaca za svaki zadatak sa datumom, korisnikom i potrošenim vremenom za svako pomicanje.', + 'Currency rates' => 'Tečajna lista', + 'Rate' => 'Tečaj', + 'Change reference currency' => 'Promijeni osnovnu valutu', + 'Reference currency' => 'Referentna valuta', + 'The currency rate has been added successfully.' => 'Tečaj je uspješno dodan.', + 'Unable to add this currency rate.' => 'Nije moguće dodati ovaj tečaj.', + 'Webhook URL' => 'Webhook URL', + '%s removed the assignee of the task %s' => '%s je maknuo/la izvršitelja zadatka %s', + 'Information' => 'Informacije', + 'Check two factor authentication code' => 'Provjerite kod za dvo-faktorsku autorizaciju', + 'The two factor authentication code is not valid.' => 'Kod za dvo-faktorsku autorizaciju nije ispravan.', + 'The two factor authentication code is valid.' => 'Kod za dvo-faktorsku autorizaciju je ispravan', + 'Code' => 'Kod', + 'Two factor authentication' => 'Dvo-faktorska autorizacija', + 'This QR code contains the key URI: ' => 'Ovaj QR kod sadrži ključni URL:', + 'Check my code' => 'Provjeri moj kod', + 'Secret key: ' => 'Tajni ključ: ', + 'Test your device' => 'Testiraj svoj uređaj', + 'Assign a color when the task is moved to a specific column' => 'Dodijeli boju kada je zadatak pomaknut u odabrani stupac', + '%s via Kanboard' => '%s pomoću Kanboard', + 'Burndown chart' => 'Preostalo posla / vrijeme', + 'This chart show the task complexity over the time (Work Remaining).' => 'Ovaj graf pokazuje kompleksnost zadataka tokom vremena (Preostalo posla)', + 'Screenshot taken %s' => 'Slika ekrana preuzeta %s', + 'Add a screenshot' => 'Dodaj sliku ekrana', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Uzmi sliku ekrana i pritisni CTRL+V ili ⌘+V za zalijepiti.', + 'Screenshot uploaded successfully.' => 'Slika ekrana uspješno dodana.', + 'SEK - Swedish Krona' => 'SEK - Švedska kruna', + 'Identifier' => 'Oznaka', + 'Disable two factor authentication' => 'Onemogućite dvo-faktorsku autorizaciju', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Doista želite onemogućiti dvo-faktorsku autorizaciju za ovog korisnika: "%s"?', + 'Edit link' => 'Uredi vezu', + 'Start to type task title...' => 'Počnite pisati naslov zadatka...', + 'A task cannot be linked to itself' => 'Zadatak ne može biti povezan sa samim sobom', + 'The exact same link already exists' => 'Identična veza već postoji', + 'Recurrent task is scheduled to be generated' => 'Ponavljajući zadatak je pripremljen za kreiranje', + 'Score' => 'Bodovi', + 'The identifier must be unique' => 'Identifikator mora biti jedinstven', + 'This linked task id doesn\'t exists' => 'Povezani RB zadatka ne postoji', + 'This value must be alphanumeric' => 'Ova vrijednost mora biti znakovno-brojčana', + 'Edit recurrence' => 'Promjena ponavljanja', + 'Generate recurrent task' => 'Kreiraj ponavljajući zadatak', + 'Trigger to generate recurrent task' => 'Okidač koji kreira ponavljajući zadatak', + 'Factor to calculate new due date' => 'Faktor za računanje novog roka završetka', + 'Timeframe to calculate new due date' => 'Vremenski okvir za računanje novog roka završetka', + 'Base date to calculate new due date' => 'Početni datum za računanje novog roka završetka', + 'Action date' => 'Datum radnje', + 'Base date to calculate new due date: ' => 'Početni datum za računanje novog roka završetka: ', + 'This task has created this child task: ' => 'Zadatak je kreirao ovaj zadatak-dijete: ', + 'Day(s)' => 'Dan/i', + 'Existing due date' => 'Postojeći rok završetka', + 'Factor to calculate new due date: ' => 'Faktor za računanje novog roka završetka: ', + 'Month(s)' => 'Mjesec(i)', + 'This task has been created by: ' => 'Ovaj zadatak je kreirao: ', + 'Recurrent task has been generated:' => 'Ponavljajući zadatak je kreirao:', + 'Timeframe to calculate new due date: ' => 'Vremenski okvir za računanje novog roka završetka:', + 'Trigger to generate recurrent task: ' => 'Okidač za kreiranje ponavljajućeg zadatka', + 'When task is closed' => 'Kada je zadatak zatvoren', + 'When task is moved from first column' => 'Kada je zadatak pomaknut iz prvog stupca', + 'When task is moved to last column' => 'Kada je zadatak pomaknut u poslednji stupac', + 'Year(s)' => 'Godina/e', + 'Project settings' => 'Postavke projekta', + 'Automatically update the start date' => 'Automatski dopuni početni datum', + 'iCal feed' => 'iCal kanal', + 'Preferences' => 'Postavke', + 'Security' => 'Sigurnost', + 'Two factor authentication disabled' => 'Dvo-faktorska autorizacija je onemogućena', + 'Two factor authentication enabled' => 'Dvo-faktorska autorizacija je omogućena', + 'Unable to update this user.' => 'Nije moguće dopuniti ovog korisnika', + 'There is no user management for personal projects.' => 'Nema upravljanja korisnicima kod osobnih projekata.', + 'User that will receive the email' => 'Korisnik koji će primiti e-mail', + 'Email subject' => 'Predmet e-mail-a', + 'Date' => 'Datum', + 'Add a comment log when moving the task between columns' => 'Dodaj komentar u dnevnik kada se zadatak pomakne iz jednog stupca u drugi', + 'Move the task to another column when the category is changed' => 'Pomakni zadatak u drugi stupac kada se promijeni kategorija', + 'Send a task by email to someone' => 'Pošalji zadatak nekome putem e-mail-a', + 'Reopen a task' => 'Ponovo otvori zadatak', + 'Notification' => 'Obavijesti', + '%s moved the task #%d to the first swimlane' => '%s je pomaknuo/la zadatak #%d u prvu stazu', + 'Swimlane' => 'Staza', + '%s moved the task %s to the first swimlane' => '%s je pomaknuo/la zadatak %s u prvu stazu', + '%s moved the task %s to the swimlane "%s"' => '%s je pomaknuo/la zadatak %s u stazu "%s"', + 'This report contains all subtasks information for the given date range.' => 'Ovaj izvještaj sadrži sve informacije o podzadacima u zadanom razdoblju', + 'This report contains all tasks information for the given date range.' => 'Ovaj izvještaj sadrži sve informacije o zadacima u zadanom razdoblju', + 'Project activities for %s' => 'Aktivnosti projekta za %s', + 'view the board on Kanboard' => 'Pregledaj ploču', + 'The task has been moved to the first swimlane' => 'Zadatak je pomaknut u prvu stazu', + 'The task has been moved to another swimlane:' => 'Zadatak je pomaknut u drugu stazu:', + 'New title: %s' => 'Novi naslov: %s', + 'The task is not assigned anymore' => 'Ovaj zadatak više nema izvršitelja', + 'New assignee: %s' => 'Novi izvršitelj: %s', + 'There is no category now' => 'Sada nema kategorije', + 'New category: %s' => 'Nova kategorija: %s', + 'New color: %s' => 'Nova boja: %s', + 'New complexity: %d' => 'Nova kompleksnost: %d', + 'The due date has been removed' => 'Rok završetka je maknut', + 'There is no description anymore' => 'Nema više opisa', + 'Recurrence settings has been modified' => 'Promjena podešenja za ponavljajuće zadatke', + 'Time spent changed: %sh' => 'Potrošeno vrijeme je promijenjeno: %sh', + 'Time estimated changed: %sh' => 'Procjena vremena je promijenjena: %sh', + 'The field "%s" has been updated' => 'Polje "%s" je dopunjeno', + 'The description has been modified:' => 'Promjena opisa:', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Doista želite zatvoriti zadatak "%s" i sve podzadatke?', + 'I want to receive notifications for:' => 'Želim primati obavijesti za:', + 'All tasks' => 'Sve zadatke', + 'Only for tasks assigned to me' => 'Samo zadatke koji su mi dodijeljeni', + 'Only for tasks created by me' => 'Samo zadatke koje sam kreirao', + 'Only for tasks created by me and tasks assigned to me' => 'Samo zadatke koji su mi dodijeljeni i koje sam kreirao', + '%%Y-%%m-%%d' => '%%d.%%m.%%Y', + 'Total for all columns' => 'Ukupno za sve stupce', + 'You need at least 2 days of data to show the chart.' => 'Da bi se prikazao grafikon trebaju postojati podaci barem dva dana unatrag.', + '<15m' => '<15m', + '<30m' => '<30m', + 'Stop timer' => 'Zaustavi timer', + 'Start timer' => 'Pokreni timer', + 'My activity stream' => 'Tijek mojih aktivnosti', + 'Search tasks' => 'Pretraživanje zadataka', + 'Reset filters' => 'Obriši filtere', + 'My tasks due tomorrow' => 'Moji zadaci sa rokom sutra', + 'Tasks due today' => 'Zadaci sa rokom danas', + 'Tasks due tomorrow' => 'Zadaci sa rokom sutra', + 'Tasks due yesterday' => 'Zadaci sa rokom jučer', + 'Closed tasks' => 'Zatvoreni zadaci', + 'Open tasks' => 'Otvoreni zadaci', + 'Not assigned' => 'Nije dodijeljeno', + 'View advanced search syntax' => 'Pogledaj naprednu sintaksu pretraživanja', + 'Overview' => 'Pregled', + 'Board/Calendar/List view' => 'Pregled Ploče/Kalendara/Liste', + 'Switch to the board view' => 'Prebacivanje na prikaz ploče', + 'Switch to the list view' => 'Prebacivanje na prikaz liste', + 'Go to the search/filter box' => 'Idi na polje za pretragu / filter', + 'There is no activity yet.' => 'Još uvijek nema aktivnosti.', + 'No tasks found.' => 'Zadaci nisu pronađeni.', + 'Keyboard shortcut: "%s"' => 'Prečica tipkovnice: "%s"', + 'List' => 'Lista', + 'Filter' => 'Filter', + 'Advanced search' => 'Napredno pretraživanje', + 'Example of query: ' => 'Primjer upita: ', + 'Search by project: ' => 'Pretraživanje po projektu: ', + 'Search by column: ' => 'Pretraživanje po stupcu: ', + 'Search by assignee: ' => 'Pretraživanje kome je dodijeljeno: ', + 'Search by color: ' => 'Pretraživanje po boji: ', + 'Search by category: ' => 'Pretraživanje po kategoriji: ', + 'Search by description: ' => 'Pretraživanje po opisu: ', + 'Search by due date: ' => 'Pretraživanje po datumskom roku: ', + 'Average time spent in each column' => 'Prosek potrošenog vremena u svakom stupcu', + 'Average time spent' => 'Prosjek potrošenog vremena', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Ovaj graf pokazuje prosjek potrošenog vremena u svakom stupcu za zadnjih %d zadataka.', + 'Average Lead and Cycle time' => 'Prosječno vrijeme realizacije i izvođenja', + 'Average lead time: ' => 'Prosječno vrijeme realizacije: ', + 'Average cycle time: ' => 'Prosječno vrijeme ciklusa: ', + 'Cycle Time' => 'Vrijeme ciklusa', + 'Lead Time' => 'Vrijeme realizacije', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Ova grafika prikazuje prosjek vremena realizacije i ciklusa za zadnjih %d zadataka tijekom vremena.', + 'Average time into each column' => 'Prosječno vrijeme u svakom stupcu', + 'Lead and cycle time' => 'Vrijeme realizacije i ciklusa', + 'Lead time: ' => 'Vrijeme realizacije: ', + 'Cycle time: ' => 'Vrijeme ciklusa: ', + 'Time spent in each column' => 'Potrošeno vrijeme u svakom stupcu', + 'The lead time is the duration between the task creation and the completion.' => 'Vrijeme realizacije je trajanje od otvaranja do završetka zadatka.', + 'The cycle time is the duration between the start date and the completion.' => 'Vrijeme ciklusa je trajanje od početka do završetka zadatka.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Ako zadatak nije završen, trenutno vrijeme se koristi umesto datuma završetka.', + 'Set the start date automatically' => 'Automatski postavi početno vrijeme', + 'Edit Authentication' => 'Uredi Autorizacije', + 'Remote user' => 'Udaljeni korisnik', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Za udaljenog korisnika lozinka se ne čuva u Kanboard bazi, npr: LDAP, Google i Github korisnički računi.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Ako je označeno polje "Zabrani formu prijave" unos pristupnih podataka u formi prijave biti će ignoriran.', + 'Default task color' => 'Inicijalna boja zadataka', + 'This feature does not work with all browsers.' => 'Ova funckionalnost ne radi na svim Web preglednicima.', + 'There is no destination project available.' => 'Nije dostupan niti jedan odredišni projekt.', + 'Trigger automatically subtask time tracking' => 'Automatsko pokretanje evidencije vremena za podzadatke', + 'Include closed tasks in the cumulative flow diagram' => 'Uključi zatvorene zadatke u kumulativni dijagram toka', + 'Current swimlane: %s' => 'Trenutna staza: %s', + 'Current column: %s' => 'Trenutni stupac: %s', + 'Current category: %s' => 'Trenutna kategorija: %s', + 'no category' => 'bez kategorije', + 'Current assignee: %s' => 'Trenutni izvršitelj: %s', + 'not assigned' => 'nije dodijeljeno', + 'Author:' => 'Autor:', + 'contributors' => 'suradnici', + 'License:' => 'Licenca:', + 'License' => 'Licenca', + 'Enter the text below' => 'Upišite tekst ispod', + 'Start date:' => 'Početno vrijeme:', + 'Due date:' => 'Rok:', + 'People who are project managers' => 'Osobe koji su manageri projekta', + 'People who are project members' => 'Osobe koje su članovi projekta', + 'NOK - Norwegian Krone' => 'NOK - Norveška kruna', + 'Show this column' => 'Prikaži ovaj stupac', + 'Hide this column' => 'Sakrij ovaj stupac', + 'End date' => 'Datum završetka', + 'Users overview' => 'Pregled korisnika', + 'Members' => 'Članovi', + 'Shared project' => 'Dijeljeni projekt', + 'Project managers' => 'Manageri projekta', + 'Projects list' => 'Lista projekata', + 'End date:' => 'Datum završetka:', + 'Change task color when using a specific task link' => 'Promijeni boju kada se koristi određena vrsta veze na zadatku', + 'Task link creation or modification' => 'Veza na zadatku je kreirana ili promijenjena', + 'Milestone' => 'Ključna točka', + 'Reset the search/filter box' => 'Resetiraj polje za pretragu/filtere', + 'Documentation' => 'Dokumentacija', + 'Author' => 'Autor', + 'Version' => 'Verzija', + 'Plugins' => 'Dodaci', + 'There is no plugin loaded.' => 'Nema učitanih dodataka.', + 'My notifications' => 'Moje obavijesti', + 'Custom filters' => 'Prilagođeni filteri', + 'Your custom filter has been created successfully.' => 'Prilagođeni filter je uspješno kreiran.', + 'Unable to create your custom filter.' => 'Nije moguće kreirati prilagođeni filter.', + 'Custom filter removed successfully.' => 'Prilagođeni filter je uspješno maknut.', + 'Unable to remove this custom filter.' => 'Nije moguće maknuti prilagođeni filter.', + 'Edit custom filter' => 'Uredi prilagođeni filter', + 'Your custom filter has been updated successfully.' => 'Prilagođeni filter je uspješno dopunjen.', + 'Unable to update custom filter.' => 'Nije moguće dopuniti prilagođeni filter', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Novi privitak na zadatku #%d: %s', + 'New comment on task #%d' => 'Novi komentar na zadatku #%d', + 'Comment updated on task #%d' => 'Dopunjen komentar na zadatku #%d', + 'New subtask on task #%d' => 'Novi podzadatak na zadatku #%d', + 'Subtask updated on task #%d' => 'Podzadatak dopunjen na zadatku #%d', + 'New task #%d: %s' => 'Novi zadatak #%d: %s', + 'Task updated #%d' => 'Zadatak dopunjen #%d', + 'Task #%d closed' => 'Zadatak #%d zatvoren', + 'Task #%d opened' => 'Zadatak #%d otvoren', + 'Column changed for task #%d' => 'Promijenjen stupac za zadatak #%d', + 'New position for task #%d' => 'Nova pozicija za zadatak #%d', + 'Swimlane changed for task #%d' => 'Staza izmjenjena za zadatak #%d', + 'Assignee changed on task #%d' => 'Izvršitelj je promijenjen na zadatku #%d', + '%d overdue tasks' => '%d zadataka kasni', + 'No notification.' => 'Nema novih obavijesti.', + 'Mark all as read' => 'Označi sve kao pročitano', + 'Mark as read' => 'Označi kao pročitano', + 'Total number of tasks in this column across all swimlanes' => 'Ukupan broj zadataka u ovom stupcu u svim stazama', + 'Collapse swimlane' => 'Skupi stazu', + 'Expand swimlane' => 'Proširi stazu', + 'Add a new filter' => 'Dodaj novi filter', + 'Share with all project members' => 'Podijeli sa svim članovima projekta', + 'Shared' => 'Podijeljeno', + 'Owner' => 'Vlasnik', + 'Unread notifications' => 'Nepročitane obavijesti', + 'Notification methods:' => 'Vrste obavijesti:', + 'Unable to read your file' => 'Nije moguće pročitati datoteku', + '%d task(s) have been imported successfully.' => '%d zadataka uspješno uvezeno.', + 'Nothing has been imported!' => 'Ništa nije uvezeno!', + 'Import users from CSV file' => 'Uvezi korisnike putem CSV datoteke', + '%d user(s) have been imported successfully.' => '%d korisnika je uspješno uvezeno.', + 'Comma' => 'Zarez', + 'Semi-colon' => 'Točka-zarez', + 'Tab' => 'Tab', + 'Vertical bar' => 'Vertikalna traka', + 'Double Quote' => 'Dvostruki navodnici', + 'Single Quote' => 'Jednostruki navodnici', + '%s attached a file to the task #%d' => '%s dodao/la je novu datoteku u zadatak %d', + 'There is no column or swimlane activated in your project!' => 'Nema stupca ili aktivne staze u vašem projektu!', + 'Append filter (instead of replacement)' => 'Dodaj filter (umesto zamjene postojećeg)', + 'Append/Replace' => 'Dodaj/Zamijeni', + 'Append' => 'Dodaj', + 'Replace' => 'Zamijeni', + 'Import' => 'Uvoz', + 'Change sorting' => 'Promijeni sortiranje', + 'Tasks Importation' => 'Uvoz zadataka', + 'Delimiter' => 'Graničnik', + 'Enclosure' => 'Prilog', + 'CSV File' => 'CSV datoteka', + 'Instructions' => 'Uputstva', + 'Your file must use the predefined CSV format' => 'Vaša datoteka mora koristiti predefinirani CSV format', + 'Your file must be encoded in UTF-8' => 'Vaša datoteka mora koristiti UTF-8 kodiranje', + 'The first row must be the header' => 'Prvi red mora biti zaglavlje', + 'Duplicates are not verified for you' => 'Duplikati se ne provjeravaju (morate sami)', + 'The due date must use the ISO format: YYYY-MM-DD' => 'Datum roka završetka mora biti u ISO formatu: YYYY-MM-DD', + 'Download CSV template' => 'Preuzmi CSV predložak', + 'No external integration registered.' => 'Nije zabilježena niti jedna vanjska integracija.', + 'Duplicates are not imported' => 'Duplikati nisu uveženi', + 'Usernames must be lowercase and unique' => 'Korisnička imena moraju biti napisana malim slovima i jedinstvena', + 'Passwords will be encrypted if present' => 'Lozinke (ako postoje) će biti kriptirane', + '%s attached a new file to the task %s' => '%s je dodao datoteku za zadatak %s', + 'Link type' => 'Tip veze', + 'Assign automatically a category based on a link' => 'Automatsko dodjeljivanje kategorije bazirano na vezi', + 'BAM - Konvertible Mark' => 'BAM - Konvertibilna marka', + 'Assignee Username' => 'Korisničko ime izvršitelja', + 'Assignee Name' => 'Ime izvršitelja', + 'Groups' => 'Grupe', + 'Members of %s' => 'Članovi u %s', + 'New group' => 'Nova grupa', + 'Group created successfully.' => 'Grupa uspješno kreirana.', + 'Unable to create your group.' => 'Nije moguće kreirati grupu.', + 'Edit group' => 'Uredi grupu', + 'Group updated successfully.' => 'Grupa uspješno dopunjena.', + 'Unable to update your group.' => 'Nije moguće dopuniti grupu', + 'Add group member to "%s"' => 'Dodaj člana u grupu "%s"', + 'Group member added successfully.' => 'Uspješno dodan član grupe.', + 'Unable to add group member.' => 'Nije moguće dodati člana grupi.', + 'Remove user from group "%s"' => 'Makni korisnika iz grupe "%s"', + 'User removed successfully from this group.' => 'Korisnik uspješno maknut iz grupe.', + 'Unable to remove this user from the group.' => 'Nije moguće maknuti korisnika iz grupe.', + 'Remove group' => 'Makni grupu', + 'Group removed successfully.' => 'Grupa je uspješno maknuta.', + 'Unable to remove this group.' => 'Nije moguće maknuti grupu.', + 'Project Permissions' => 'Prava na projektu', + 'Manager' => 'Manager', + 'Project Manager' => 'Manager projekta', + 'Project Member' => 'Član projekta', + 'Project Viewer' => 'Promatrač projekta', + 'Your account is locked for %d minutes' => 'Vaš korisnički račun je zaključan idućih %d minuta', + 'Invalid captcha' => 'Pogrešna captcha', + 'The name must be unique' => 'Ime mora biti jedinstveno', + 'View all groups' => 'Pregled svih grupa', + 'There is no user available.' => 'Nema dostupnih korisnika.', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Doista želite maknuti korisnika "%s" iz grupe "%s"?', + 'There is no group.' => 'Nema grupe', + 'Add group member' => 'Dodaj člana grupe', + 'Do you really want to remove this group: "%s"?' => 'Doista želite da maknuti ovu grupu: "%s"?', + 'There is no user in this group.' => 'Trenutno nema korisnika u grupi.', + 'Permissions' => 'Prava', + 'Allowed Users' => 'Dozvoljeni korisnici', + 'No specific user has been allowed.' => 'Nije dozvoljeno niti jednom korisniku.', + 'Role' => 'Uloga', + 'Enter user name...' => 'Upiši korisničko ime...', + 'Allowed Groups' => 'Dozvoljene grupe', + 'No group has been allowed.' => 'Nije dozvoljeno niti jednoj grupi.', + 'Group' => 'Grupa', + 'Group Name' => 'Ime grupe', + 'Enter group name...' => 'Upiši ime grupe...', + 'Role:' => 'Uloga:', + 'Project members' => 'Članovi projekta', + '%s mentioned you in the task #%d' => '%s vas je spomenuo/la u zadatku #%d', + '%s mentioned you in a comment on the task #%d' => '%s vas je spomenuo/la u komentaru zadatka #%d', + 'You were mentioned in the task #%d' => 'Spomenuti ste u zadatku #%d', + 'You were mentioned in a comment on the task #%d' => 'Spomenuti ste u komntaru od zadatka #%d', + 'Estimated hours: ' => 'Procijenjeno sati:', + 'Actual hours: ' => 'Stvarno sati:', + 'Hours Spent' => 'Potrošeno sati:', + 'Hours Estimated' => 'Sati procijenjeno', + 'Estimated Time' => 'Procijenjeno vrijeme', + 'Actual Time' => 'Stvarno Vrijeme', + 'Estimated vs actual time' => 'Procijenjeno / stvarno vrijeme', + 'RUB - Russian Ruble' => 'RUB - Ruska rublja', + 'Assign the task to the person who does the action when the column is changed' => 'Dodijeli zadatak osobi koja izvrši radnju promjene stupca', + 'Close a task in a specific column' => 'Zatvori zadatak u određenom stupcu', + 'Time-based One-time Password Algorithm' => 'Algoritam vremenski bazirane jednokratne lozinke', + 'Two-Factor Provider: ' => 'Izdavač dvofaktorske prijave:', + 'Disable two-factor authentication' => 'Onemogući dvofaktorsku autorizaciju', + 'Enable two-factor authentication' => 'Omogući dvofaktorsku autorizaciju', + 'There is no integration registered at the moment.' => 'Trenutno nije registrirana niti jedna integracija.', + 'Password Reset for Kanboard' => 'Promjena lozinke za Kanboard', + 'Forgot password?' => 'Zaboravili ste lozinku?', + 'Enable "Forget Password"' => 'Omogući opciju "Zaboravljena lozinka"', + 'Password Reset' => 'Promjena lozinke', + 'New password' => 'Nova lozinka', + 'Change Password' => 'Promijeni lozinku', + 'To reset your password click on this link:' => 'Za promjenu lozinke kliknite na ovaj link:', + 'Last Password Reset' => 'Zadnja promjena lozinke', + 'The password has never been reinitialized.' => 'Lozinka nikada nije bila mijenjana.', + 'Creation' => 'Napravljeno', + 'Expiration' => 'Ističe', + 'Password reset history' => 'Povijest promjena lozinke', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Svi zadaci stupca "%s" i staze "%s" su uspješno zatvoreni.', + 'Do you really want to close all tasks of this column?' => 'Doista želite zatvoriti sve zadatke ovog stupca?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d zadatak/a u stupcu "%s" i stazi "%s" će biti zatvoreno.', + 'Close all tasks in this column and this swimlane' => 'Zatvori sve zadatke ovog stupca', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Niti jedan dodatak nije registrirao metodu za obavijesti o projektu. U korisničkom profilu i dalje možete urediti pojedinačne obavijesti.', + 'My dashboard' => 'Moja nadzorna ploča', + 'My profile' => 'Moj profil', + 'Project owner: ' => 'Vlasnik projekta: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'Oznaka projekta je opcija i mora biti slovno-brojčana, npr. MOJPROJEKT1', + 'Project owner' => 'Vlasnik projekta', + 'Personal projects do not have users and groups management.' => 'Osobni projekti nemaju upravljanje korisnicima i grupama.', + 'There is no project member.' => 'Nema članova projekta.', + 'Priority' => 'Prioritet', + 'Task priority' => 'Prioritet zadataka', + 'General' => 'Općenito', + 'Dates' => 'Datumi', + 'Default priority' => 'Inicijalni prioritet', + 'Lowest priority' => 'Najmanji prioritet', + 'Highest priority' => 'Najveći prioritet', + 'Close a task when there is no activity' => 'Zatvori zadatak kada nema aktivnosti', + 'Duration in days' => 'Trajanje u danima', + 'Send email when there is no activity on a task' => 'Pošalji e-mail kada nema aktivnosti na zadatku', + 'Unable to fetch link information.' => 'Ne mogu dohvatiti informacije o vezi.', + 'Daily background job for tasks' => 'Dnevni poslovi u pozadini za zadatke', + 'Auto' => 'Automatski', + 'Related' => 'Povezan', + 'Attachment' => 'Privitak', + 'Web Link' => 'Web link', + 'External links' => 'Vanjske veze', + 'Add external link' => 'Dodaj vanjsku vezu', + 'Type' => 'Vrsta', + 'Dependency' => 'Zavisnost', + 'Add internal link' => 'Dodaj unutarnju vezu', + 'Add a new external link' => 'Dodaj novu vanjsku vezu', + 'Edit external link' => 'Uredi vanjsku vezu', + 'External link' => 'Vanjska veza', + 'Copy and paste your link here...' => 'Kopirajte i zalijepite ovdje vašu vezu...', + 'URL' => 'URL', + 'Internal links' => 'Unutarnje veze', + 'Assign to me' => 'Dodijeli meni', + 'Me' => 'Ja', + 'Do not duplicate anything' => 'Ništa ne dupliraj', + 'Projects management' => 'Uređivanje projekata', + 'Users management' => 'Upravljanje korisnicima', + 'Groups management' => 'Upravljanje grupama', + 'Create from another project' => 'Napravi iz drugog projekta', + 'open' => 'otvoren', + 'closed' => 'zatvoren', + 'Priority:' => 'Prioritet:', + 'Reference:' => 'Referenca:', + 'Complexity:' => 'Kompleksnost:', + 'Swimlane:' => 'Staza:', + 'Column:' => 'Stupac:', + 'Position:' => 'Pozicija:', + 'Creator:' => 'Napravio:', + 'Time estimated:' => 'Procjena vremena:', + '%s hours' => '%s sati', + 'Time spent:' => 'Potrošeno vrijeme:', + 'Created:' => 'Kreirano:', + 'Modified:' => 'Promjenjeno:', + 'Completed:' => 'Završeno:', + 'Started:' => 'Započeto:', + 'Moved:' => 'Pomaknuto:', + 'Task #%d' => 'Zadatak #%d', + 'Time format' => 'Oblik vremena', + 'Start date: ' => 'Početni datum:', + 'End date: ' => 'Datum završetka:', + 'New due date: ' => 'Novi rok završetka:', + 'Start date changed: ' => 'Početni datum promijenjen: ', + 'Disable personal projects' => 'Onemogući privatne projekte', + 'Do you really want to remove this custom filter: "%s"?' => 'Doista želite maknuti ovaj prilagođeni filter "%s"?', + 'Remove a custom filter' => 'Makni prilagođeni filter', + 'User activated successfully.' => 'Korisnik uspješno aktiviran.', + 'Unable to enable this user.' => 'Nije moguće aktivirati ovog korisnika.', + 'User disabled successfully.' => 'Korisnik uspješno deaktiviran.', + 'Unable to disable this user.' => 'Nije moguće deaktivirati ovog korisnika.', + 'All files have been uploaded successfully.' => 'Sve datoteke su uspješno poslane.', + 'The maximum allowed file size is %sB.' => 'Maksimalna dozvoljena veličina datoteke je %sB.', + 'Drag and drop your files here' => 'Povucite i ovdje spustite datoteke', + 'choose files' => 'izaberite datoteke', + 'View profile' => 'Pregledaj profil', + 'Two Factor' => 'Dva faktora', + 'Disable user' => 'Deaktiviraj korisnika', + 'Do you really want to disable this user: "%s"?' => 'Doista želite deaktivirati ovog korisnika: "%s"?', + 'Enable user' => 'Aktiviraj korisnika', + 'Do you really want to enable this user: "%s"?' => 'Doista želite aktivirati ovog korisnika: "%s"?', + 'Download' => 'Preuzimanje', + 'Uploaded: %s' => 'Poslano: %s', + 'Size: %s' => 'Veličina: %s', + 'Uploaded by %s' => 'Poslao %s', + 'Filename' => 'Ime datoteke', + 'Size' => 'Veličina', + 'Column created successfully.' => 'Stupac uspješno napravljen.', + 'Another column with the same name exists in the project' => 'Već postoji stupac sa istim imenom u ovom projektu.', + 'Default filters' => 'Inicijalni filteri', + 'Your board doesn\'t have any columns!' => 'Tvoja ploča nema niti jedan stupac!', + 'Change column position' => 'Promijeni poziciju stupca', + 'Switch to the project overview' => 'Prebaci na pregled projekta', + 'User filters' => 'Fliteri korisnika', + 'Category filters' => 'Fliteri kategorija', + 'Upload a file' => 'Pošalji datoteku', + 'View file' => 'Pogledaj datoteku', + 'Last activity' => 'Zadnja aktivnost', + 'Change subtask position' => 'Promijeni poziciju podzadatka', + 'This value must be greater than %d' => 'Ova vrijednost mora biti veća od %d', + 'Another swimlane with the same name exists in the project' => 'Već postoji staza sa istim imenom u ovom projektu', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Primjer: https://primjer.kanboard.org/ (koristi se za stvaranje apsolutnih URL adresa)', + 'Actions duplicated successfully.' => 'Radnje uspješno duplirane.', + 'Unable to duplicate actions.' => 'Nije moguće duplirati radnje.', + 'Add a new action' => 'Dodaj novu radnju', + 'Import from another project' => 'Uvezi iz drugog projekta', + 'There is no action at the moment.' => 'Trenutno nema radnji.', + 'Import actions from another project' => 'Uvezi radnje iz drugog projekta', + 'There is no available project.' => 'Trenutno nema dostupnih projekata.', + 'Local File' => 'Lokalna datoteka', + 'Configuration' => 'Uređivanje', + 'PHP version:' => 'Verzija PHP-a:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'Verzija operativnog sustava:', + 'Database version:' => 'Verzija baze podataka:', + 'Browser:' => 'Web preglednik:', + 'Task view' => 'Pregled zadataka', + 'Edit task' => 'Uredi zadatak', + 'Edit description' => 'Uredi opis', + 'New internal link' => 'Nova unutarnja veza', + 'Display list of keyboard shortcuts' => 'Prikaži listu prečica na tipkovnici', + 'Avatar' => 'Avatar', + 'Upload my avatar image' => 'Pošalji moju avatar sliku', + 'Remove my image' => 'Makni moju sliku', + 'The OAuth2 state parameter is invalid' => 'Parametar stanja OAuth2 nije ispravan', + 'User not found.' => 'Korisnik nije pronađen.', + 'Search in activity stream' => 'Pretraživanje u tijeku aktivnosti', + 'My activities' => 'Moje aktivnosti', + 'Activity until yesterday' => 'Aktivnosti do jučer', + 'Activity until today' => 'Aktivnosti do danas', + 'Search by creator: ' => 'Pretraživanje tko je kreirao: ', + 'Search by creation date: ' => 'Pretraživanje po datumu kreiranja: ', + 'Search by task status: ' => 'Pretraživanje po statusu zadatka: ', + 'Search by task title: ' => 'Pretraživanje po naslovu zadatka: ', + 'Activity stream search' => 'Pretraživanje tijeka aktivnosti', + 'Projects where "%s" is manager' => 'Projekti gde je "%s" manager', + 'Projects where "%s" is member' => 'Projekti gde je "%s" član', + 'Open tasks assigned to "%s"' => 'Otvoreni zadaci dodijeljeni "%s"', + 'Closed tasks assigned to "%s"' => 'Zatvoreni zadaci dodijeljeni "%s"', + 'Assign automatically a color based on a priority' => 'Automatski dodijeli boju ovisno o prioritetu', + 'Overdue tasks for the project(s) "%s"' => 'Zadaci koji kasne za projekt/e "%s"', + 'Upload files' => 'Pošalji datoteke', + 'Installed Plugins' => 'Instalirani dodaci', + 'Plugin Directory' => 'Folder dodataka', + 'Plugin installed successfully.' => 'Dodatak je uspješno instaliran.', + 'Plugin updated successfully.' => 'Dodatak je uspješno dopunjen.', + 'Plugin removed successfully.' => 'Dodatak uspješno maknut.', + 'Subtask converted to task successfully.' => 'Podzadatak uspješno pretvoren u zadatak.', + 'Unable to convert the subtask.' => 'Nije moguće pretvoriti podzadatak.', + 'Unable to extract plugin archive.' => 'Nije moguće raspakirati arhivu dodatka.', + 'Plugin not found.' => 'Dodatak nije pronađen.', + 'You don\'t have the permission to remove this plugin.' => 'Nemate pravo za micanje ovog dodatka.', + 'Unable to download plugin archive.' => 'Nije moguće preuzeti arhivu dodatka.', + 'Unable to write temporary file for plugin.' => 'Nije moguće zapisati privremenu datoteku za dodatak.', + 'Unable to open plugin archive.' => 'Nije moguće otvoriti arhivu dodatka.', + 'There is no file in the plugin archive.' => 'Nema datoteke u arhivi dodatka.', + 'Create tasks in bulk' => 'Masovno kreiranja zadataka', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Ovaj Kanboard nije namješten za instaliranje dodataka kroz korisničko sučelje.', + 'There is no plugin available.' => 'Nema dostupnih dodataka.', + 'Install' => 'Instaliraj', + 'Update' => 'Dopuni', + 'Up to date' => 'Ažurno', + 'Not available' => 'Nije dostupno', + 'Remove plugin' => 'Makni dodatak', + 'Do you really want to remove this plugin: "%s"?' => 'Doista želite maknuti ovaj dodatak: "%s"?', + 'Uninstall' => 'Deinstaliraj', + 'Listing' => 'Popis', + 'Metadata' => 'Meta-podaci', + 'Manage projects' => 'Uređivanje projekata', + 'Convert to task' => 'Pretvori u zadatak', + 'Convert sub-task to task' => 'Pretvori podzadatak u zadatak', + 'Do you really want to convert this sub-task to a task?' => 'Doista želite pretvoriti podzadatak u zadatak?', + 'My task title' => 'Naslov zadatka', + 'Enter one task by line.' => 'Upiši jedan zadatak u svakom redu.', + 'Number of failed login:' => 'Broj neuspješnih prijava:', + 'Account locked until:' => 'Korisnički račun zaključan do:', + 'Email settings' => 'E-mail postavke', + 'Email sender address' => 'E-mail adresa pošiljatelja', + 'Email transport' => 'Način slanja e-maila', + 'Webhook token' => 'Webhook token', + 'Project tags management' => 'Upravljanje oznakama projekta', + 'Tag created successfully.' => 'Oznaka uspješno kreirana.', + 'Unable to create this tag.' => 'Nije moguće kreirati oznaku.', + 'Tag updated successfully.' => 'Oznaka uspješno dopunjena.', + 'Unable to update this tag.' => 'Nije moguće dopuniti ovu oznaku.', + 'Tag removed successfully.' => 'Oznaka uspješno maknuta.', + 'Unable to remove this tag.' => 'Nije moguće maknuti ovu oznaku.', + 'Global tags management' => 'Globalno upravljanje oznakama.', + 'Tags' => 'Oznake', + 'Tags management' => 'Uređivanje oznaka', + 'Add new tag' => 'Dodaj novu oznaku', + 'Edit a tag' => 'Uređivanje oznake', + 'Project tags' => 'Oznake projekta', + 'There is no specific tag for this project at the moment.' => 'Trenutno nema oznaka za ovaj projekt.', + 'Tag' => 'Oznaka', + 'Remove a tag' => 'Brisanje oznake', + 'Do you really want to remove this tag: "%s"?' => 'Doista želite obrisati oznaku: "%s"?', + 'Global tags' => 'Globalne oznake', + 'There is no global tag at the moment.' => 'Trenutno nema globalno definiranih oznaka.', + 'This field cannot be empty' => 'Ovo polje ne može biti prazno', + 'Close a task when there is no activity in a specific column' => 'Zatvori zadatak kada nema aktivnosti u odabranom stupcu', + '%s removed a subtask for the task #%d' => '%s je maknuo podzadatak od zadataka #%d', + '%s removed a comment on the task #%d' => '%s je maknuo/la komentar na zadatku #%d', + 'Comment removed on task #%d' => 'Komentar je maknut na zadatku #%d', + 'Subtask removed on task #%d' => 'Podzadatak je maknut na zadatku #%d', + 'Hide tasks in this column in the dashboard' => 'Sakrijte zadatke u ovom stupcu na kontrolnoj ploči', + '%s removed a comment on the task %s' => '%s je maknuo/la komentar za zadatak %s', + '%s removed a subtask for the task %s' => '%s je maknuo podzadatak od zadataka #%s', + 'Comment removed' => 'Komentar maknut', + 'Subtask removed' => 'Podzadatak maknut', + '%s set a new internal link for the task #%d' => '%s je postavio/la novu unutarnju vezu na zadatak #%d', + '%s removed an internal link for the task #%d' => '%s je maknuo/la unutarnju vezu na zadatak #%d', + 'A new internal link for the task #%d has been defined' => 'Nova unutarnja veza na zadatak #%d je postavljena', + 'Internal link removed for the task #%d' => 'unutarnja veza je maknuta za zadatak #%d', + '%s set a new internal link for the task %s' => '%s je postavio/la novu unutarnju vezu za zadatak %s', + '%s removed an internal link for the task %s' => '%s je maknuo/la unutarnju vezu za zadatak %s', + 'Automatically set the due date on task creation' => 'Automatski dodaj rok završetka na kreiranom zadatku', + 'Move the task to another column when closed' => 'Makni zadatak u drugi stupac kada je zatvoren', + 'Move the task to another column when not moved during a given period' => 'Makni zadatak u drugi stupac kada ne bude pomaknut određeno vrijeme', + 'Dashboard for %s' => 'Nadzorna ploča za %s', + 'Tasks overview for %s' => 'Pregled zadataka za %s', + 'Subtasks overview for %s' => 'Pregled podzadataka za %s', + 'Projects overview for %s' => 'Pregled projekata za %s', + 'Activity stream for %s' => 'Tijek aktivnosti za %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Dodijeli boju kada je zadatak pomaknut u određenu stazu', + 'Assign a priority when the task is moved to a specific swimlane' => 'Dodijeli prioritet kada je zadatak pomaknut u određenu stazu', + 'User unlocked successfully.' => 'Korisnik je uspješno otključan.', + 'Unable to unlock the user.' => 'Nije moguće otključati korisnika.', + 'Move a task to another swimlane' => 'Pomakni zadatak u drugu stazu', + 'Creator Name' => 'Ime tvorca', + 'Time spent and estimated' => 'Potrošeno i procijenjeno vrijeme', + 'Move position' => 'Promjena pozicije', + 'Move task to another position on the board' => 'Pomakni zadatak na drugu poziciju na ploči', + 'Insert before this task' => 'Umetni prije ovog zadatka', + 'Insert after this task' => 'Umetni poslije ovog zadatka', + 'Unlock this user' => 'Otključaj ovog korisnika', + 'Custom Project Roles' => 'Prilagođne uloge projekta', + 'Add a new custom role' => 'Dodaj novu prilagođenu ulogu', + 'Restrictions for the role "%s"' => 'Ograničenja za ulogu "%s"', + 'Add a new project restriction' => 'Dodaj nova ograničenja na projektu', + 'Add a new drag and drop restriction' => 'Dodaj nova ograničenja za "povuci i spusti"', + 'Add a new column restriction' => 'Dodaj nova ograničenja za stupac', + 'Edit this role' => 'Uredi ovu ulogu', + 'Remove this role' => 'Makni ovu ulogu', + 'There is no restriction for this role.' => 'Nema ograničenja za ovu ulogu.', + 'Only moving task between those columns is permitted' => 'Dozvoljeno micanje zadataka samo među ovim stupcima', + 'Close a task in a specific column when not moved during a given period' => 'Zatvori zadatak u odabranom stupcu kada nema micanja određeno vrijeme', + 'Edit columns' => 'Uredi stupce', + 'The column restriction has been created successfully.' => 'Ograničenje za stupac je uspješno kreiranao.', + 'Unable to create this column restriction.' => 'Nije moguće kreirati ograničenja za stupac.', + 'Column restriction removed successfully.' => 'Ograničenje za stupac je uspješno obrisano.', + 'Unable to remove this restriction.' => 'Nije moguće maknuti ovo ograničenje.', + 'Your custom project role has been created successfully.' => 'Tvoja prilagođena uloga na projektu je uspješno kreirana.', + 'Unable to create custom project role.' => 'Nije moguće kreirati prilagođenu ulogu na projektu.', + 'Your custom project role has been updated successfully.' => 'Tvoja prilagođena uloga na projektu je uspješno dopunjena.', + 'Unable to update custom project role.' => 'Nije moguće dopuniti prilagođenu ulogu na projektu.', + 'Custom project role removed successfully.' => 'Prilagođena uloga na projektu je uspješno maknuta.', + 'Unable to remove this project role.' => 'Nije moguće maknuti ovu ulogu na projektu.', + 'The project restriction has been created successfully.' => 'Ograničenje na projektu je uspješno kreirano.', + 'Unable to create this project restriction.' => 'Nije moguće kreirati ovo ograničenje na projektu.', + 'Project restriction removed successfully.' => 'Ograničenje na projektu je uspješno maknuto.', + 'You cannot create tasks in this column.' => 'Ne možete kreirati zadatak u ovom stupcu.', + 'Task creation is permitted for this column' => 'Kreiranje zadataka u ovom stupcu je zabranjeno', + 'Closing or opening a task is permitted for this column' => 'Zatvaranje ili otvaranje zadatka u ovom stupcu je zabranjeno', + 'Task creation is blocked for this column' => 'Kreiranje zadataka je onemogućeno za ovaj stupac', + 'Closing or opening a task is blocked for this column' => 'Zatvaranje ili otvaranje zadataka u ovom stupcu je onemogućeno', + 'Task creation is not permitted' => 'Kreiranje zadatka nije dozvoljeno', + 'Closing or opening a task is not permitted' => 'Zatvarnaje ili otvaranje zadatka nije dozvoljeno', + 'New drag and drop restriction for the role "%s"' => 'Novo "povuci i spusti" ograničenje za ulogu "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Osobe sa ovom ulogom će moći samo micati zadatke između izvornog i odredišnog stupca.', + 'Remove a column restriction' => 'Makni ograničenja za stupac', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Doista želite maknuti ovo ograničenje za stupac: "%s" u "%s"?', + 'New column restriction for the role "%s"' => 'Novo ograničenje za stupac za ulogu "%s"', + 'Rule' => 'Pravilo', + 'Do you really want to remove this column restriction?' => 'Doista želite maknuti ovo ograničenje za stupac?', + 'Custom roles' => 'Prilagođene uloge', + 'New custom project role' => 'Nova prilagođena uloga za projekt', + 'Edit custom project role' => 'Uredi prilagođenu ulogu za projekt', + 'Remove a custom role' => 'Makni prilagođenu ulogu', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Doista želite maknuti ovu prilagođenu ulogu: "%s"? Sve osobe kojima je dodijeljena ova uloga će postati članovi projekta.', + 'There is no custom role for this project.' => 'Nema prilagođenih uloga za ovaj projekt.', + 'New project restriction for the role "%s"' => 'Novo ograničenje na projektu za ulogu "%s"', + 'Restriction' => 'Ograničenje', + 'Remove a project restriction' => 'Makni ograničenje na projektu', + 'Do you really want to remove this project restriction: "%s"?' => 'Doista želite da maknuti ovo ograničenje na projektu: "%s"?', + 'Duplicate to multiple projects' => 'Dupliraj u više projekata', + 'This field is required' => 'Ovo polje je obavezno', + 'Moving a task is not permitted' => 'Pomicanje zadatka nije dozvoljeno', + 'This value must be in the range %d to %d' => 'Ova vrijednost mora biti u rasponu od %d do %d', + 'You are not allowed to move this task.' => 'Nemate dozvolu za pomicanje ovog zadatka.', + 'API User Access' => 'Pristup korisnika putem API', + 'Preview' => 'Pregled', + 'Write' => 'Upiši', + 'Write your text in Markdown' => 'Upiši tekst u Markdown notaciji', + 'No personal API access token registered.' => 'Nije zabilježen niti jedan pristupni token.', + 'Your personal API access token is "%s"' => 'Vaš osobni API token za pristup je: "%s"', + 'Remove your token' => 'Makni vaš token', + 'Generate a new token' => 'Kreiraj novi token', + 'Showing %d-%d of %d' => 'Prikaži %d-%d od %d', + 'Outgoing Emails' => 'Odlazne e-mail poruke', + 'Add or change currency rate' => 'Dodaj ili promijeni tečaj', + 'Reference currency: %s' => 'Osnovna valuta: %s', + 'Add custom filters' => 'Dodaj prilagođene filtere', + 'Export' => 'Izvezi', + 'Add link label' => 'Dodaj oznaku poveznice', + 'Incompatible Plugins' => 'Nekompatibilni dodaci', + 'Compatibility' => 'Kompatibilnost', + 'Permissions and ownership' => 'Dozvole i vlasništvo', + 'Priorities' => 'Prioriteti', + 'Close this window' => 'Zatvori ovaj prozor', + 'Unable to upload this file.' => 'Nije moguće poslati ovu datoteku.', + 'Import tasks' => 'Uvezi zadatke', + 'Choose a project' => 'Odaberite projekt', + 'Profile' => 'Profil', + 'Application role' => 'Uloga u aplikaciji', + '%d invitations were sent.' => '%d poslano pozivnica.', + '%d invitation was sent.' => '%d pozivnica poslana.', + 'Unable to create this user.' => 'Nije moguće kreirati ovog korisnika.', + 'Kanboard Invitation' => 'Kanboard pozivnica', + 'Visible on dashboard' => 'Vidljivo na upravljačkoj ploči', + 'Created at:' => 'Kreirano:', + 'Updated at:' => 'Dopunjeno:', + 'There is no custom filter.' => 'Nema prilagođenih filtera.', + 'New User' => 'Novi korisnik', + 'Authentication' => 'Autorizacija', + 'If checked, this user will use a third-party system for authentication.' => 'Ako je označeno, ovaj korisnik će koristiti vanjski sistem za autorizaciju.', + 'The password is necessary only for local users.' => 'Lozinka je obavezna samo za lokalne korisnike.', + 'You have been invited to register on Kanboard.' => 'Pozvani ste da se registrirate u Kanboard.', + 'Click here to join your team' => 'Kliknite ovdje kako bi se prodružili vašem timu', + 'Invite people' => 'Pozovi ljude', + 'Emails' => 'E-mail-ovi', + 'Enter one email address by line.' => 'Upiši jednu email adresu u redu.', + 'Add these people to this project' => 'Dodaj ove ljude u ovaj projekt', + 'Add this person to this project' => 'Dodaj ovu osobu u ovaj projekt', + 'Sign-up' => 'Registracija', + 'Credentials' => 'Pristupni podaci', + 'New user' => 'Novi korisnik', + 'This username is already taken' => 'Ovo korisničko ime je zauzeto', + 'Your profile must have a valid email address.' => 'Profil mora imati ispravnu email adresu.', + 'TRL - Turkish Lira' => 'TRL - Turska lira', + 'The project email is optional and could be used by several plugins.' => 'e-mail projekta je opcija, a mogi je koristiti i dodaci (plugins).', + 'The project email must be unique across all projects' => 'e-mail projekta mora biti jedinstven za svaki projekt', + 'The email configuration has been disabled by the administrator.' => 'Uređivanje e-mail postavki je onemogućeno od strane administratora.', + 'Close this project' => 'Zatvori ovaj projekt', + 'Open this project' => 'Otvori ovaj projekt', + 'Close a project' => 'Zatvori projekt', + 'Do you really want to close this project: "%s"?' => 'Doista želite zatvoriti projekt: "%s"?', + 'Reopen a project' => 'Ponovo otvori projekt', + 'Do you really want to reopen this project: "%s"?' => 'Doista želite ponovo otvoriti projekt: "%s"?', + 'This project is open' => 'Ovaj projekt je otvoren', + 'This project is closed' => 'Ovaj projekt je zatvoren', + 'Unable to upload files, check the permissions of your data folder.' => 'Nije moguće poslati datoteke, provjerite prava na server folderima.', + 'Another category with the same name exists in this project' => 'Kategorija sa istim imenom već postoji na ovom projektu', + 'Comment sent by email successfully.' => 'Komentar je uspješno poslan e-mail-om.', + 'Sent by email to "%s" (%s)' => 'Poslano na e-mail "%s" (%s)', + 'Unable to read uploaded file.' => 'Nije moguće pročitati poslanu datoteku.', + 'Database uploaded successfully.' => 'Baza podataka je uspješno poslana.', + 'Task sent by email successfully.' => 'Zadatak je uspješno poslan e-mail-om.', + 'There is no category in this project.' => 'Nema kategorija u ovom projektu.', + 'Send by email' => 'Pošalji e-mail-om', + 'Create and send a comment by email' => 'Napravi i pošalji komentar e-mail-om', + 'Subject' => 'Predmet', + 'Upload the database' => 'Pošalji bazu podataka', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Možete poslati prethodno preuzetu Sqlite bazu podataka (Gzip format).', + 'Database file' => 'Datoteka baze podataka', + 'Upload' => 'Pošalji', + 'Your project must have at least one active swimlane.' => 'Projekt mora imati barem jednu aktivnu stazu.', + 'Project: %s' => 'Projekt: %s', + 'Automatic action not found: "%s"' => 'Automatska radnja nije pronađena: "%s"', + '%d projects' => '%d projekta', + '%d project' => '%d projekt', + 'There is no project.' => 'Nema projekata.', + 'Sort' => 'Sortiraj', + 'Project ID' => 'RB projekta', + 'Project name' => 'Naziv projekta', + 'Public' => 'Javno', + 'Personal' => 'Osobno', + '%d tasks' => '%d zadataka', + '%d task' => '%d zadatak', + 'Task ID' => 'RB zadatka', + 'Assign automatically a color when due date is expired' => 'Automatski postavi boju kada prošao rok završetka', + 'Total score in this column across all swimlanes' => 'Ukupni rezultat u ovom stupcu za sve staze', + 'HRK - Kuna' => 'HRK - Hrvatska Kuna', + 'ARS - Argentine Peso' => 'ARS - Argentiski pezos', + 'COP - Colombian Peso' => 'COP - Kolumbijski pezos', + '%d groups' => '%d grupe', + '%d group' => '%d grupa', + 'Group ID' => 'RB grupe', + 'External ID' => 'Vanjski RB', + '%d users' => '%d korisnika', + '%d user' => '%d korisnik', + 'Hide subtasks' => 'Sakrij podzadatke', + 'Show subtasks' => 'Prikaži podzadatke', + 'Authentication Parameters' => 'Parametri za autorizaciju', + 'API Access' => 'API pristup', + 'No users found.' => 'Nema ponađenih korisnika.', + 'User ID' => 'RB korisnika', + 'Notifications are activated' => 'Obavijesti su omogućene', + 'Notifications are disabled' => 'Obavijesti su onemogućene', + 'User disabled' => 'Korisnik je onemogućen', + '%d notifications' => '%d obavjesti', + '%d notification' => '%d obavijest', + 'There is no external integration installed.' => 'Nema usluga vanjskih servisa.', + 'You are not allowed to update tasks assigned to someone else.' => 'Nemate dozvolu mijenjati zadatke dodijeljene nekom drugom.', + 'You are not allowed to change the assignee.' => 'Nemate dozvolu mijenjati izvršitelja.', + 'Task suppression is not permitted' => 'Suzbijanje zadataka nije dozvoljeno', + 'Changing assignee is not permitted' => 'Promjena izvršitelja nije dozvoljena', + 'Update only assigned tasks is permitted' => 'Dozvoljena je promjena samo dodijeljenih zadataka', + 'Only for tasks assigned to the current user' => 'Samo za zadatke dodijeljene trenutnom korisniku', + 'My projects' => 'Moji projekti', + 'You are not a member of any project.' => 'Niste član niti jednog projekta.', + 'My subtasks' => 'Moji podzadaci', + '%d subtasks' => '%d podzadataka', + '%d subtask' => '%d podzadatak', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Premještanje između tih stupaca je dozvoljeno samo za zadatke dodijeljene trenutnom korisniku', + '[DUPLICATE]' => '[DUPLIKAT]', + 'DKK - Danish Krona' => 'DKK - Danska kruna', + 'Remove user from group' => 'Makni korisnika iz grupe', + 'Assign the task to its creator' => 'Dodijeli zadatak njegovom tvorcu', + 'This task was sent by email to "%s" with subject "%s".' => 'Ovaj zadatak je poslan na email "%s" sa predmetom "%s".', + 'Predefined Email Subjects' => 'Unaprijed definirani e-mail predmeti', + 'Write one subject by line.' => 'Unesite jedan predmet po liniji', + 'Create another link' => 'Napravi još jednu vezu', + 'BRL - Brazilian Real' => 'BRL - Brazilski real', + 'Add a new Kanboard task' => 'Dodajte novi Kanboard zadatak', + 'Subtask not started' => 'Podzadatak nije započeo', + 'Subtask currently in progress' => 'Podzadatak je trenutno u tijeku', + 'Subtask completed' => 'Podzadatak je završen', + 'Subtask added successfully.' => 'Podzadatak je uspješno dodan.', + '%d subtasks added successfully.' => '%d podzadataka je uspješno dodano.', + 'Enter one subtask by line.' => 'Unesite jedan podzadatak u redu.', + 'Predefined Contents' => 'Unaprijed definirani sadržaj', + 'Predefined contents' => 'Unaprijed definirani sadržaj', + 'Predefined Task Description' => 'Predefinirani opis zadatka', + 'Do you really want to remove this template? "%s"' => 'Doista želite maknuti ovaj predložak? "%s"', + 'Add predefined task description' => 'Dodan predefinirani opis zadatka', + 'Predefined Task Descriptions' => 'Predefinirani opis zadatka', + 'Template created successfully.' => 'Predložak uspješno kreiran', + 'Unable to create this template.' => 'Nije moguće kreirati ovaj predložak', + 'Template updated successfully.' => 'Predložak uspješno dopunjen', + 'Unable to update this template.' => 'Nije moguće dopuniti ovaj predložak', + 'Template removed successfully.' => 'Predložak uspješno maknut', + 'Unable to remove this template.' => 'Nije moguće maknuti ovaj predložak', + 'Template for the task description' => 'Predložak za opis zadatka', + 'The start date is greater than the end date' => 'Početni datum je veći od kranjeg datuma', + 'Tags must be separated by a comma' => 'Oznake moraju biti razdvojene zarezom', + 'Only the task title is required' => 'Samo naslov zadatka je obavezan', + 'Creator Username' => 'Korisničko ime tvorca', + 'Color Name' => 'Boja stupca', + 'Column Name' => 'Naziv stupca', + 'Swimlane Name' => 'Naziv staze', + 'Time Estimated' => 'Procijenjeno vrijeme', + 'Time Spent' => 'Potrošeno vrijeme', + 'External Link' => 'Vanjska veza', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Ova funkcionalnost omogućava iCal kanal, RSS kanal i javni prikaz ploče.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Zaustavi timer na svim podzadacima prilikom premještanja zadatka u drugi stupac', + 'Subtask Title' => 'Naslov podzadatka', + 'Add a subtask and activate the timer when moving a task to another column' => 'Dodaj podzadatak i aktiviraj timer prilikom micanja zadatka u drugi stupac', + 'days' => 'dana', + 'minutes' => 'minuta', + 'seconds' => 'sekundi', + 'Assign automatically a color when preset start date is reached' => 'Automatski postavi boju kada se dosegne unaprijed zadani datum početka', + 'Move the task to another column once a predefined start date is reached' => 'Premjesti zadatak u drugi stupac kada se dosegne unaprijed zadani datum početka', + 'This task is now linked to the task %s with the relation "%s"' => 'Zadatak je sada povezan sa zadatkom %s relacijom "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'Veza sa relacijom "%s" na zadatak %s je maknuta', + 'Custom Filter:' => 'Prilagođeni filter', + 'Unable to find this group.' => 'Nije moguće pronaći ovu grupu.', + '%s moved the task #%d to the column "%s"' => '%s je pomaknuo/la zadatak #%d u stupac "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => '%s je pmaknuo/la zadatak #%d na poziciju %d u stupcu "%s"', + '%s moved the task #%d to the swimlane "%s"' => '%s je pomaknuo/la zadatak #%d u stazu "%s"', + '%sh spent' => '%ss potrošeno', + '%sh estimated' => '%ss procijenjeno', + 'Select All' => 'Označi sve', + 'Unselect All' => 'Poništi odabir svega', + 'Apply action' => 'Primjeni radnju', + 'Move selected tasks to another column or swimlane' => 'Pomakni odabrani zadatak u neki drugi stupac', + 'Edit tasks in bulk' => 'Masovno uređivanje zadataka', + 'Choose the properties that you would like to change for the selected tasks.' => 'Izaberite svojstva koja želite promijeniti na izabranim zadacima.', + 'Configure this project' => 'Uredite ovaj projekt', + 'Start now' => 'Započni sada', + '%s removed a file from the task #%d' => '%s je maknuo/la dataoteku sa zadatka #%d', + 'Attachment removed from task #%d: %s' => 'Privitak je maknut sa zadatka #%d: %s', + 'No color' => 'Bez boje', + 'Attachment removed "%s"' => 'Privitak maknut "%s"', + '%s removed a file from the task %s' => '%s je maknuo/la datoteku sa zadatka %s', + 'Move the task to another swimlane when assigned to a user' => 'Premjesti zadatak u drugu stazu kada je zadatak dodijeljen korisniku', + 'Destination swimlane' => 'Odredišna staza', + 'Assign a category when the task is moved to a specific swimlane' => 'Postavi kategoriju kada je zadatak premješten u određenu stazu', + 'Move the task to another swimlane when the category is changed' => 'Pomakni zadatak u drugu stazu kada se promijeni kategorija', + 'Reorder this column by priority (ASC)' => 'Presloži ovaj stupac po prioritetu (UZLAZNO)', + 'Reorder this column by priority (DESC)' => 'Presloži ovaj stupac po prioritetu (SILAZNO)', + 'Reorder this column by assignee and priority (ASC)' => 'Presloži ovaj stupac po izvršitelju (UZLAZNO)', + 'Reorder this column by assignee and priority (DESC)' => 'Presloži ovaj stupac po izvršitelju (SILAZNO)', + 'Reorder this column by assignee (A-Z)' => 'Presloži ovaj stupac po izvršitelju (A-Ž)', + 'Reorder this column by assignee (Z-A)' => 'Presloži ovaj stupac po izvršitelju (Ž-A)', + 'Reorder this column by due date (ASC)' => 'Presloži ovaj stupac po roku završetka (UZLAZNO)', + 'Reorder this column by due date (DESC)' => 'Presloži ovaj stupac po roku završetka (SILAZNO)', + 'Reorder this column by id (ASC)' => 'Poredaj stupac po id-u (ASC)', + 'Reorder this column by id (DESC)' => 'Poredaj stupac po id-u (DESC)', + '%s moved the task #%d "%s" to the project "%s"' => '%s je premjestio/la zadatak #%d "%s" u projekt "%s"', + 'Task #%d "%s" has been moved to the project "%s"' => 'Zadatak #%d "%s" je premješten u projekt "%s"', + 'Move the task to another column when the due date is less than a certain number of days' => 'Pomakni zadatak u drugi stupac kada je datum roka završetka kraći od određenog broja dana', + 'Automatically update the start date when the task is moved away from a specific column' => 'Automatski dopuni datum početka kada se zadatak pomakne iz određenog stupca', + 'HTTP Client:' => 'HTTP klijent', + 'Assigned' => 'Dodijeljeno', + 'Task limits apply to each swimlane individually' => 'Ograničenje broja zadataka vrijedi za svaku stazu zasebno', + 'Column task limits apply to each swimlane individually' => 'Ograničenje broja zadataka u stupcu je za svaku stazu posebno', + 'Column task limits are applied to each swimlane individually' => 'Ograničenje broja zadataka u stupcu se primjenjuje za svaku stazu posebno', + 'Column task limits are applied across swimlanes' => 'Ograničenje broja zadataka u stupcu je po svim stazama', + 'Task limit: ' => 'Ograničenje broja zadataka: ', + 'Change to global tag' => 'Promijeni u globalnu oznaku', + 'Do you really want to make the tag "%s" global?' => 'Doista želite da oznaka "%s" postane globalna?', + 'Enable global tags for this project' => 'Omogući globalne oznake za ovaj projekt', + 'Group membership(s):' => 'Članstvo u grupama:', + '%s is a member of the following group(s): %s' => '%s je član grupa: %s', + '%d/%d group(s) shown' => '%d/%d prikazane grupe', + 'Subtask creation or modification' => 'Kreiranje ili promjena podzadatka', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Dodijeli zadatak određenom korisniku kada je zadatak premješten u određenu stazu', + 'Comment' => 'Komentar', + 'Collapse vertically' => 'Skupi vertikalno', + 'Expand vertically' => 'Proširi vertikalno', + 'MXN - Mexican Peso' => 'MXN - Meksički pezos', + 'Estimated vs actual time per column' => 'Procijenjeno u odnosu na stvarno vrijeme po stupcu', + 'HUF - Hungarian Forint' => 'HUD - Mađarska forinta', + 'XBT - Bitcoin' => 'XBT - Bitcoin', + 'You must select a file to upload as your avatar!' => 'Morate odabrati datoteku za prijenos kao svoj avatar!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'Datoteka koju ste prenijeli nije važeća slika! (Dopušteni su samo *.gif, *.jpg, *.jpeg i *.png!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Automatski postavite datum dospijeća kada se zadatak premjesti iz određenog stupca', + 'No other projects found.' => 'Nema pronađenih drugih projekata.', + 'Tasks copied successfully.' => 'Zadaci uspješno kopirani.', + 'Unable to copy tasks.' => 'Nije moguće kopirati zadatke.', + 'Theme' => 'Tema', + 'Theme:' => 'Tema:', + 'Light theme' => 'Svijetla tema', + 'Dark theme' => 'Tamna tema', + 'Automatic theme - Sync with system' => 'Automatska tema - Sinkronizacija sa sustavom', + 'Application managers or more' => 'Voditelji aplikacija ili više', + 'Administrators' => 'Administratori', + 'Visibility:' => 'Vidljivost:', + 'Standard users' => 'Standardni korisnici', + 'Visibility is required' => 'Vidljivost je obavezna', + 'The visibility should be an app role' => 'Vidljivost bi trebala biti uloga aplikacije', + 'Reply' => 'Odgovori', + '%s wrote: ' => '%s napisao: ', + 'Number of visible tasks in this column and swimlane' => 'Broj vidljivih zadataka u ovom stupcu i plivačkoj stazi', + 'Number of tasks in this swimlane' => 'Broj zadataka u ovoj plivačkoj stazi', + 'Unable to find another subtask in progress, you can close this window.' => 'Nije moguće pronaći drugi podzadatak u tijeku, možete zatvoriti ovaj prozor.', + 'This theme is invalid' => 'Ova tema je nevažeća', + 'This role is invalid' => 'Ova uloga je nevažeća', + 'This timezone is invalid' => 'Ova vremenska zona je nevažeća', + 'This language is invalid' => 'Ovaj jezik je nevažeći', + 'This URL is invalid' => 'Ovaj URL je nevažeći', + 'Date format invalid' => 'Format datuma nevažeći', + 'Time format invalid' => 'Format vremena nevažeći', + 'Invalid Mail transport' => 'Nevažeći transport pošte', + 'Color invalid' => 'Boja nevažeća', + 'This value must be greater or equal to %d' => 'Ova vrijednost mora biti veća ili jednaka %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Dodajte BOM na početak datoteke (obavezno za Microsoft Excel)', + 'Just add these tag(s)' => 'Samo dodajte ove oznake', + 'Remove internal link(s)' => 'Uklonite unutarnje poveznice', + 'Import tasks from another project' => 'Uvezite zadatke iz drugog projekta', + 'Select the project to copy tasks from' => 'Odaberite projekt iz kojeg želite kopirati zadatke', + 'The total maximum allowed attachments size is %sB.' => 'Ukupna maksimalna dopuštena veličina privitaka je %sB.', + 'Add attachments' => 'Dodaj privitke', + 'Task #%d "%s" is overdue' => 'Zadatak #%d "%s" je istekao', + 'Enable notifications by default for all new users' => 'Omogući obavijesti prema zadanim postavkama za sve nove korisnike', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Dodijeli zadatak njegovom kreatoru za određene stupce ako izvršitelj nije ručno postavljen', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Dodijeli zadatak prijavljenom korisniku pri promjeni stupca na zadani stupac ako nitko nije dodijeljen', +]; diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php new file mode 100644 index 0000000..7820426 --- /dev/null +++ b/app/Locale/hu_HU/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => ' ', + 'None' => 'Nincs', + 'Edit' => 'Szerkesztés', + 'Remove' => 'Eltávolítás', + 'Yes' => 'Igen', + 'No' => 'Nem', + 'cancel' => 'mégse', + 'or' => 'vagy', + 'Yellow' => 'Sárga', + 'Blue' => 'Kék', + 'Green' => 'Zöld', + 'Purple' => 'Lila', + 'Red' => 'Piros', + 'Orange' => 'Narancssárga', + 'Grey' => 'Szürke', + 'Brown' => 'Barna', + 'Deep Orange' => 'Sötét narancs', + 'Dark Grey' => 'Sötét szürke', + 'Pink' => 'Rózsaszín', + 'Teal' => 'Kékeszöld', + 'Cyan' => 'Ciánkék', + 'Lime' => 'Citrus', + 'Light Green' => 'Világos zöld', + 'Amber' => 'Borostyán', + 'Save' => 'Mentés', + 'Login' => 'Bejelentkezés', + 'Official website:' => 'Hivatalos weboldal:', + 'Unassigned' => 'Nincs felelős', + 'View this task' => 'Feladat megtekintése', + 'Remove user' => 'Felhasználó eltávolítása', + 'Do you really want to remove this user: "%s"?' => 'Valóban el szeretné távolítani ezt a felhasználót: „%s”?', + 'All users' => 'Összes felhasználó', + 'Username' => 'Felhasználónév', + 'Password' => 'Jelszó', + 'Administrator' => 'Adminisztrátor', + 'Sign in' => 'Bejelentkezés', + 'Users' => 'Felhasználók', + 'Forbidden' => 'Tiltott', + 'Access Forbidden' => 'Hozzáférés megtagadva', + 'Edit user' => 'Felhasználó szerkesztése', + 'Logout' => 'Kilépés', + 'Bad username or password' => 'Hibás felhasználónév vagy jelszó', + 'Edit project' => 'Projekt szerkesztése', + 'Name' => 'Név', + 'Projects' => 'Projektek', + 'No project' => 'Nincs projekt', + 'Project' => 'Projekt', + 'Status' => 'Állapot', + 'Tasks' => 'Feladatok', + 'Board' => 'Tábla', + 'Actions' => 'Műveletek', + 'Inactive' => 'Inaktív', + 'Active' => 'Aktív', + 'Unable to update this board.' => 'Nem lehet frissíteni ezt a táblát.', + 'Disable' => 'Letiltás', + 'Enable' => 'Engedélyezés', + 'New project' => 'Új projekt', + 'Do you really want to remove this project: "%s"?' => 'Valóban el szeretné távolítani ezt a projektet: „%s”?', + 'Remove project' => 'Projekt eltávolítása', + 'Edit the board for "%s"' => 'Tábla szerkesztése: „%s”', + 'Add a new column' => 'Új oszlop hozzáadása', + 'Title' => 'Cím', + 'Assigned to %s' => 'Felelős: %s', + 'Remove a column' => 'Oszlop eltávolítása', + 'Unable to remove this column.' => 'Nem lehet eltávolítani ezt az oszlopot.', + 'Do you really want to remove this column: "%s"?' => 'Valóban el szeretné távolítani ezt az oszlopot: „%s”?', + 'Settings' => 'Beállítások', + 'Application settings' => 'Alkalmazás beállításai', + 'Language' => 'Nyelv', + 'Webhook token:' => 'Webhurok token:', + 'API token:' => 'API token:', + 'Database size:' => 'Adatbázis mérete:', + 'Download the database' => 'Adatbázis letöltése', + 'Optimize the database' => 'Adatbázis optimalizálása', + '(VACUUM command)' => '(VACUUM parancs)', + '(Gzip compressed Sqlite file)' => '(Gzip-pel tömörített SQLite fájl)', + 'Close a task' => 'Feladat lezárása', + 'Column' => 'Oszlop', + 'Color' => 'Szín', + 'Assignee' => 'Felelős', + 'Create another task' => 'Másik feladat létrehozása', + 'New task' => 'Új feladat', + 'Open a task' => 'Feladat megnyitása', + 'Do you really want to open this task: "%s"?' => 'Valóban meg szeretné nyitni ezt a feladatot: „%s”?', + 'Back to the board' => 'Vissza a táblához', + 'There is nobody assigned' => 'Nincs senki se hozzárendelve', + 'Column on the board:' => 'Oszlop a táblán:', + 'Close this task' => 'Feladat lezárása', + 'Open this task' => 'Feladat megnyitása', + 'There is no description.' => 'Nincs leírás.', + 'Add a new task' => 'Új feladat hozzáadása', + 'The username is required' => 'A felhasználónév kötelező', + 'The maximum length is %d characters' => 'A legnagyobb hossz %d karakter', + 'The minimum length is %d characters' => 'A legkisebb hossz %d karakter', + 'The password is required' => 'A jelszó kötelező', + 'This value must be an integer' => 'Ez az érték csak egész szám lehet', + 'The username must be unique' => 'A felhasználónévnek egyedinek kell lennie', + 'The user id is required' => 'A felhasználó-azonosító kötelező', + 'Passwords don\'t match' => 'A jelszavak nem egyeznek', + 'The confirmation is required' => 'A megerősítés kötelező', + 'The project is required' => 'A projekt kötelező', + 'The id is required' => 'Az azonosító kötelező', + 'The project id is required' => 'A projekt-azonosító kötelező', + 'The project name is required' => 'A projektnév kötelező', + 'The title is required' => 'A cím kötelező', + 'Settings saved successfully.' => 'A beállítások sikeresen mentve.', + 'Unable to save your settings.' => 'Nem lehet elmenteni a beállításokat.', + 'Database optimization done.' => 'Az adatbázis optimalizálása kész.', + 'Your project has been created successfully.' => 'A projekt sikeresen létrehozva.', + 'Unable to create your project.' => 'Nem lehet létrehozni a projektet.', + 'Project updated successfully.' => 'A projekt sikeresen frissítve.', + 'Unable to update this project.' => 'Nem lehet frissíteni ezt a projektet.', + 'Unable to remove this project.' => 'Nem lehet eltávolítani ezt a projektet.', + 'Project removed successfully.' => 'A projekt sikeresen eltávolítva.', + 'Project activated successfully.' => 'A projekt sikeresen aktiválva.', + 'Unable to activate this project.' => 'Nem lehet aktiválni ezt a projektet.', + 'Project disabled successfully.' => 'A projekt sikeresen letiltva.', + 'Unable to disable this project.' => 'Nem lehet letiltani ezt a projektet.', + 'Unable to open this task.' => 'Nem lehet megnyitni ezt a feladatot.', + 'Task opened successfully.' => 'A feladat sikeresen megnyitva.', + 'Unable to close this task.' => 'Nem lehet lezárni ezt a feladatot.', + 'Task closed successfully.' => 'A feladat sikeresen lezárva.', + 'Unable to update your task.' => 'Nem lehet frissíteni a feladatot.', + 'Task updated successfully.' => 'A feladat sikeresen frissítve.', + 'Unable to create your task.' => 'Nem lehet létrehozni a feladatot.', + 'Task created successfully.' => 'A feladat sikeresen létrehozva.', + 'User created successfully.' => 'A felhasználó sikeresen létrehozva.', + 'Unable to create your user.' => 'Nem lehet létrehozni a felhasználót.', + 'User updated successfully.' => 'A felhasználó sikeresen frissítve.', + 'User removed successfully.' => 'A felhasználó sikeresen eltávolítva.', + 'Unable to remove this user.' => 'Nem lehet eltávolítani ezt a felhasználót.', + 'Board updated successfully.' => 'A tábla sikeresen frissítve.', + 'Ready' => 'Felkészülés', + 'Backlog' => 'Elintézendő', + 'Work in progress' => 'Folyamatban', + 'Done' => 'Kész', + 'Application version:' => 'Alkalmazás verziója:', + 'Id' => 'Azonosító', + 'Public link' => 'Nyilvános hivatkozás', + 'Timezone' => 'Időzóna', + 'Sorry, I didn\'t find this information in my database!' => 'Elnézést, ez az információ nem található az adatbázisban!', + 'Page not found' => 'Az oldal nem található', + 'Complexity' => 'Bonyolultság', + 'Task limit' => 'Feladatkorlát', + 'Task count' => 'Feladatok száma', + 'User' => 'Felhasználó', + 'Comments' => 'Hozzászólások', + 'Comment is required' => 'A hozzászólás kötelező', + 'Comment added successfully.' => 'A hozzászólás sikeresen hozzáadva.', + 'Unable to create your comment.' => 'Nem lehet létrehozni a hozzászólást.', + 'Due Date' => 'Határidő', + 'Invalid date' => 'Érvénytelen dátum', + 'Automatic actions' => 'Automatikus műveletek', + 'Your automatic action has been created successfully.' => 'Az automatikus művelet sikeresen létrehozva.', + 'Unable to create your automatic action.' => 'Nem lehet létrehozni az automatikus műveletet.', + 'Remove an action' => 'Művelet eltávolítása', + 'Unable to remove this action.' => 'Nem lehet eltávolítani ezt a műveletet.', + 'Action removed successfully.' => 'A művelet sikeresen eltávolítva.', + 'Automatic actions for the project "%s"' => 'Automatikus műveletek a projekthez: „%s”', + 'Add an action' => 'Művelet hozzáadása', + 'Event name' => 'Esemény neve', + 'Action' => 'Művelet', + 'Event' => 'Esemény', + 'When the selected event occurs execute the corresponding action.' => 'Ha a kiválasztott esemény bekövetkezik, hajtsa végre a megfelelő műveletet.', + 'Next step' => 'Következő lépés', + 'Define action parameters' => 'Művelet paramétereinek meghatározása', + 'Do you really want to remove this action: "%s"?' => 'Valóban el szeretné távolítani ezt a műveletet: „%s”?', + 'Remove an automatic action' => 'Automatikus művelet eltávolítása', + 'Assign the task to a specific user' => 'Feladat kiosztása egy adott felhasználónak', + 'Assign the task to the person who does the action' => 'Feladat kiosztása a műveletet elvégző személynek', + 'Duplicate the task to another project' => 'Feladat másolása egy másik projektbe', + 'Move a task to another column' => 'Feladat áthelyezése egy másik oszlopba', + 'Task modification' => 'Feladat módosítása', + 'Task creation' => 'Feladat létrehozása', + 'Closing a task' => 'Feladat lezárása', + 'Assign a color to a specific user' => 'Szín hozzárendelése egy adott felhasználóhoz', + 'Position' => 'Pozíció', + 'Duplicate to project' => 'Másolás egy projektbe', + 'Duplicate' => 'Másolás', + 'Link' => 'Hivatkozás', + 'Comment updated successfully.' => 'A megjegyzés sikeresen frissítve.', + 'Unable to update your comment.' => 'Nem lehet frissíteni a megjegyzést.', + 'Remove a comment' => 'Megjegyzés eltávolítása', + 'Comment removed successfully.' => 'A megjegyzés sikeresen eltávolítva.', + 'Unable to remove this comment.' => 'Nem lehet eltávolítani a megjegyzést.', + 'Do you really want to remove this comment?' => 'Valóban el szeretné távolítani ezt a megjegyzést?', + 'Current password for the user "%s"' => '„%s” felhasználó jelenlegi jelszava', + 'The current password is required' => 'A jelenlegi jelszó kötelező', + 'Wrong password' => 'Hibás jelszó', + 'Unknown' => 'Ismeretlen', + 'Last logins' => 'Legutóbbi bejelentkezések', + 'Login date' => 'Bejelentkezés dátuma', + 'Authentication method' => 'Hitelesítési módszer', + 'IP address' => 'IP-cím', + 'User agent' => 'Felhasználói ügynök', + 'Persistent connections' => 'Tartós kapcsolatok', + 'No session.' => 'Nincs munkamenet.', + 'Expiration date' => 'Lejárat dátuma', + 'Remember Me' => 'Emlékezzen rám', + 'Creation date' => 'Létrehozás dátuma', + 'Everybody' => 'Mindenki', + 'Open' => 'Nyitott', + 'Closed' => 'Lezárt', + 'Search' => 'Keresés', + 'Nothing found.' => 'Nincs találat.', + 'Due date' => 'Határidő', + 'Description' => 'Leírás', + '%d comments' => '%d megjegyzés', + '%d comment' => '%d megjegyzés', + 'Email address invalid' => 'Érvénytelen e-mail-cím', + 'Your external account is not linked anymore to your profile.' => 'A külső fiókja többé nincs hozzákapcsolva a profiljához.', + 'Unable to unlink your external account.' => 'Nem lehet megszüntetni a kapcsolatot a külső fiókjával.', + 'External authentication failed' => 'A külső hitelesítés sikertelen', + 'Your external account is linked to your profile successfully.' => 'A külső fiókja sikeresen össze lett kapcsolva a profiljával.', + 'Email' => 'E-mail', + 'Task removed successfully.' => 'A feladat sikeresen eltávolítva.', + 'Unable to remove this task.' => 'Nem lehet eltávolítani a feladatot.', + 'Remove a task' => 'Feladat eltávolítása', + 'Do you really want to remove this task: "%s"?' => 'Valóban el szeretné távolítani ezt a feladatot: „%s”?', + 'Assign automatically a color based on a category' => 'Szín automatikus hozzárendelése egy kategória alapján', + 'Assign automatically a category based on a color' => 'Kategória automatikus hozzárendelése egy szín alapján', + 'Task creation or modification' => 'Feladat létrehozása vagy módosítása', + 'Category' => 'Kategória', + 'Category:' => 'Kategória:', + 'Categories' => 'Kategóriák', + 'Your category has been created successfully.' => 'A kategória sikeresen létrehozva.', + 'This category has been updated successfully.' => 'A kategória sikeresen frissítve.', + 'Unable to update this category.' => 'Nem lehet frissíteni ezt a kategóriát.', + 'Remove a category' => 'Kategória eltávolítása', + 'Category removed successfully.' => 'A kategória sikeresen eltávolítva.', + 'Unable to remove this category.' => 'Nem lehet eltávolítani ezt a kategóriát.', + 'Category modification for the project "%s"' => 'Kategória módosítása a projektnél: „%s”', + 'Category Name' => 'Kategória neve', + 'Add a new category' => 'Új kategória hozzáadása', + 'Do you really want to remove this category: "%s"?' => 'Valóban el szeretné távolítani ezt a kategóriát: „%s”?', + 'All categories' => 'Összes kategória', + 'No category' => 'Nincs kategória', + 'The name is required' => 'A név kötelező', + 'Remove a file' => 'Fájl eltávolítása', + 'Unable to remove this file.' => 'Nem lehet eltávolítani ezt a fájlt.', + 'File removed successfully.' => 'A fájl sikeresen eltávolítva.', + 'Attach a document' => 'Dokumentum csatolása', + 'Do you really want to remove this file: "%s"?' => 'Valóban el szeretné távolítani ezt a fájlt: „%s”?', + 'Attachments' => 'Mellékletek', + 'Edit the task' => 'Feladat szerkesztése', + 'Add a comment' => 'Megjegyzés hozzáadása', + 'Edit a comment' => 'Megjegyzés szerkesztése', + 'Summary' => 'Összegzés', + 'Time tracking' => 'Idő követés', + 'Estimate:' => 'Becsült:', + 'Spent:' => 'Eltöltött:', + 'Do you really want to remove this sub-task?' => 'Valóban el szeretné távolítani ezt a részfeladatot?', + 'Remaining:' => 'Hátralévő:', + 'hours' => 'óra', + 'estimated' => 'becsült', + 'Sub-Tasks' => 'Részfeladatok', + 'Add a sub-task' => 'Részfeladat hozzáadása', + 'Original estimate' => 'Eredeti időbecslés', + 'Create another sub-task' => 'További részfeladat létrehozása', + 'Time spent' => 'Eltöltött idő', + 'Edit a sub-task' => 'Részfeladat szerkesztése', + 'Remove a sub-task' => 'Részfeladat eltávolítása', + 'The time must be a numeric value' => 'Az idő csak számérték lehet', + 'Todo' => 'Teendő', + 'In progress' => 'Folyamatban', + 'Sub-task removed successfully.' => 'A részfeladat sikeresen eltávolítva.', + 'Unable to remove this sub-task.' => 'Nem lehet eltávolítani a részfeladatot.', + 'Sub-task updated successfully.' => 'A részfeladat sikeresen frissítve.', + 'Unable to update your sub-task.' => 'Nem lehet frissíteni a részfeladatot.', + 'Unable to create your sub-task.' => 'Nem lehet létrehozni a részfeladatot.', + 'Maximum size: ' => 'Legnagyobb méret: ', + 'Display another project' => 'Másik projekt megjelenítése', + 'Created by %s' => 'Létrehozta: %s', + 'Tasks Export' => 'Feladatok exportálása', + 'Start Date' => 'Kezdési dátum', + 'Execute' => 'Végrehajtás', + 'Task Id' => 'Feladatazonosító', + 'Creator' => 'Létrehozó', + 'Modification date' => 'Módosítás dátuma', + 'Completion date' => 'Befejezés dátuma', + 'Clone' => 'Klónozás', + 'Project cloned successfully.' => 'A projekt sikeresen lemásolva.', + 'Unable to clone this project.' => 'Nem lehet lemásolni a projektet.', + 'Enable email notifications' => 'E-mail értesítések engedélyezése', + 'Task position:' => 'Feladat helye:', + 'The task #%d has been opened.' => '#%d. feladat megnyitva.', + 'The task #%d has been closed.' => '#%d. feladat lezárva.', + 'Sub-task updated' => 'Részfeladat frissítve', + 'Title:' => 'Cím', + 'Status:' => 'Állapot:', + 'Assignee:' => 'Felelős:', + 'Time tracking:' => 'Idő követés:', + 'New sub-task' => 'Új részfeladat', + 'New attachment added "%s"' => 'Új melléklet hozzáadva: „%s”.', + 'New comment posted by %s' => 'Új megjegyzést adott hozzá: %s', + 'New comment' => 'Új megjegyzés', + 'Comment updated' => 'Megjegyzés frissítve', + 'New subtask' => 'Új részfeladat', + 'I only want to receive notifications for these projects:' => 'Csak ezekről a projektekről szeretnék értesítést kapni:', + 'view the task on Kanboard' => 'feladat megtekintése a Kanboardon', + 'Public access' => 'Nyilvános hozzáférés', + 'Disable public access' => 'Nyilvános hozzáférés letiltása', + 'Enable public access' => 'Nyilvános hozzáférés engedélyezése', + 'Public access disabled' => 'Nyilvános hozzáférés letiltva', + 'Move the task to another project' => 'Feladat áthelyezése másik projektbe', + 'Move to project' => 'Áthelyezés egy projektbe', + 'Do you really want to duplicate this task?' => 'Valóban le szeretné másolni ezt a feladatot?', + 'Duplicate a task' => 'Feladat másolása', + 'External accounts' => 'Külső fiókok', + 'Account type' => 'Fiók típusa', + 'Local' => 'Helyi', + 'Remote' => 'Távoli', + 'Enabled' => 'Engedélyezve', + 'Disabled' => 'Letiltva', + 'Login:' => 'Felhasználónév:', + 'Full Name:' => 'Teljes név:', + 'Email:' => 'E-mail:', + 'Notifications:' => 'Értesítések:', + 'Notifications' => 'Értesítések', + 'Account type:' => 'Fiók típusa:', + 'Edit profile' => 'Profil szerkesztése', + 'Change password' => 'Jelszó megváltoztatása', + 'Password modification' => 'Jelszó módosítása', + 'External authentications' => 'Külső hitelesítések', + 'Never connected.' => 'Sosem csatlakoztatott.', + 'No external authentication enabled.' => 'Nincs külső hitelesítés engedélyezve.', + 'Password modified successfully.' => 'A jelszó sikeresen módosítva.', + 'Unable to change the password.' => 'Nem lehet megváltoztatni a jelszót.', + 'Change category' => 'Kategória megváltoztatása', + '%s updated the task %s' => '%s frissítette a(z) %s feladatot', + '%s opened the task %s' => '%s megnyitott a(z) %s feladatot', + '%s moved the task %s to the position #%d in the column "%s"' => '%s áthelyezte a(z) %s feladatot a(z) #%d. pozícióba a(z) „%s” oszlopban', + '%s moved the task %s to the column "%s"' => '%s áthelyezte a(z) %s feladatot a(z) „%s” oszlopba', + '%s created the task %s' => '%s létrehozta a(z) %s feladatot', + '%s closed the task %s' => '%s lezárta a(z) %s feladatot', + '%s created a subtask for the task %s' => '%s létrehozott egy részfeladatot a(z) %s feladathoz', + '%s updated a subtask for the task %s' => '%s frissített egy részfeladatot a(z) %s feladatnál', + 'Assigned to %s with an estimate of %s/%sh' => 'Hozzárendelve a(z) %s feladathoz %s/%s óra becsült idővel', + 'Not assigned, estimate of %sh' => 'Nincs kiosztva, becsült idő: %s óra', + '%s updated a comment on the task %s' => '%s frissített egy megjegyzését a(z) %s feladatban', + '%s commented the task %s' => '%s megjegyzést írt a(z) %s feladathoz', + '%s\'s activity' => '%s tevékenységei', + 'RSS feed' => 'RSS hírforrás', + '%s updated a comment on the task #%d' => '%s frissített egy megjegyzést a(z) #%d. feladatban', + '%s commented on the task #%d' => '%s megjegyzést írt a(z) #%d. feladathoz', + '%s updated a subtask for the task #%d' => '%s frissített egy részfeladatot a(z) #%d. feladatnál', + '%s created a subtask for the task #%d' => '%s létrehozott egy részfeladatot a(z) #%d. feladatnál', + '%s updated the task #%d' => '%s frissítette a(z) #%d. feladatot', + '%s created the task #%d' => '%s létrehozta a(z) #%d. feladatot', + '%s closed the task #%d' => '%s lezárta a(z) #%d feladatot', + '%s opened the task #%d' => '%s megnyitotta a(z) #%d. feladatot', + 'Activity' => 'Tevékenység', + 'Default values are "%s"' => 'Az alapértelmezett értékek: „%s”', + 'Default columns for new projects (Comma-separated)' => 'Alapértelmezett oszlopok az új projekteknél (vesszővel elválasztva)', + 'Task assignee change' => 'A feladat felelősének megváltoztatása', + '%s changed the assignee of the task #%d to %s' => '%s megváltoztatta a(z) #%d. feladat felelősét erre: %s', + '%s changed the assignee of the task %s to %s' => '%s megváltoztatta a(z) %s feladat felelősét erre: %s', + 'New password for the user "%s"' => '„%s” felhasználó új jelszava', + 'Choose an event' => 'Esemény választása', + 'Create a task from an external provider' => 'Feladat létrehozása egy külsős szolgáltatóból', + 'Change the assignee based on an external username' => 'Felelős megváltoztatása egy külső felhasználónév alapján', + 'Change the category based on an external label' => 'Kategória megváltoztatása egy külső címke alapján', + 'Reference' => 'Hivatkozás', + 'Label' => 'Címke', + 'Database' => 'Adatbázis', + 'About' => 'Névjegy', + 'Database driver:' => 'Adatbázis-meghajtó:', + 'Board settings' => 'Tábla beállításai', + 'Webhook settings' => 'Webhurok beállításai', + 'Reset token' => 'Token visszaállítása', + 'API endpoint:' => 'API végpont:', + 'Refresh interval for personal board' => 'Frissítési időköz a személyes táblánál', + 'Refresh interval for public board' => 'Frissítési időköz a nyilvános táblánál', + 'Task highlight period' => 'Feladat kiemelésének időtartama', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Az időszak (másodpercben), amíg a feladatot legutóbb módosítottnak kell tekinteni (0: letiltás, alapértelmezetten 2 nap)', + 'Frequency in second (60 seconds by default)' => 'Gyakoriság másodpercben (alapértelmezetten 60 másodperc)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Gyakoriság másodpercben (0: a funkció letiltása, alapértelmezetten 10 másodperc)', + 'Application URL' => 'Alkalmazás URL', + 'Token regenerated.' => 'A token újra létrehozva.', + 'Date format' => 'Dátumformátum', + 'ISO format is always accepted, example: "%s" and "%s"' => 'Az ISO formátum mindig elfogadott, például „%s” és „%s”', + 'New personal project' => 'Új személyes projekt', + 'This project is personal' => 'Ez a projekt személyes', + 'Add' => 'Hozzáadás', + 'Start date' => 'Kezdési dátum', + 'Time estimated' => 'Becsült idő', + 'There is nothing assigned to you.' => 'Nincs semmi sem Önhöz rendelve.', + 'My tasks' => 'Saját feladatok', + 'Activity stream' => 'Tevékenységfolyam', + 'Dashboard' => 'Vezérlőpult', + 'Confirmation' => 'Megerősítés', + 'Webhooks' => 'Webhurkok', + 'API' => 'API', + 'Create a comment from an external provider' => 'Megjegyzés létrehozása egy külső szolgáltatótól', + 'Project management' => 'Projektmenedzsment', + 'Columns' => 'Oszlopok', + 'Task' => 'Feladat', + 'Percentage' => 'Százalék', + 'Number of tasks' => 'Feladatok száma', + 'Task distribution' => 'Feladatelosztás', + 'Analytics' => 'Elemzések', + 'Subtask' => 'Részfeladat', + 'User repartition' => 'Felhasználónkénti megoszlás', + 'Clone this project' => 'Projekt másolása', + 'Column removed successfully.' => 'Az oszlop sikeresen eltávolítva.', + 'Not enough data to show the graph.' => 'Nincs elég adat a grafikon megjelenítéséhez.', + 'Previous' => 'Előző', + 'The id must be an integer' => 'Az azonosító csak egész szám lehet', + 'The project id must be an integer' => 'A projektazonosító csak egész szám lehet', + 'The status must be an integer' => 'Az állapot csak egész szám lehet', + 'The subtask id is required' => 'A részfeladat-azonosító kötelező', + 'The subtask id must be an integer' => 'A részfeladat-azonosító csak egész szám lehet', + 'The task id is required' => 'A feladatazonosító kötelező', + 'The task id must be an integer' => 'A feladatazonosító csak egész szám lehet', + 'The user id must be an integer' => 'A felhasználó-azonosító csak egész szám lehet', + 'This value is required' => 'Ez az érték kötelező', + 'This value must be numeric' => 'Ez az érték csak szám lehet', + 'Unable to create this task.' => 'Nem lehet létrehozni a feladatot.', + 'Cumulative flow diagram' => 'Halmozott folyamatdiagram', + 'Daily project summary' => 'Napi projektösszefoglaló', + 'Daily project summary export' => 'Napi projektösszefoglaló exportálása', + 'Exports' => 'Exportálások', + 'This export contains the number of tasks per column grouped per day.' => 'Ez az export tartalmazza a feladatok számát oszloponként csoportosítva, napokra lebontva.', + 'Active swimlanes' => 'Aktív sávok', + 'Add a new swimlane' => 'Új sáv hozzáadása', + 'Default swimlane' => 'Alapértelmezett sáv', + 'Do you really want to remove this swimlane: "%s"?' => 'Valóban el szeretné távolítani ezt a sávot: „%s”?', + 'Inactive swimlanes' => 'Inaktív sávok', + 'Remove a swimlane' => 'Sáv eltávolítása', + 'Swimlane modification for the project "%s"' => 'Sávmódosítás a(z) „%s” projektnél', + 'Swimlane removed successfully.' => 'A sáv sikeresen eltávolítva.', + 'Swimlanes' => 'Sávok', + 'Swimlane updated successfully.' => 'A sáv sikeresen frissítve.', + 'Unable to remove this swimlane.' => 'Nem lehet eltávolítani ezt a sávot.', + 'Unable to update this swimlane.' => 'Nem lehet frissíteni ezt a sávot.', + 'Your swimlane has been created successfully.' => 'A sáv sikeresen létrehozva.', + 'Example: "Bug, Feature Request, Improvement"' => 'Például: Hiba, Funkciókérés, Fejlesztés', + 'Default categories for new projects (Comma-separated)' => 'Alapértelmezett kategóriák az új projekteknél (vesszővel elválasztva)', + 'Integrations' => 'Integrációk', + 'Integration with third-party services' => 'Integráció harmadik féltől származó szolgáltatásokkal', + 'Subtask Id' => 'Részfeladat-azonosító', + 'Subtasks' => 'Részfeladatok', + 'Subtasks Export' => 'Részfeladat exportálása', + 'Task Title' => 'Feladat címe', + 'Untitled' => 'Névtelen', + 'Application default' => 'Alkalmazás alapértelmezettje', + 'Language:' => 'Nyelv:', + 'Timezone:' => 'Időzóna:', + 'All columns' => 'Összes oszlop', + 'Next' => 'Következő', + '#%d' => '#%d', + 'All swimlanes' => 'Összes sáv', + 'All colors' => 'Összes szín', + 'Moved to column %s' => '%s oszlopba áthelyezve', + 'User dashboard' => 'Felhasználó vezérlőpultja', + 'Allow only one subtask in progress at the same time for a user' => 'Egyszerre csak egy folyamatban levő részfeladat engedélyezése egy felhasználónak', + 'Edit column "%s"' => '„%s” oszlop szerkesztése', + 'Select the new status of the subtask: "%s"' => 'A részfeladat új állapotának kiválasztása: „%s”', + 'Subtask timesheet' => 'Részfeladat időbeosztása', + 'There is nothing to show.' => 'Nincs mit megjeleníteni.', + '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é másolni?', + 'Disallow login form' => 'Bejelentkezési űrlap letiltása', + 'Start' => 'Kezdet', + 'End' => 'Vég', + 'Task age in days' => 'Feladat életkora napokban', + 'Days in this column' => 'Napok ebben az oszlopban', + '%dd' => '%dn', + 'Add a new link' => 'Új hivatkozás hozzáadása', + 'Do you really want to remove this link: "%s"?' => 'Valóban el szeretné távolítani ezt a hivatkozást: „%s”?', + 'Do you really want to remove this link with task #%d?' => 'Valóban el szeretné távolítani a(z) #%d. feladatra mutató hivatkozást?', + 'Field required' => 'A mező kötelező', + 'Link added successfully.' => 'A hivatkozás sikeresen hozzáadva.', + 'Link updated successfully.' => 'A hivatkozás sikeresen frissítve.', + 'Link removed successfully.' => 'A hivatkozás sikeresen eltávolítva.', + 'Link labels' => 'Hivatkozás címkék', + 'Link modification' => 'Hivatkozás módosítása', + 'Opposite label' => 'Ellenkező címke', + 'Remove a link' => 'Hivatkozás eltávolítása', + 'The labels must be different' => 'A címkék nem lehetnek azonosak', + 'There is no link.' => 'Nincs hivatkozás.', + 'This label must be unique' => 'A címkének egyedinek kell lennie.', + 'Unable to create your link.' => 'Nem lehet létrehozni a hivatkozást.', + 'Unable to update your link.' => 'Nem lehet frissíteni a hivatkozást.', + 'Unable to remove this link.' => 'Nem lehet eltávolítani a hivatkozást.', + 'relates to' => 'ehhez tartozik:', + 'blocks' => 'blokkolja:', + 'is blocked by' => 'blokkolva van:', + 'duplicates' => 'másolja:', + 'is duplicated by' => 'másolva van:', + 'is a child of' => 'gyermeke a következőnek:', + 'is a parent of' => 'szülője a következőnek:', + 'targets milestone' => 'megcélzott mérföldkő:', + 'is a milestone of' => 'mérföldköve:', + 'fixes' => 'javítja:', + 'is fixed by' => 'javította:', + 'This task' => 'Ez a feladat', + '<1h' => '<1ó', + '%dh' => '%dó', + 'Expand tasks' => 'Feladatok kinyitása', + 'Collapse tasks' => 'Feladatok összecsukása', + 'Expand/collapse tasks' => 'Feladatok kinyitása/összecsukása', + 'Close dialog box' => 'Párbeszédablak bezárása', + 'Submit a form' => 'Űrlap elküldése', + 'Board view' => 'Tábla nézet', + 'Keyboard shortcuts' => 'Gyorsbillentyűk', + 'Open board switcher' => 'Táblaválasztó megnyitása', + 'Application' => 'Alkalmazás', + 'Compact view' => 'Tömör nézet', + 'Horizontal scrolling' => 'Vízszintes görgetés', + 'Compact/wide view' => 'Tömör/széles nézet', + 'Currency' => 'Pénznem', + 'Personal project' => 'Személyes projekt', + 'AUD - Australian Dollar' => 'AUD – ausztrál dollár', + 'CAD - Canadian Dollar' => 'CAD – kanadai dollár', + 'CHF - Swiss Francs' => 'CHF – svájci frank', + 'Custom Stylesheet' => 'Egyéni stíluslap', + 'EUR - Euro' => 'EUR – euro', + 'GBP - British Pound' => 'GBP – angol font', + 'INR - Indian Rupee' => 'INR – indiai rúpia', + 'JPY - Japanese Yen' => 'JPY – japán jen', + 'NZD - New Zealand Dollar' => 'NZD – új-zélandi dollár', + 'PEN - Peruvian Sol' => 'PEN - Perui sol', + 'RSD - Serbian dinar' => 'RSD – szerb dinár', + 'CNY - Chinese Yuan' => 'CNY – kínai jüan', + 'USD - US Dollar' => 'USD – amerikai dollár', + 'VES - Venezuelan Bolívar' => 'VES – venezuelai bolívar', + 'Destination column' => 'Céloszlop', + 'Move the task to another column when assigned to a user' => 'Feladat áthelyezése másik oszlopba, ha egy felhasználóhoz rendelik', + 'Move the task to another column when assignee is cleared' => 'Feladat áthelyezése másik oszlopba, ha a felelőst törlik', + 'Source column' => 'Forrásoszlop', + 'Transitions' => 'Átmenetek', + 'Executer' => 'Végrehajtó', + 'Time spent in the column' => 'Az oszlopban eltöltött idő', + 'Task transitions' => 'Feladatátmenetek', + 'Task transitions export' => 'Feladatátmenetek exportálása', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Ez a jelentés az egyes feladatok összes oszlopáthelyezését tartalmazza dátummal, felhasználóval és az egyes átmenetekben eltöltött idővel.', + 'Currency rates' => 'Devizaárfolyamok', + 'Rate' => 'Árfolyam', + 'Change reference currency' => 'Bázis pénznem megváltoztatása', + 'Reference currency' => 'Bázis pénznem', + 'The currency rate has been added successfully.' => 'A devizaárfolyam sikeresen hozzáadva.', + 'Unable to add this currency rate.' => 'Nem lehet hozzáadni ezt a devizaárfolyamot.', + 'Webhook URL' => 'Webhurok URL', + '%s removed the assignee of the task %s' => '%s eltávolította a(z) %s feladat felelősét', + 'Information' => 'Információ', + 'Check two factor authentication code' => 'A kétlépcsős hitelesítés kódjának ellenőrzése', + 'The two factor authentication code is not valid.' => 'A kétlépcsős hitelesítés kódja nem érvényes.', + 'The two factor authentication code is valid.' => 'A kétlépcsős hitelesítés kódja érvényes.', + 'Code' => 'Kód', + 'Two factor authentication' => 'Kétlépcsős hitelesítés', + 'This QR code contains the key URI: ' => 'Ez a QR-kód tartalmazza a kulcs URI-t: ', + 'Check my code' => 'A kódom ellenőrzése', + 'Secret key: ' => 'Titkos kulcs: ', + 'Test your device' => 'Az eszköz ellenőrzése', + 'Assign a color when the task is moved to a specific column' => 'Szín hozzárendelése, ha a feladatot egy adott oszlopba helyezték át', + '%s via Kanboard' => '%s a Kanboardon keresztül', + 'Burndown chart' => 'Burndown diagram', + 'This chart show the task complexity over the time (Work Remaining).' => 'Ez a diagram a feladat időbeli bonyolultságát ábrázolja (mennyi munka van hátra)', + 'Screenshot taken %s' => 'Képernyőkép készült: %s', + 'Add a screenshot' => 'Képernyőkép hozzáadása', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Készítsen képernyőképet, majd a CTRL+V vagy ⌘+V megnyomásával illessze be ide.', + 'Screenshot uploaded successfully.' => 'A képernyőkép sikeresen feltöltése.', + 'SEK - Swedish Krona' => 'SEK – svéd korona', + 'Identifier' => 'Azonosító', + 'Disable two factor authentication' => 'A kétlépcsős hitelesítés letiltása', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Valóban le szeretné tiltani a kétlépcsős hitelesítést ennél a felhasználónál: „%s”?', + 'Edit link' => 'Hivatkozás szerkesztése', + 'Start to type task title...' => 'Kezdje el gépelni a feladat címét…', + 'A task cannot be linked to itself' => 'Egy feladatot nem lehet önmagához kapcsolni', + 'The exact same link already exists' => 'Már létezik pontosan ugyanez a hivatkozás', + 'Recurrent task is scheduled to be generated' => 'Az ismétlődő feladat előállítása ütemezve lett', + 'Score' => 'Pontszám', + 'The identifier must be unique' => 'Az azonosítónak egyedinek kell lennie', + 'This linked task id doesn\'t exists' => 'Ez a hivatkozott feladatazonosító nem létezik', + 'This value must be alphanumeric' => 'Ez az érték csak betűket és számokat tartalmazhat', + 'Edit recurrence' => 'Ismétlődés szerkesztése', + 'Generate recurrent task' => 'Ismétlődő feladat előállítása', + 'Trigger to generate recurrent task' => 'Aktiváló az ismétlődő feladat előállításához', + 'Factor to calculate new due date' => 'Tényező az új határidő kiszámításához', + 'Timeframe to calculate new due date' => 'Időablak az új határidő kiszámításához', + 'Base date to calculate new due date' => 'Alapdátum az új határidő kiszámításához', + 'Action date' => 'Tevékenység dátuma', + 'Base date to calculate new due date: ' => 'Alapdátum az új határidő kiszámításához: ', + 'This task has created this child task: ' => 'Ez a feladat ezt a gyermekfeladatot hozta létre: ', + 'Day(s)' => 'Nap', + 'Existing due date' => 'Meglévő határidő', + 'Factor to calculate new due date: ' => 'Tényező az új határidő kiszámításához: ', + 'Month(s)' => 'Hónap', + 'This task has been created by: ' => 'Ezt a feladatot a következő személy hozta létre: ', + 'Recurrent task has been generated:' => 'Ismétlődő feladat lett előállítva: ', + 'Timeframe to calculate new due date: ' => 'Időablak az új határidő kiszámításához: ', + 'Trigger to generate recurrent task: ' => 'Aktiváló az ismétlődő feladat előállításához: ', + 'When task is closed' => 'Ha a feladatok lezárták', + 'When task is moved from first column' => 'Ha a feladatot áthelyezték az első oszlopból', + 'When task is moved to last column' => 'Ha a feladatot áthelyezték az utolsó oszlopba', + 'Year(s)' => 'Év', + 'Project settings' => 'Projekt beállításai', + 'Automatically update the start date' => 'A kezdési dátum automatikus frissítése', + 'iCal feed' => 'iCal hírforrás', + 'Preferences' => 'Beállítások', + 'Security' => 'Biztonság', + 'Two factor authentication disabled' => 'A kétlépcsős hitelesítés letiltva', + 'Two factor authentication enabled' => 'A kétlépcsős hitelesítés engedélyezve', + 'Unable to update this user.' => 'Nem lehet frissíteni a felhasználót.', + 'There is no user management for personal projects.' => 'Nincs felhasználókezelés a személyes projekteknél.', + 'User that will receive the email' => 'A felhasználó, aki megkapja az e-mailt', + 'Email subject' => 'E-mail tárgya', + 'Date' => 'Dátum', + 'Add a comment log when moving the task between columns' => 'Megjegyzésnapló hozzáadása, ha a feladatot az oszlopok között mozgatják', + 'Move the task to another column when the category is changed' => 'Feladat áthelyezése egy másik oszlopba, ha megváltozik a kategória', + 'Send a task by email to someone' => 'Feladat elküldése e-mailben valakinek', + 'Reopen a task' => 'Feladat ismételt megnyitása', + 'Notification' => 'Értesítés', + '%s moved the task #%d to the first swimlane' => '%s áthelyezte a(z) #%d. feladatot az első sávba', + 'Swimlane' => 'Sáv', + '%s moved the task %s to the first swimlane' => '%s áthelyezte a(z) %s feladatot az első sávba', + '%s moved the task %s to the swimlane "%s"' => '%s áthelyezte a(z) %s feladatot a(z) „%s” sávba', + 'This report contains all subtasks information for the given date range.' => 'Ez a jelentés az összes részfeladat-információt tartalmazza az adott dátumtartományban', + 'This report contains all tasks information for the given date range.' => 'Ez a jelentés az összes feladatinformációt tartalmazza az adott dátumtartományban', + 'Project activities for %s' => '%s projekttevékenységei', + 'view the board on Kanboard' => 'a tábla megjelenítése a Kanboardon', + 'The task has been moved to the first swimlane' => 'A feladat át lett helyezve az első sávba', + 'The task has been moved to another swimlane:' => 'A feladat át lett helyezve egy másik sávba:', + 'New title: %s' => 'Új cím: %s', + 'The task is not assigned anymore' => 'A feladatnak többé már nincs felelőse', + 'New assignee: %s' => 'Új felelős: %s', + 'There is no category now' => 'Jelenleg nincs kategória', + 'New category: %s' => 'Új kategória: %s', + 'New color: %s' => 'Új szín: %s', + 'New complexity: %d' => 'Új bonyolultság: %d', + 'The due date has been removed' => 'A határidő eltávolításra került', + 'There is no description anymore' => 'Többé már nincs leírás', + 'Recurrence settings has been modified' => 'Az ismétlődés beállításai módosítva lettek', + 'Time spent changed: %sh' => 'Az eltöltött idő megváltozott: %s óra', + 'Time estimated changed: %sh' => 'A becsült idő megváltozott: %s óra', + 'The field "%s" has been updated' => 'A(z) „%s” mező frissítve lett', + 'The description has been modified:' => 'A leírás módosítva lett:', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Valóban le szeretné zárni a(z) „%s” feladatot, valamint az összes részfeladatot?', + 'I want to receive notifications for:' => 'Értesítéseket szeretnék kapni a következőkről:', + 'All tasks' => 'Összes feladat', + 'Only for tasks assigned to me' => 'Csak a hozzám rendelt feladatok', + 'Only for tasks created by me' => 'Csak az általam létrehozott feladatok', + 'Only for tasks created by me and tasks assigned to me' => 'Csak az általam létrehozott és a hozzám rendelt feladatok', + '%%Y-%%m-%%d' => '%%Y-%%m-%%d', + 'Total for all columns' => 'Az összes oszlop összege', + 'You need at least 2 days of data to show the chart.' => 'Legalább 2 nap adatára van szükség a diagram megjelenítéséhez.', + '<15m' => '<15p', + '<30m' => '<30p', + 'Stop timer' => 'Időmérő leállítása', + 'Start timer' => 'Időmérő elindítása', + 'My activity stream' => 'Saját tevékenységfolyamom', + 'Search tasks' => 'Feladatok keresése', + 'Reset filters' => 'Szűrők visszaállítása', + 'My tasks due tomorrow' => 'Holnapi határidejű feladataim', + 'Tasks due today' => 'Mai határidejű feladatok', + 'Tasks due tomorrow' => 'Holnapi határidejű feladatok', + 'Tasks due yesterday' => 'Tegnapi határidejű feladatok', + 'Closed tasks' => 'Lezárt feladatok', + 'Open tasks' => 'Nyitott feladatok', + 'Not assigned' => 'Nincs hozzárendelve', + 'View advanced search syntax' => 'Speciális keresés szintaxis megtekintése', + 'Overview' => 'Áttekintés', + 'Board/Calendar/List view' => 'Tábla/Naptár/Lista nézet', + 'Switch to the board view' => 'Átváltás tábla nézetre', + 'Switch to the list view' => 'Átváltás lista nézetre', + 'Go to the search/filter box' => 'Ugrás a keresés/szűrés dobozhoz', + 'There is no activity yet.' => 'Még nincs tevékenység.', + 'No tasks found.' => 'Nem található feladat.', + 'Keyboard shortcut: "%s"' => 'Gyorsbillentyű: „%s”', + 'List' => 'Lista', + 'Filter' => 'Szűrő', + 'Advanced search' => 'Speciális keresés', + 'Example of query: ' => 'Lekérdezési példa: ', + 'Search by project: ' => 'Keresés projekt alapján: ', + 'Search by column: ' => 'Keresés oszlop alapján: ', + 'Search by assignee: ' => 'Keresés felelős alapján: ', + 'Search by color: ' => 'Keresés szín alapján: ', + 'Search by category: ' => 'Keresés kategória alapján: ', + 'Search by description: ' => 'Keresés leírás alapján: ', + 'Search by due date: ' => 'Keresés határidő alapján: ', + 'Average time spent in each column' => 'Átlagosan eltöltött idő az egyes oszlopokban', + 'Average time spent' => 'Átlagosan eltöltött idő', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Ez a diagram az egyes oszlopokban átlagosan eltöltött időt jeleníti meg az utolsó %d feladatnál.', + 'Average Lead and Cycle time' => 'Átlagos átfutási és ciklusidő', + 'Average lead time: ' => 'Átlagos átfutási idő: ', + 'Average cycle time: ' => 'Átlagos ciklusidő: ', + 'Cycle Time' => 'Ciklusidő', + 'Lead Time' => 'Átfutási idő', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Ez a diagram az átlagos átfutási és ciklusidőt jeleníti meg az utolsó %d feladatnál az idő függvényében.', + 'Average time into each column' => 'Átlagos idő az egyes oszlopokban', + 'Lead and cycle time' => 'Átfutási és ciklusidő', + 'Lead time: ' => 'Átfutási idő: ', + 'Cycle time: ' => 'Ciklusidő: ', + 'Time spent in each column' => 'Az egyes oszlopokban eltöltött idő', + 'The lead time is the duration between the task creation and the completion.' => 'Az átfutási idő a feladat létrehozása és befejezése közötti időtartam.', + 'The cycle time is the duration between the start date and the completion.' => 'A ciklusidő a kezdési dátum és a befejezés közötti időtartam.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Ha a feladat nincs lezárva, akkor az aktuális idő lesz használva a befejezés dátuma helyett.', + 'Set the start date automatically' => 'A kezdési dátum automatikus beállítása', + 'Edit Authentication' => 'Hitelesítés szerkesztése', + 'Remote user' => 'Távoli felhasználó', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'A távoli felhasználók jelszava nem a Kanboard adatbázisban van tárolva. Példák: LDAP, Google és GitHub fiókok.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Ha bejelöli a „Bejelentkezési űrlap letiltása” jelölőnégyzetet, akkor a bejelentkezési űrlapon megadott hitelesítési adatok figyelmen kívül lesznek hagyva.', + 'Default task color' => 'Alapértelmezett feladatszín', + 'This feature does not work with all browsers.' => 'Ez a funkció nem működik minden böngészőben.', + 'There is no destination project available.' => 'Nem érhető el célprojekt.', + 'Trigger automatically subtask time tracking' => 'A részfeladatok időkövetésének automatikus aktiválása', + 'Include closed tasks in the cumulative flow diagram' => 'A lezárt feladatok is legyenek benne a halmozott folyamdiagramban', + 'Current swimlane: %s' => 'Jelenlegi sáv: %s', + 'Current column: %s' => 'Jelenlegi oszlop: %s', + 'Current category: %s' => 'Jelenlegi kategória: %s', + 'no category' => 'nincs kategória', + 'Current assignee: %s' => 'Jelenlegi felelős: %s', + 'not assigned' => 'nincs kijelölt felelős', + 'Author:' => 'Szerző:', + 'contributors' => 'közreműködők', + 'License:' => 'Licenc:', + 'License' => 'Licenc', + 'Enter the text below' => 'Adja meg a lenti szöveget', + 'Start date:' => 'Kezdési dátum:', + 'Due date:' => 'Határidő:', + 'People who are project managers' => 'Emberek, akik projektvezetők', + 'People who are project members' => 'Emberek, akik projekttagok', + 'NOK - Norwegian Krone' => 'NOK – norvég korona', + 'Show this column' => 'Oszlop megjelenítése', + 'Hide this column' => 'Oszlop elrejtése', + 'End date' => 'Befejezési dátum', + 'Users overview' => 'Felhasználók áttekintése', + 'Members' => 'Tagok', + 'Shared project' => 'Megosztott projekt', + 'Project managers' => 'Projektvezetők', + 'Projects list' => 'Projektek listája', + 'End date:' => 'Befejezési dátum:', + 'Change task color when using a specific task link' => 'Feladatszín megváltoztatása, ha egy adott feladathivatkozást használnak', + 'Task link creation or modification' => 'Feladathivatkozás létrehozása vagy módosítása', + 'Milestone' => 'Mérföldkő', + 'Reset the search/filter box' => 'A keresés/szűrés doboz visszaállítása', + 'Documentation' => 'Dokumentáció', + 'Author' => 'Szerző', + 'Version' => 'Verzió', + 'Plugins' => 'Bővítmények', + 'There is no plugin loaded.' => 'Nincsenek betöltött bővítmények.', + 'My notifications' => 'Saját emlékeztetők', + 'Custom filters' => 'Egyéni szűrők', + 'Your custom filter has been created successfully.' => 'Az egyéni szűrője sikeresen létrehozva.', + 'Unable to create your custom filter.' => 'Nem lehet létrehozni az egyéni szűrőjét.', + 'Custom filter removed successfully.' => 'Az egyéni szűrő sikeresen eltávolítva.', + 'Unable to remove this custom filter.' => 'Nem lehet eltávolítani az egyéni szűrőt.', + 'Edit custom filter' => 'Egyéni szűrő szerkesztése', + 'Your custom filter has been updated successfully.' => 'Az egyéni szűrője sikeresen frissítve.', + 'Unable to update custom filter.' => 'Nem lehet frissíteni az egyéni szűrőt.', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Új melléklet a(z) #%d. feladatnál: %s', + 'New comment on task #%d' => 'Új megjegyzés a(z) #%d. feladatnál', + 'Comment updated on task #%d' => 'A megjegyzés frissítve lett a(z) #%d. feladatnál', + 'New subtask on task #%d' => 'Új részfeladat a(z) #%d. feladatnál', + 'Subtask updated on task #%d' => 'A részfeladat frissítve lett a(z) #%d. feladatnál', + 'New task #%d: %s' => 'Új #%d számú feladat: %s', + 'Task updated #%d' => 'A(z) #%d. feladat frissült', + 'Task #%d closed' => 'A(z) #%d. feladat le lett zárva', + 'Task #%d opened' => 'A(z) #%d. feladat meg lett nyitva', + 'Column changed for task #%d' => 'Az oszlop megváltozott a(z) #%d. feladatnál', + 'New position for task #%d' => 'Új pozíció a(z) #%d. feladatnál', + 'Swimlane changed for task #%d' => 'A sáv megváltozott a(z) #%d. feladatnál', + 'Assignee changed on task #%d' => 'A felelős megváltozott a(z) #%d. feladatnál', + '%d overdue tasks' => '%d lejárt feladat', + 'No notification.' => 'Nincs értesítés.', + 'Mark all as read' => 'Az összes megjelölése olvasottként', + 'Mark as read' => 'Megjelölés olvasottként', + 'Total number of tasks in this column across all swimlanes' => 'Az ebben az oszlopban, az összes sávban lévő feladatok száma', + 'Collapse swimlane' => 'Sáv összecsukása', + 'Expand swimlane' => 'Sáv kinyitása', + 'Add a new filter' => 'Új szűrő hozzáadása', + 'Share with all project members' => 'Megosztás az összes projekttaggal', + 'Shared' => 'Megosztva', + 'Owner' => 'Tulajdonos', + 'Unread notifications' => 'Olvasatlan értesítések', + 'Notification methods:' => 'Értesítési módszerek:', + 'Unable to read your file' => 'Nem lehet beolvasni a fájlt', + '%d task(s) have been imported successfully.' => '%d feladat sikeresen importálva.', + 'Nothing has been imported!' => 'Semmi sem lett importálva!', + 'Import users from CSV file' => 'Felhasználók importálása CSV-fájlból', + '%d user(s) have been imported successfully.' => '%d felhasználó sikeresen importálva.', + 'Comma' => 'Vessző', + 'Semi-colon' => 'Pontosvessző', + 'Tab' => 'Tabulátor', + 'Vertical bar' => 'Függőleges vonal', + 'Double Quote' => 'Idézőjel', + 'Single Quote' => 'Aposztróf', + '%s attached a file to the task #%d' => '%s mellékelt egy fájlt a(z) #%d. feladathoz', + 'There is no column or swimlane activated in your project!' => 'Nincs aktivált oszlop vagy sáv a projektjében!', + 'Append filter (instead of replacement)' => 'Szűrő hozzáfűzése (kicserélés helyett)', + 'Append/Replace' => 'Hozzáfűzés/csere', + 'Append' => 'Hozzáfűzés', + 'Replace' => 'Csere', + 'Import' => 'Importálás', + 'Change sorting' => 'Rendezés megváltoztatása', + 'Tasks Importation' => 'Feladat importálása', + 'Delimiter' => 'Elválasztó', + 'Enclosure' => 'Határoló', + 'CSV File' => 'CSV-fájl', + 'Instructions' => 'Utasítások', + 'Your file must use the predefined CSV format' => 'A fájlnak az előre meghatározott CSV-formátumot kell használnia', + 'Your file must be encoded in UTF-8' => 'A fájlnak UTF-8 kódolásúnak kell lennie', + 'The first row must be the header' => 'Az első sornak fejlécnek kell lennie', + 'Duplicates are not verified for you' => 'Az ismétlődések nincsenek ellenőrizve', + 'The due date must use the ISO format: YYYY-MM-DD' => 'A határidőnek ISO formátumot kell használnia: YYYY-MM-DD', + 'Download CSV template' => 'CSV-sablon letöltése', + 'No external integration registered.' => 'Nincs külső integráció regisztrálva.', + 'Duplicates are not imported' => 'Az ismétlődések nincsenek importálva', + 'Usernames must be lowercase and unique' => 'A felhasználóneveknek kisbetűsnek és egyedinek kell lenniük', + 'Passwords will be encrypted if present' => 'A jelszavak titkosítva lesznek, ha meg vannak adva', + '%s attached a new file to the task %s' => '%s mellékelt egy új fájlt a(z) %s feladathoz', + 'Link type' => 'Hivatkozás típusa', + 'Assign automatically a category based on a link' => 'Kategória automatikus hozzárendelése egy hivatkozás alapján', + 'BAM - Konvertible Mark' => 'BAM – konvertibilis márka', + 'Assignee Username' => 'Felelős felhasználóneve', + 'Assignee Name' => 'Felelős neve', + 'Groups' => 'Csoportok', + 'Members of %s' => '%s tagjai', + 'New group' => 'Új csoport', + 'Group created successfully.' => 'A csoport sikeresen létrehozva.', + 'Unable to create your group.' => 'Nem lehet létrehozni a csoportot.', + 'Edit group' => 'Csoport szerkesztése', + 'Group updated successfully.' => 'A csoport sikeresen frissítve.', + 'Unable to update your group.' => 'Nem lehet frissíteni a csoportot.', + 'Add group member to "%s"' => 'Csoporttag hozzáadása ehhez: „%s”', + 'Group member added successfully.' => 'A csoporttag sikeresen hozzáadva.', + 'Unable to add group member.' => 'Nem lehet hozzáadni a csoporttagot.', + 'Remove user from group "%s"' => 'Felhasználó eltávolítása a(z) „%s” csoportból', + 'User removed successfully from this group.' => 'A felhasználó sikeresen el lett távolítva a csoportból.', + 'Unable to remove this user from the group.' => 'Nem lehet eltávolítani a felhasználót a csoportból.', + 'Remove group' => 'Csoport eltávolítása', + 'Group removed successfully.' => 'A csoport sikeresen eltávolítva.', + 'Unable to remove this group.' => 'Nem lehet eltávolítani a csoportot.', + 'Project Permissions' => 'Projektjogosultságok', + 'Manager' => 'Vezető', + 'Project Manager' => 'Projektvezető', + 'Project Member' => 'Projekttag', + 'Project Viewer' => 'Projektmegtekintő', + 'Your account is locked for %d minutes' => 'A fiókja zárolva lett %d percre', + 'Invalid captcha' => 'Érvénytelen captcha', + 'The name must be unique' => 'A névnek egyedinek kell lennie', + 'View all groups' => 'Az összes csoport megtekintése', + 'There is no user available.' => 'Nincs elérhető felhasználó.', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Valóban el szeretné távolítani „%s” felhasználót a(z) „%s” csoportból?', + 'There is no group.' => 'Nincs csoport.', + 'Add group member' => 'Csoporttag hozzáadása', + 'Do you really want to remove this group: "%s"?' => 'Valóban el szeretné távolítani ezt a csoportot: „%s”?', + 'There is no user in this group.' => 'Nincs felhasználó ebben a csoportban.', + 'Permissions' => 'Jogosultságok', + 'Allowed Users' => 'Engedélyezett felhasználók', + 'No specific user has been allowed.' => 'Egyetlen megadott felhasználó sem lett engedélyezve.', + 'Role' => 'Szerep', + 'Enter user name...' => 'Adja meg a felhasználó nevét…', + 'Allowed Groups' => 'Engedélyezett csoportok', + 'No group has been allowed.' => 'Egyetlen csoport sem lett engedélyezve.', + 'Group' => 'Csoport', + 'Group Name' => 'Csoport neve', + 'Enter group name...' => 'Adja meg a csoport nevét…', + 'Role:' => 'Szerep:', + 'Project members' => 'Projekttagok', + '%s mentioned you in the task #%d' => '%s megemlítette Önt a(z) #%d. feladatban', + '%s mentioned you in a comment on the task #%d' => '%s megemlítette Önt a(z) #%d. feladathoz fűzött megjegyzésben', + 'You were mentioned in the task #%d' => 'Megemlítették Önt a(z) #%d. feladatban', + 'You were mentioned in a comment on the task #%d' => 'Megemlítették Önt a(z) #%d. feladathoz fűzött megjegyzésben', + 'Estimated hours: ' => 'Becsült órák: ', + 'Actual hours: ' => 'Tényleges órák: ', + 'Hours Spent' => 'Eltöltött órák', + 'Hours Estimated' => 'Becsült órák', + 'Estimated Time' => 'Becsült idő', + 'Actual Time' => 'Tényleges idő', + 'Estimated vs actual time' => 'Becsült ↔ tényleges idő', + 'RUB - Russian Ruble' => 'RUB – orosz rubel', + 'Assign the task to the person who does the action when the column is changed' => 'A feladat hozzárendelése ahhoz a személyhez, aki elvégzi a műveletet az oszlop megváltoztatásakor', + 'Close a task in a specific column' => 'Feladat lezárása egy adott oszlopban', + 'Time-based One-time Password Algorithm' => 'Időalapú, egyszer használható jelszó algoritmus', + 'Two-Factor Provider: ' => 'Kétlépcsős hitelesítés szolgáltatója: ', + 'Disable two-factor authentication' => 'Kétlépcsős hitelesítés letiltása', + 'Enable two-factor authentication' => 'Kétlépcsős hitelesítés engedélyezése', + 'There is no integration registered at the moment.' => 'Jelenleg nincs integráció regisztrálva.', + 'Password Reset for Kanboard' => 'Kanboard jelszó visszaállítása', + 'Forgot password?' => 'Elfelejtette a jelszavát?', + 'Enable "Forget Password"' => '„Elfelejtett jelszó” engedélyezése', + 'Password Reset' => 'Jelszó visszaállítása', + 'New password' => 'Új jelszó', + 'Change Password' => 'Jelszó megváltoztatása', + 'To reset your password click on this link:' => 'A jelszó visszaállításához kattintson erre a hivatkozásra:', + 'Last Password Reset' => 'Jelszó alaphelyzetbe állítás utoljára ekkor', + 'The password has never been reinitialized.' => 'A jelszó soha sem volt visszaállítva.', + 'Creation' => 'Létrehozás', + 'Expiration' => 'Lejárat', + 'Password reset history' => 'Jelszó visszaállításának előzményei ', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'A(z) „%s” oszlop és a(z) „%s” sáv összes feladata sikeresen lezárva.', + 'Do you really want to close all tasks of this column?' => 'Valóban le szeretné zárni az oszlop összes feladatát?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d feladat lesz lezárva a(z) „%s” oszlopban és a(z) „%s” sávban.', + 'Close all tasks in this column and this swimlane' => 'Az összes feladat lezárása ebben az oszlopban és ebben a sávban', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Egyetlen bővítmény sem regisztrált projekt értesítési módszert. Egyedi értesítéseket továbbra is beállíthat a felhasználói profiljában.', + 'My dashboard' => 'Saját vezérlőpult', + 'My profile' => 'Saját profil', + 'Project owner: ' => 'Projekttulajdonos: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'A projektazonosító elhagyható, és csak betűk és számok lehetnek, például: MYPROJECT.', + 'Project owner' => 'Projekttulajdonos', + 'Personal projects do not have users and groups management.' => 'A személyes projekteknek nincs felhasználó- és csoportkezelése.', + 'There is no project member.' => 'Nincs projekttag.', + 'Priority' => 'Prioritás', + 'Task priority' => 'Feladat prioritása', + 'General' => 'Általános', + 'Dates' => 'Dátumok', + 'Default priority' => 'Alapértelmezett prioritás', + 'Lowest priority' => 'Legalacsonyabb prioritás', + 'Highest priority' => 'Legmagasabb prioritás', + 'Close a task when there is no activity' => 'Feladat lezárása, ha nincs tevékenység', + 'Duration in days' => 'Időtartam napokban', + 'Send email when there is no activity on a task' => 'E-mail küldése, ha nincs tevékenység egy feladatban', + 'Unable to fetch link information.' => 'Nem lehet lekérni a hivatkozás információit.', + 'Daily background job for tasks' => 'Napi háttérmunka a feladatoknál', + 'Auto' => 'Automatikus', + 'Related' => 'Kapcsolódó', + 'Attachment' => 'Melléklet', + 'Web Link' => 'Webes hivatkozás', + 'External links' => 'Külső hivatkozások', + 'Add external link' => 'Külső hivatkozás hozzáadása', + 'Type' => 'Típus', + 'Dependency' => 'Függőség', + 'Add internal link' => 'Belső hivatkozás hozzáadása', + 'Add a new external link' => 'Új külső hivatkozás hozzáadása', + 'Edit external link' => 'Külső hivatkozás szerkesztése', + 'External link' => 'Külső hivatkozás', + 'Copy and paste your link here...' => 'Másolja ki és illessze be a hivatkozást ide…', + 'URL' => 'URL', + 'Internal links' => 'Belső hivatkozások', + 'Assign to me' => 'Rendelje hozzám', + 'Me' => 'Hozzám', + 'Do not duplicate anything' => 'Ne kettőzzön semmit sem', + 'Projects management' => 'Projektek kezelése', + 'Users management' => 'Felhasználók kezelése', + 'Groups management' => 'Csoportok kezelése', + 'Create from another project' => 'Létrehozás egy másik projektből', + 'open' => 'nyitott', + 'closed' => 'lezárt', + 'Priority:' => 'Prioritás:', + 'Reference:' => 'Hivatkozás:', + 'Complexity:' => 'Bonyolultság:', + 'Swimlane:' => 'Sáv:', + 'Column:' => 'Oszlop:', + 'Position:' => 'Pozíció:', + 'Creator:' => 'Létrehozó:', + 'Time estimated:' => 'Becsült idő:', + '%s hours' => '%s óra', + 'Time spent:' => 'Eltöltött idő:', + 'Created:' => 'Létrehozva:', + 'Modified:' => 'Módosítva:', + 'Completed:' => 'Elkészült:', + 'Started:' => 'Elindult:', + 'Moved:' => 'Áthelyezve:', + 'Task #%d' => '#%d. feladat', + 'Time format' => 'Időformátum', + 'Start date: ' => 'Kezdési dátum: ', + 'End date: ' => 'Befejezési dátum: ', + 'New due date: ' => 'Új határidő: ', + 'Start date changed: ' => 'A kezdési dátum megváltozott: ', + 'Disable personal projects' => 'Személyes projektek letiltása', + 'Do you really want to remove this custom filter: "%s"?' => 'Valóban el szeretné távolítani ezt az egyéni szűrőt: „%s”?', + 'Remove a custom filter' => 'Egyéni szűrő eltávolítása', + 'User activated successfully.' => 'A felhasználó sikeresen aktiválva.', + 'Unable to enable this user.' => 'Nem lehet engedélyezni a felhasználót.', + 'User disabled successfully.' => 'A felhasználó sikeresen letiltva.', + 'Unable to disable this user.' => 'Nem lehet letiltani a felhasználót.', + 'All files have been uploaded successfully.' => 'Az összes fájl sikeresen feltöltve.', + 'The maximum allowed file size is %sB.' => 'A legnagyobb megengedett fájlméret %s bájt.', + 'Drag and drop your files here' => 'Fogd-és-vidd módszerrel húzza ide a fájlokat', + 'choose files' => 'fájlok kiválasztása', + 'View profile' => 'Profil megtekintése', + 'Two Factor' => 'Kétlépcsős', + 'Disable user' => 'Felhasználó letiltása', + 'Do you really want to disable this user: "%s"?' => 'Valóban le szeretné tiltani ezt a felhasználót: „%s”?', + 'Enable user' => 'Felhasználó engedélyezése', + 'Do you really want to enable this user: "%s"?' => 'Valóban engedélyezni szeretné ezt a felhasználót: „%s”?', + 'Download' => 'Letöltés', + 'Uploaded: %s' => 'Feltöltve: %s', + 'Size: %s' => 'Méret: %s', + 'Uploaded by %s' => 'Feltöltötte: %s', + 'Filename' => 'Fájlnév', + 'Size' => 'Méret', + 'Column created successfully.' => 'Az oszlop sikeresen létrehozva.', + 'Another column with the same name exists in the project' => 'Létezik egy ugyanilyen nevű oszlop a projektben', + 'Default filters' => 'Alapértelmezett szűrők', + 'Your board doesn\'t have any columns!' => 'A táblának nincs egyetlen oszlopa sem!', + 'Change column position' => 'Oszlop helyzetének megváltoztatása', + 'Switch to the project overview' => 'Átváltás a projekt áttekintőre', + 'User filters' => 'Felhasználók szűrői', + 'Category filters' => 'Kategóriák szűrői', + 'Upload a file' => 'Fájl feltöltése', + 'View file' => 'Fájl megtekintése', + 'Last activity' => 'Utolsó tevékenység', + 'Change subtask position' => 'Részfeladat helyzetének megváltoztatása', + 'This value must be greater than %d' => 'Ennek az értéknek nagyobbnak kell lennie mint %d', + 'Another swimlane with the same name exists in the project' => 'Létezik egy ugyanilyen nevű sáv a projektben', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Példa: https://example.kanboard.org/ (abszolút URL-ek előállításához használható)', + 'Actions duplicated successfully.' => 'A műveletek sikeresen megkettőzve.', + 'Unable to duplicate actions.' => 'Nem lehet megkettőzni a műveleteket.', + 'Add a new action' => 'Új művelet hozzáadása', + 'Import from another project' => 'Importálás egy másik projektből', + 'There is no action at the moment.' => 'Jelenleg nincs egyetlen művelet sem.', + 'Import actions from another project' => 'Műveletek importálása egy másik projektből', + 'There is no available project.' => 'Nincs elérhető projekt.', + 'Local File' => 'Helyi fájl', + 'Configuration' => 'Konfiguráció', + 'PHP version:' => 'PHP verziója:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'Operációs rendszer verziója:', + 'Database version:' => 'Adatbázis verziója:', + 'Browser:' => 'Böngésző:', + 'Task view' => 'Feladat nézet', + 'Edit task' => 'Feladat szerkesztése', + 'Edit description' => 'Leírás szerkesztése', + 'New internal link' => 'Új belső hivatkozás', + 'Display list of keyboard shortcuts' => 'Gyorsbillentyűk listájának megjelenítése', + 'Avatar' => 'Profilkép', + 'Upload my avatar image' => 'Saját profilkép feltöltése', + 'Remove my image' => 'Saját kép eltávolítása', + 'The OAuth2 state parameter is invalid' => 'Az OAuth2 állapotparaméter érvénytelen', + 'User not found.' => 'A felhasználó nem található.', + 'Search in activity stream' => 'Keresés a tevékenységfolyamban', + 'My activities' => 'Saját tevékenységek', + 'Activity until yesterday' => 'Tevékenységek tegnapig', + 'Activity until today' => 'Tevékenységek a mai napig', + 'Search by creator: ' => 'Keresés a létrehozó szerint: ', + 'Search by creation date: ' => 'Keresés a létrehozás dátuma szerint: ', + 'Search by task status: ' => 'Keresés a feladat állapota szerint: ', + 'Search by task title: ' => 'Keresés a feladat címe szerint: ', + 'Activity stream search' => 'Tevékenységfolyam keresés', + 'Projects where "%s" is manager' => 'Azok a projektek, ahol „%s” vezető', + 'Projects where "%s" is member' => 'Azok a projektek, ahol „%s” tag', + 'Open tasks assigned to "%s"' => '„%s” felhasználóhoz rendelt nyitott feladatok', + 'Closed tasks assigned to "%s"' => '„%s” felhasználóhoz rendelt lezárt feladatok', + 'Assign automatically a color based on a priority' => 'Szín automatikus hozzárendelése prioritás alapján', + 'Overdue tasks for the project(s) "%s"' => 'Lejárt feladatok a(z) „%s” projektnél', + 'Upload files' => 'Fájlok feltöltése', + 'Installed Plugins' => 'Telepített bővítmények', + 'Plugin Directory' => 'Bővítmény könyvtár', + 'Plugin installed successfully.' => 'A bővítmény sikeresen telepítve.', + 'Plugin updated successfully.' => 'A bővítmény sikeresen frissítve.', + 'Plugin removed successfully.' => 'A bővítmény sikeresen eltávolítva.', + 'Subtask converted to task successfully.' => 'A részfeladat sikeresen átalakítva feladattá.', + 'Unable to convert the subtask.' => 'Nem lehet átalakítani a részfeladatot.', + 'Unable to extract plugin archive.' => 'Nem lehet kibontani a bővítmény archívumot.', + 'Plugin not found.' => 'A bővítmény nem található.', + 'You don\'t have the permission to remove this plugin.' => 'Nincs jogosultsága eltávolítani ezt a bővítményt.', + 'Unable to download plugin archive.' => 'Nem lehet letölteni a bővítmény archívumot.', + 'Unable to write temporary file for plugin.' => 'Nem lehet írni az átmeneti fájlt a bővítményhez.', + 'Unable to open plugin archive.' => 'Nem lehet megnyitni a bővítmény archívumot.', + 'There is no file in the plugin archive.' => 'Nincs fájl a bővítmény archívumban.', + 'Create tasks in bulk' => 'Feladatok tömeges létrehozása', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'A Kanboard példánya úgy lett beállítva, hogy ne lehessen bővítményeket telepíteni a felhasználói felületről.', + 'There is no plugin available.' => 'Nincs elérhető bővítmény.', + 'Install' => 'Telepítés', + 'Update' => 'Frissítés', + 'Up to date' => 'Naprakész', + 'Not available' => 'Nem érhető el', + 'Remove plugin' => 'Bővítmény eltávolítása', + 'Do you really want to remove this plugin: "%s"?' => 'Valóban el szeretné távolítani ezt a bővítményt: „%s”?', + 'Uninstall' => 'Eltávolítás', + 'Listing' => 'Listázás', + 'Metadata' => 'Metaadat', + 'Manage projects' => 'Projektek kezelése', + 'Convert to task' => 'Átalakítás feladattá', + 'Convert sub-task to task' => 'Részfeladat átalakítása feladattá', + 'Do you really want to convert this sub-task to a task?' => 'Valóban át szeretné alakítani ezt a részfeladatot feladattá?', + 'My task title' => 'Saját feladatcím', + 'Enter one task by line.' => 'Adjon meg soronként egy feladatot.', + 'Number of failed login:' => 'Sikertelen bejelentkezések száma:', + 'Account locked until:' => 'A fiók zárolva eddig:', + 'Email settings' => 'E-mail beállítások', + 'Email sender address' => 'E-mail küldőcíme', + 'Email transport' => 'E-mail átviteli módja', + 'Webhook token' => 'Webhurok token', + 'Project tags management' => 'Projektcímkék kezelése', + 'Tag created successfully.' => 'A címke sikeresen létrehozva.', + 'Unable to create this tag.' => 'Nem lehet létrehozni a címkét.', + 'Tag updated successfully.' => 'A címke sikeresen frissítve.', + 'Unable to update this tag.' => 'Nem lehet frissíteni a címkét.', + 'Tag removed successfully.' => 'A címke sikeresen eltávolítva.', + 'Unable to remove this tag.' => 'Nem lehet eltávolítani a címkét.', + 'Global tags management' => 'Globális címkék kezelése', + 'Tags' => 'Címkék', + 'Tags management' => 'Címkék kezelése', + 'Add new tag' => 'Új címke hozzáadása', + 'Edit a tag' => 'Címke szerkesztése', + 'Project tags' => 'Projektcímkék', + 'There is no specific tag for this project at the moment.' => 'Jelenleg nincs címke megadva ehhez a projekthez.', + 'Tag' => 'Címke', + 'Remove a tag' => 'Címke eltávolítása', + 'Do you really want to remove this tag: "%s"?' => 'Valóban el szeretné távolítani ezt a címkét: „%s”?', + 'Global tags' => 'Globális címkék', + 'There is no global tag at the moment.' => 'Jelenleg nincs globális címke.', + 'This field cannot be empty' => 'Ez a mező nem lehet üres', + 'Close a task when there is no activity in a specific column' => 'Feladat lezárása, ha nincs tevékenység egy adott oszlopban', + '%s removed a subtask for the task #%d' => '%s eltávolított egy részfeladatot a(z) #%d. feladatból', + '%s removed a comment on the task #%d' => '%s eltávolított egy megjegyzést a(z) #%d. feladatból', + 'Comment removed on task #%d' => 'Megjegyzés eltávolítva a(z) #%d. feladatból', + 'Subtask removed on task #%d' => 'Részfeladat eltávolítva a(z) #%d. feladatból', + 'Hide tasks in this column in the dashboard' => 'Feladatok elrejtése ebben az oszlopban a vezérlőpulton', + '%s removed a comment on the task %s' => '%s eltávolított egy megjegyzést a(z) %s feladatból', + '%s removed a subtask for the task %s' => '%s eltávolított egy részfeladatot a(z) %s feladatból', + 'Comment removed' => 'Megjegyzés eltávolítva', + 'Subtask removed' => 'Részfeladat eltávolítva', + '%s set a new internal link for the task #%d' => '%s beállított egy új belső hivatkozást a(z) #%d. feladathoz', + '%s removed an internal link for the task #%d' => '%s eltávolított egy belső hivatkozást a(z) #%d. feladatból', + 'A new internal link for the task #%d has been defined' => 'Új belső hivatkozás lett meghatározva a(z) #%d. feladathoz', + 'Internal link removed for the task #%d' => 'Belső hivatkozás eltávolítva a(z) #%d. feladatból', + '%s set a new internal link for the task %s' => '%s beállított egy új belső hivatkozást a(z) %s feladathoz', + '%s removed an internal link for the task %s' => '%s eltávolított egy belső hivatkozást a(z) %s feladatból', + 'Automatically set the due date on task creation' => 'A határidő automatikus beállítása a feladat létrehozásakor', + 'Move the task to another column when closed' => 'Feladat áthelyezése egy másik oszlopba, ha lezárják', + 'Move the task to another column when not moved during a given period' => 'Feladat áthelyezése egy másik oszlopba, ha nincs áthelyezve egy adott ideig', + 'Dashboard for %s' => '%s vezérlőpultja', + 'Tasks overview for %s' => 'Feladatok áttekintése: %s', + 'Subtasks overview for %s' => 'Részfeladatok áttekintése: %s', + 'Projects overview for %s' => 'Projektek áttekintése: %s', + 'Activity stream for %s' => '%s tevékenységfolyama', + 'Assign a color when the task is moved to a specific swimlane' => 'Szín hozzárendelése, ha a feladatot áthelyezik egy adott sávba', + 'Assign a priority when the task is moved to a specific swimlane' => 'Prioritás hozzárendelése, ha a feladatot áthelyezik egy adott sávba', + 'User unlocked successfully.' => 'A felhasználó sikeresen feloldva.', + 'Unable to unlock the user.' => 'Nem lehet feloldani a felhasználót.', + 'Move a task to another swimlane' => 'Feladat áthelyezése egy másik sávba', + 'Creator Name' => 'Létrehozó neve', + 'Time spent and estimated' => 'Eltöltött és becsült idő', + 'Move position' => 'Pozíció áthelyezése', + 'Move task to another position on the board' => 'Feladat áthelyezése egy másik helyre a táblán', + 'Insert before this task' => 'Beszúrás a feladat elé', + 'Insert after this task' => 'Beszúrás a feladat után', + 'Unlock this user' => 'Felhasználó feloldása', + 'Custom Project Roles' => 'Egyéni projektszerepek', + 'Add a new custom role' => 'Új egyéni szerep hozzáadása', + 'Restrictions for the role "%s"' => 'Korlátozások a(z) „%s” szerepnél', + 'Add a new project restriction' => 'Új projektkorlátozás hozzáadása', + 'Add a new drag and drop restriction' => 'Új fogd-és-vidd korlátozás hozzáadása', + 'Add a new column restriction' => 'Új oszlopkorlátozás hozzáadása', + 'Edit this role' => 'Szerep szerkesztése', + 'Remove this role' => 'Szerep eltávolítása', + 'There is no restriction for this role.' => 'Nincs korlátozás ennél a szerepnél.', + 'Only moving task between those columns is permitted' => 'Az oszlopok között csak áthelyezés megengedett', + 'Close a task in a specific column when not moved during a given period' => 'Feladat lezárása egy adott oszlopban, ha nem helyezték át egy adott ideig', + 'Edit columns' => 'Oszlopok szerkesztése', + 'The column restriction has been created successfully.' => 'Az oszlopkorlátozás sikeresen létrehozva.', + 'Unable to create this column restriction.' => 'Nem lehet létrehozni az oszlopkorlátozást.', + 'Column restriction removed successfully.' => 'Az oszlopkorlátozás sikeresen eltávolítva.', + 'Unable to remove this restriction.' => 'Nem lehet eltávolítani a korlátozást.', + 'Your custom project role has been created successfully.' => 'Az egyéni projektszerep sikeresen létrehozva.', + 'Unable to create custom project role.' => 'Nem lehet létrehozni az egyéni projektszerepet.', + 'Your custom project role has been updated successfully.' => 'Az egyéni projektszerep sikeresen frissítve.', + 'Unable to update custom project role.' => 'Nem lehet frissíteni az egyéni projektszerepet.', + 'Custom project role removed successfully.' => 'Az egyéni projektszerep sikeresen eltávolítva.', + 'Unable to remove this project role.' => 'Nem lehet eltávolítani a projektszerepet.', + 'The project restriction has been created successfully.' => 'A projektkorlátozás sikeresen létrehozva.', + 'Unable to create this project restriction.' => 'Nem lehet létrehozni a projektkorlátozást.', + 'Project restriction removed successfully.' => 'A projektkorlátozás sikeresen eltávolítva.', + 'You cannot create tasks in this column.' => 'Nem lehet feladatokat létrehozni ebben az oszlopban.', + 'Task creation is permitted for this column' => 'A feladat létrehozása engedélyezett ennél az oszlopnál', + 'Closing or opening a task is permitted for this column' => 'A feladat lezárása vagy megnyitása engedélyezett ennél az oszlopnál', + 'Task creation is blocked for this column' => 'A feladat létrehozása blokkolva van ennél az oszlopnál', + 'Closing or opening a task is blocked for this column' => 'A feladat lezárása vagy megnyitása blokkolva van ennél az oszlopnál', + 'Task creation is not permitted' => 'A feladat létrehozása nem engedélyezett', + 'Closing or opening a task is not permitted' => 'Feladat lezárása vagy megnyitása nem engedélyezett', + 'New drag and drop restriction for the role "%s"' => 'Új fogd-és-vidd korlátozás a(z) „%s” szerephez', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Az ehhez a szerephez tartozó emberek csak a forrás- és a céloszlopok között helyezhetnek át feladatokat.', + 'Remove a column restriction' => 'Oszlopkorlátozás eltávolítása', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Valóban el szeretné távolítani ezt az oszlopkorlátozást: „%s” erre: „%s”?', + 'New column restriction for the role "%s"' => 'Új oszlopkorlátozás a(z) „%s” szerephez', + 'Rule' => 'Szabály', + 'Do you really want to remove this column restriction?' => 'Valóban el szeretné távolítani ezt az oszlopkorlátozást?', + 'Custom roles' => 'Egyéni szerepek', + 'New custom project role' => 'Új egyéni projektszerep', + 'Edit custom project role' => 'Egyéni projektszerep szerkesztése', + 'Remove a custom role' => 'Egyéni szerep eltávolítása', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Valóban el szeretné távolítani ezt az egyéni szerepet: „%s”? A szerephez rendelt összes ember projekttaggá válik.', + 'There is no custom role for this project.' => 'Nincs egyéni szerep ennél a projektnél.', + 'New project restriction for the role "%s"' => 'Új projektkorlátozás a(z) „%s” szerephez', + 'Restriction' => 'Korlátozás', + 'Remove a project restriction' => 'Projektkorlátozás eltávolítása', + 'Do you really want to remove this project restriction: "%s"?' => 'Valóban el szeretné távolítani ezt a projektkorlátozást: „%s”?', + 'Duplicate to multiple projects' => 'Másolás több projektbe', + 'This field is required' => 'Ez a mező kötelező', + 'Moving a task is not permitted' => 'A feladat áthelyezése nem engedélyezett', + 'This value must be in the range %d to %d' => 'Ennek az értéknek %d és %d közötti tartományban kell lennie', + 'You are not allowed to move this task.' => 'A feladat áthelyezése nem engedélyezett.', + 'API User Access' => 'API felhasználói hozzáférés', + 'Preview' => 'Előnézet', + 'Write' => 'Írás', + 'Write your text in Markdown' => 'Írja be a szöveget Markdown formátumban', + 'No personal API access token registered.' => 'Nincs személyes API hozzáférési token regisztrálva.', + 'Your personal API access token is "%s"' => 'A személyes API hozzáférési token: „%s”', + 'Remove your token' => 'Token eltávolítása', + 'Generate a new token' => 'Új token előállítása', + 'Showing %d-%d of %d' => '%d-%d / %d megjelenítése', + 'Outgoing Emails' => 'Kimenő e-mailek', + 'Add or change currency rate' => 'Devizaárfolyam hozzáadása vagy megváltoztatása', + 'Reference currency: %s' => 'Bázis pénznem: %s', + 'Add custom filters' => 'Egyéni szűrők hozzáadása', + 'Export' => 'Exportálás', + 'Add link label' => 'Hivatkozási címke hozzáadása', + 'Incompatible Plugins' => 'Összeférhetetlen bővítmények', + 'Compatibility' => 'Összeférhetőség', + 'Permissions and ownership' => 'Jogosultságok és tulajdonjog', + 'Priorities' => 'Prioritások', + 'Close this window' => 'Ablak bezárása', + 'Unable to upload this file.' => 'Nem lehet feltölteni a fájlt.', + 'Import tasks' => 'Feladatok importálása', + 'Choose a project' => 'Projekt kiválasztása', + 'Profile' => 'Profil', + 'Application role' => 'Alkalmazás szerep', + '%d invitations were sent.' => '%d meghívás elküldve.', + '%d invitation was sent.' => '%d meghívás elküldve.', + 'Unable to create this user.' => 'Nem lehet létrehozni a felhasználót.', + 'Kanboard Invitation' => 'Kanboard meghívás', + 'Visible on dashboard' => 'Látható a vezérlőpulton', + 'Created at:' => 'Létrehozva:', + 'Updated at:' => 'Frissítve:', + 'There is no custom filter.' => 'Nincs egyéni szűrő.', + 'New User' => 'Új felhasználó', + 'Authentication' => 'Hitelesítés', + 'If checked, this user will use a third-party system for authentication.' => 'Ha be van jelölve, akkor ez a felhasználó harmadik fél rendszerét fogja használni a hitelesítéshez.', + 'The password is necessary only for local users.' => 'A jelszó csak helyi felhasználóknak szükséges.', + 'You have been invited to register on Kanboard.' => 'Meghívták, hogy regisztráljon a Kanboardra.', + 'Click here to join your team' => 'Kattintson ide a csapathoz való csatlakozáshoz', + 'Invite people' => 'Emberek meghívása', + 'Emails' => 'E-mailek', + 'Enter one email address by line.' => 'Adjon meg soronként egy e-mail-címet.', + 'Add these people to this project' => 'Az emberek hozzáadása a projekthez', + 'Add this person to this project' => 'A személy hozzáadása a projekthez', + 'Sign-up' => 'Regisztráció', + 'Credentials' => 'Hitelesítési adatok', + 'New user' => 'Új felhasználó', + 'This username is already taken' => 'Ez a felhasználónév már foglalt', + 'Your profile must have a valid email address.' => 'A profiljának érvényes e-mail-címmel kell rendelkeznie.', + 'TRL - Turkish Lira' => 'TRL – török líra', + 'The project email is optional and could be used by several plugins.' => 'A projekt e-mail elhagyható, és több bővítmény is használhatja.', + 'The project email must be unique across all projects' => 'A projekt e-mailnek egyedinek kell lennie a projektek között', + 'The email configuration has been disabled by the administrator.' => 'Az e-mail beállításait letiltotta az adminisztrátor.', + 'Close this project' => 'Projekt bezárása', + 'Open this project' => 'Projekt megnyitása', + 'Close a project' => 'Projekt bezárása', + 'Do you really want to close this project: "%s"?' => 'Valóban le szeretné zárni ezt a projektet: „%s”?', + 'Reopen a project' => 'Projekt újranyitása', + 'Do you really want to reopen this project: "%s"?' => 'Valóban újra szeretné nyitni ezt a projektet: „%s”?', + 'This project is open' => 'A projekt nyitva van', + 'This project is closed' => 'A projekt le van zárva', + 'Unable to upload files, check the permissions of your data folder.' => 'Nem lehet feltölteni fájlokat. Ellenőrizze az adatkönyvtár jogosultságait.', + 'Another category with the same name exists in this project' => 'Már létezik egy ugyanilyen nevű kategória ebben a projektben', + 'Comment sent by email successfully.' => 'A megjegyzés sikeresen elküldve e-mailben.', + 'Sent by email to "%s" (%s)' => 'E-mail küldve neki: „%s” (%s)', + 'Unable to read uploaded file.' => 'Nem lehet beolvasni a feltöltött fájlt.', + 'Database uploaded successfully.' => 'Az adatbázis sikeresen feltöltve.', + 'Task sent by email successfully.' => 'A feladat sikeresen elküldve e-mailben.', + 'There is no category in this project.' => 'Nincs kategória ebben a projektben.', + 'Send by email' => 'Küldés e-mailben', + 'Create and send a comment by email' => 'Megjegyzés létrehozása és küldése e-mailben', + 'Subject' => 'Tárgy', + 'Upload the database' => 'Az adatbázis feltöltése', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Feltöltheti az előzőleg letöltött Sqlite adatbázist (Gzip formátumban).', + 'Database file' => 'Adatbázisfájl', + 'Upload' => 'Feltöltés', + 'Your project must have at least one active swimlane.' => 'A projektnek legalább egy aktív sávval kell rendelkeznie.', + 'Project: %s' => 'Projekt: %s', + 'Automatic action not found: "%s"' => 'Az automatikus művelet nem található: „%s”', + '%d projects' => '%d projekt', + '%d project' => '%d projekt', + 'There is no project.' => 'Nincs projekt.', + 'Sort' => 'Rendezés', + 'Project ID' => 'Projektazonosító', + 'Project name' => 'Projektnév', + 'Public' => 'Nyilvános', + 'Personal' => 'Személyes', + '%d tasks' => '%d feladat', + '%d task' => '%d feladat', + 'Task ID' => 'Feladatazonosító', + 'Assign automatically a color when due date is expired' => 'Szín automatikus hozzárendelése, ha a határidő lejárt', + 'Total score in this column across all swimlanes' => 'Összes pontszám ebben az oszlopban minden sávból', + 'HRK - Kuna' => 'HRK – horvát kuna', + 'ARS - Argentine Peso' => 'ARS – argentin peso', + 'COP - Colombian Peso' => 'COP – kolumbiai peso', + '%d groups' => '%d csoport', + '%d group' => '%d csoport', + 'Group ID' => 'Csoportazonosító', + 'External ID' => 'Külső azonosító', + '%d users' => '%d felhasználó', + '%d user' => '%d felhasználó', + 'Hide subtasks' => 'Részfeladatok elrejtése', + 'Show subtasks' => 'Részfeladatok megjelenítése', + 'Authentication Parameters' => 'Hitelesítési paraméterek', + 'API Access' => 'API hozzáférés', + 'No users found.' => 'Nem találhatók felhasználók.', + 'User ID' => 'Felhasználó-azonosító', + 'Notifications are activated' => 'Értesítések aktiválva', + 'Notifications are disabled' => 'Értesítések letiltva', + 'User disabled' => 'Felhasználó letiltva', + '%d notifications' => '%d értesítés', + '%d notification' => '%d értesítés', + 'There is no external integration installed.' => 'Nincs külső integráció telepítve.', + 'You are not allowed to update tasks assigned to someone else.' => 'Nem engedélyezett a valaki máshoz rendelt feladatok frissítése.', + 'You are not allowed to change the assignee.' => 'Nem engedélyezett a felelős megváltoztatása.', + 'Task suppression is not permitted' => 'A feladatok eltávolítása nem engedélyezett', + 'Changing assignee is not permitted' => 'A felelős megváltoztatása nem engedélyezett', + 'Update only assigned tasks is permitted' => 'Csak a hozzárendelt feladatok frissítése engedélyezett', + 'Only for tasks assigned to the current user' => 'Csak az aktuális felhasználóhoz rendelt feladatoknál', + 'My projects' => 'Saját projektek', + 'You are not a member of any project.' => 'Ön nem tagja egyetlen projektnek sem.', + 'My subtasks' => 'Saját részfeladatok', + '%d subtasks' => '%d részfeladat', + '%d subtask' => '%d részfeladat', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Az aktuális felhasználóhoz rendelt feladatok csak az engedélyezett oszlopok között helyezhetők át', + '[DUPLICATE]' => '[KETTŐZÖTT]', + 'DKK - Danish Krona' => 'DKK – dán korona', + 'Remove user from group' => 'Felhasználó eltávolítása a csoportból', + 'Assign the task to its creator' => 'Feladat hozzárendelése a létrehozójához', + 'This task was sent by email to "%s" with subject "%s".' => 'A feladat elküldésre került e-mailben ide: „%s”, a következő tárggyal: „%s”.', + 'Predefined Email Subjects' => 'Előre meghatározott e-mail tárgyak', + 'Write one subject by line.' => 'Írjon be soronként egy tárgyat.', + 'Create another link' => 'Másik hivatkozás létrehozása', + 'BRL - Brazilian Real' => 'BRL – brazil real', + 'Add a new Kanboard task' => 'Új Kanboard feladat hozzáadása', + 'Subtask not started' => 'A részfeladat nincs elindítva', + 'Subtask currently in progress' => 'A részfeladat jelenleg folyamatban van', + 'Subtask completed' => 'A részfeladat befejeződött', + 'Subtask added successfully.' => 'A részfeladat sikeresen hozzáadva.', + '%d subtasks added successfully.' => '%d részfeladat sikeresen hozzáadva.', + 'Enter one subtask by line.' => 'Adjon meg soronként egy részfeladatot.', + 'Predefined Contents' => 'Előre meghatározott tartalmak', + 'Predefined contents' => 'Előre meghatározott tartalmak', + 'Predefined Task Description' => 'Előre meghatározott feladatleírás', + 'Do you really want to remove this template? "%s"' => 'Valóban el szeretné távolítani ezt a sablont: „%s”?', + 'Add predefined task description' => 'Előre meghatározott feladatleírás hozzáadása', + 'Predefined Task Descriptions' => 'Előre meghatározott feladatleírások', + 'Template created successfully.' => 'A sablon sikeresen létrehozva.', + 'Unable to create this template.' => 'Nem lehet létrehozni a sablont.', + 'Template updated successfully.' => 'A sablon sikeresen frissítve.', + 'Unable to update this template.' => 'Nem lehet frissíteni a sablont.', + 'Template removed successfully.' => 'A sablon sikeresen eltávolítva.', + 'Unable to remove this template.' => 'Nem lehet eltávolítani a sablont.', + 'Template for the task description' => 'Sablon a feladatleíráshoz', + 'The start date is greater than the end date' => 'A kezdési dátum nagyobb mint a befejezési dátum', + 'Tags must be separated by a comma' => 'A címkéket vesszővel kell elválasztani', + 'Only the task title is required' => 'Csak a feladat címe kötelező', + 'Creator Username' => 'Létrehozó felhasználóneve', + 'Color Name' => 'Szín neve', + 'Column Name' => 'Oszlop neve', + 'Swimlane Name' => 'Sáv neve', + 'Time Estimated' => 'Becsült idő', + 'Time Spent' => 'Eltöltött idő', + 'External Link' => 'Külső hivatkozás', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Ez a funkció engedélyezi az iCal hírforrást, az RSS hírforrást és a nyilvános táblanézetet.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Az összes részfeladat időmérőjének leállítása, ha a feladatot egy másik oszlopba helyezik át', + 'Subtask Title' => 'Részfeladat címe', + 'Add a subtask and activate the timer when moving a task to another column' => 'Részfeladat hozzáadása és az időmérő bekapcsolása, ha a feladatot egy másik oszlopba helyezik át', + 'days' => 'nap', + 'minutes' => 'perc', + 'seconds' => 'másodperc', + 'Assign automatically a color when preset start date is reached' => 'Szín automatikus hozzárendelése, ha az előre beállított kezdési dátumot elért', + 'Move the task to another column once a predefined start date is reached' => 'Feladat áthelyezése egy másik oszlopba, ha egy előre meghatározott kezdési dátumot elért', + 'This task is now linked to the task %s with the relation "%s"' => 'Ez a feladat mostantól hozzá van kapcsolva a(z) %s feladathoz „%s” kapcsolattal', + 'The link with the relation "%s" to the task %s has been removed' => 'A(z) „%s” kapcsolattal rendelkező összekapcsolás a(z) %s feladatnál el lett távolítva', + 'Custom Filter:' => 'Egyéni szűrő:', + 'Unable to find this group.' => 'Nem található ez a csoport.', + '%s moved the task #%d to the column "%s"' => '%s áthelyezte a(z) #%d. feladatot a következő oszlopba: „%s”', + '%s moved the task #%d to the position %d in the column "%s"' => '%s áthelyezte a(z) #%d. feladatot a(z) %d. pozícióba a következő oszlopban: „%s”', + '%s moved the task #%d to the swimlane "%s"' => '%s áthelyezte a(z) #%d. feladatot a következő sávba: „%s”', + '%sh spent' => '%s óra eltöltve', + '%sh estimated' => '%s óra becsülve', + 'Select All' => 'Összes kijelölése', + 'Unselect All' => 'Összes kijelölésének megszüntetése', + 'Apply action' => 'Művelet alkalmazása', + 'Move selected tasks to another column or swimlane' => 'Kijelölt feladatok áthelyezése egy másik oszlopba vagy sávba', + 'Edit tasks in bulk' => 'Feladatok tömeges szerkesztése', + 'Choose the properties that you would like to change for the selected tasks.' => 'Válassza ki azokat a tulajdonságokat, amelyeket meg szeretne változtatni a kijelölt feladatoknál.', + 'Configure this project' => 'A projekt beállítása', + 'Start now' => 'Kezdés most', + '%s removed a file from the task #%d' => '%s eltávolított egy fájlt a(z) #%d. feladatból', + 'Attachment removed from task #%d: %s' => 'Melléklet eltávolítva a(z) #%d. feladatból: %s', + 'No color' => 'Nincs szín', + 'Attachment removed "%s"' => 'Melléklet eltávolítva: „%s”', + '%s removed a file from the task %s' => '%s eltávolított egy fájlt a(z) %s feladatból', + 'Move the task to another swimlane when assigned to a user' => 'Feladat áthelyezése egy másik sávba, ha hozzárendelésre kerül egy felhasználóhoz', + 'Destination swimlane' => 'Célsáv', + 'Assign a category when the task is moved to a specific swimlane' => 'Kategória hozzárendelése, ha a feladatot egy bizonyos sávba helyezik át', + 'Move the task to another swimlane when the category is changed' => 'Feladat áthelyezése egy másik sávba, ha a kategória megváltozik', + 'Reorder this column by priority (ASC)' => 'Oszlop átrendezése prioritás szerint (növekvő)', + 'Reorder this column by priority (DESC)' => 'Oszlop átrendezése prioritás szerint (csökkenő)', + 'Reorder this column by assignee and priority (ASC)' => 'Oszlop átrendezése felelős és prioritás szerint (növekvő)', + 'Reorder this column by assignee and priority (DESC)' => 'Oszlop átrendezése felelős és prioritás szerint (csökkenő)', + 'Reorder this column by assignee (A-Z)' => 'Oszlop átrendezése felelős szerint (A-Z)', + 'Reorder this column by assignee (Z-A)' => 'Oszlop átrendezése felelős szerint (Z-A)', + 'Reorder this column by due date (ASC)' => 'Oszlop átrendezése határidő szerint (növekvő)', + 'Reorder this column by due date (DESC)' => 'Oszlop átrendezése határidő szerint (csökkenő)', + 'Reorder this column by id (ASC)' => 'Oszlop átrendezése azonosító szerint (növekvő)', + 'Reorder this column by id (DESC)' => 'Oszlop átrendezése azonosító szerint (csökkenő)', + '%s moved the task #%d "%s" to the project "%s"' => '%s áthelyezte a(z) #%d. „%s” feladatot ebbe a projektbe: „%s”', + 'Task #%d "%s" has been moved to the project "%s"' => 'A(z) #%d. „%s” feladat áthelyezésre került ebbe a projektbe: „%s”', + 'Move the task to another column when the due date is less than a certain number of days' => 'A feladat áthelyezése egy másik oszlopba, ha a határidő kevesebb egy bizonyos számú napnál', + 'Automatically update the start date when the task is moved away from a specific column' => 'A kezdési dátum automatikus frissítése, ha a feladatot áthelyezték egy bizonyos oszlopból', + 'HTTP Client:' => 'HTTP ügyfél:', + 'Assigned' => 'Hozzárendelve', + 'Task limits apply to each swimlane individually' => 'A feladatkorlátok alkalmazása az egyes sávokra egyénileg', + 'Column task limits apply to each swimlane individually' => 'Az oszlop feladatkorlátjainak alkalmazása az egyes sávokra egyénileg', + 'Column task limits are applied to each swimlane individually' => 'Az oszlop feladatkorlátjai alkalmazva lettek az egyes sávokra egyénileg', + 'Column task limits are applied across swimlanes' => 'Az oszlop feladatkorlátjai alkalmazva lettek a sávok között', + 'Task limit: ' => 'Feladatkorlát: ', + 'Change to global tag' => 'Megváltoztatás globális címkére', + 'Do you really want to make the tag "%s" global?' => 'Valóban globálissá szeretné tenni a(z) „%s” címkét?', + 'Enable global tags for this project' => 'Globális címkék engedélyezése ennél a projektnél', + 'Group membership(s):' => 'Csoporttagságok:', + '%s is a member of the following group(s): %s' => '%s a következő csoportok tagja: %s', + '%d/%d group(s) shown' => '%d/%d csoport megjelenítve', + 'Subtask creation or modification' => 'Részfeladat létrehozása vagy módosítása', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'A feladat hozzárendelése egy adott felhasználóhoz, ha a feladatot áthelyezik egy adott sávba', + 'Comment' => 'Megjegyzés', + 'Collapse vertically' => 'Összecsukás függőlegesen', + 'Expand vertically' => 'Kinyitás függőlegesen', + 'MXN - Mexican Peso' => 'MXN – mexikói peso', + 'Estimated vs actual time per column' => 'Becsült és tényleges idő oszloponként', + 'HUF - Hungarian Forint' => 'HUF – magyar forint', + 'XBT - Bitcoin' => 'XBT – bitcoin', + 'You must select a file to upload as your avatar!' => 'Ki kell választania egy fájlt a profilképként való feltöltéshez!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'A feltöltött fájl nem érvényes kép! Csak *.gif, *.jpg, *.jpeg és *.png engedélyezett.', + 'Automatically set the due date when the task is moved away from a specific column' => 'A határidő automatikus beállítása, ha a feladatot áthelyezik egy adott oszlopból', + 'No other projects found.' => 'Nem találhatók más projektek.', + 'Tasks copied successfully.' => 'A feladatok sikeresen másolva.', + 'Unable to copy tasks.' => 'Nem lehet másolni a feladatokat', + 'Theme' => 'Téma', + 'Theme:' => 'Téma:', + 'Light theme' => 'Világos téma', + 'Dark theme' => 'Sötét téma', + 'Automatic theme - Sync with system' => 'Automatikus téma – szinkronizálás a rendszerrel', + 'Application managers or more' => 'Alkalmazáskezelők vagy továbbiak', + 'Administrators' => 'Adminisztrátorok', + 'Visibility:' => 'Láthatóság', + 'Standard users' => 'Szabványos felhasználók', + 'Visibility is required' => 'A láthatóság kötelező', + 'The visibility should be an app role' => 'A láthatóságnak alkalmazásszerepnek kell lennie', + 'Reply' => 'Válasz', + '%s wrote: ' => '%s írta: ', + 'Number of visible tasks in this column and swimlane' => 'Látható feladatok száma ebben az oszlopban és sávban', + 'Number of tasks in this swimlane' => 'Feladatok száma ebben a sávban', + 'Unable to find another subtask in progress, you can close this window.' => 'Nem található másik folyamatban lévő alerőforrás, bezárhatja ezt az ablakot.', + 'This theme is invalid' => 'Ez a téma érvénytelen', + 'This role is invalid' => 'Ez a szerepkör érvénytelen', + 'This timezone is invalid' => 'Ez az időzóna érvénytelen', + 'This language is invalid' => 'Ez a nyelv érvénytelen', + 'This URL is invalid' => 'Ez az URL érvénytelen', + 'Date format invalid' => 'Érvénytelen dátumformátum', + 'Time format invalid' => 'Érvénytelen időformátum', + 'Invalid Mail transport' => 'Érvénytelen levélátvitel', + 'Color invalid' => 'Érvénytelen szín', + 'This value must be greater or equal to %d' => 'Ennek az értéknek nagyobbnak vagy egyenlőnek kell lennie, mint %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'BOM hozzáadása a fájl elejéhez (kötelező a Microsoft Excel számára)', + 'Just add these tag(s)' => 'Csak adja hozzá ezeket a címkéket', + 'Remove internal link(s)' => 'Belső hivatkozás(ok) eltávolítása', + 'Import tasks from another project' => 'Feladatok importálása másik projektből', + 'Select the project to copy tasks from' => 'Válassza ki a projektet, ahonnan a feladatokat másolni szeretné', + 'The total maximum allowed attachments size is %sB.' => 'A mellékletek maximális megengedett mérete %sB.', + 'Add attachments' => 'Mellékletek hozzáadása', + 'Task #%d "%s" is overdue' => 'Zadatak #%d "%s" je s iztečen rok', + 'Enable notifications by default for all new users' => 'Értesítések alapértelmezett engedélyezése minden új felhasználó számára', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'A feladat hozzárendelése a létrehozójához meghatározott oszlopokban, ha nincs kézzel beállított felelős', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'A feladat hozzárendelése a bejelentkezett felhasználóhoz oszlopváltáskor a megadott oszlopba, ha nincs hozzárendelt felhasználó', +]; diff --git a/app/Locale/id_ID/translations.php b/app/Locale/id_ID/translations.php new file mode 100644 index 0000000..a85534e --- /dev/null +++ b/app/Locale/id_ID/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => ' ', + 'None' => 'Tidak satupun', + 'Edit' => 'Edit', + 'Remove' => 'Hapus', + 'Yes' => 'Ya', + 'No' => 'Tidak', + 'cancel' => 'batal', + 'or' => 'atau', + 'Yellow' => 'Kuning', + 'Blue' => 'Biru', + 'Green' => 'Hijau', + 'Purple' => 'Ungu', + 'Red' => 'Merah', + 'Orange' => 'Jingga', + 'Grey' => 'Abu-abu', + 'Brown' => 'Coklat', + 'Deep Orange' => 'Oranye', + 'Dark Grey' => 'Abu-abu Gelap', + 'Pink' => 'Merah Muda', + 'Teal' => 'Teal', + 'Cyan' => 'Sian', + 'Lime' => 'Lime', + 'Light Green' => 'Hijau Muda', + 'Amber' => 'Amber', + 'Save' => 'Simpan', + 'Login' => 'Masuk', + 'Official website:' => 'Situs resmi:', + 'Unassigned' => 'Belum ditugaskan', + 'View this task' => 'Lihat tugas ini', + 'Remove user' => 'Hapus pengguna', + 'Do you really want to remove this user: "%s"?' => 'Anda yakin mau menghapus pengguna ini: "%s"?', + 'All users' => 'Semua pengguna', + 'Username' => 'Nama pengguna', + 'Password' => 'Kata sandi', + 'Administrator' => 'Administrator', + 'Sign in' => 'Masuk', + 'Users' => 'Pengguna', + 'Forbidden' => 'Terlarang', + 'Access Forbidden' => 'Akses Dilarang', + 'Edit user' => 'Edit pengguna', + 'Logout' => 'Keluar', + 'Bad username or password' => 'Nama pengguna atau password salah', + 'Edit project' => 'Edit proyek', + 'Name' => 'Nama', + 'Projects' => 'Proyek', + 'No project' => 'Tidak ada proyek', + 'Project' => 'Proyek', + 'Status' => 'Status', + 'Tasks' => 'Tugas', + 'Board' => 'Papan', + 'Actions' => 'Tindakan', + 'Inactive' => 'Non Aktif', + 'Active' => 'Aktif', + 'Unable to update this board.' => 'Tidak dapat memperbarui papan ini', + 'Disable' => 'Nonaktifkan', + 'Enable' => 'Aktifkan', + 'New project' => 'Proyek baru', + 'Do you really want to remove this project: "%s"?' => 'Apakah Anda yakin mau menghapus proyek ini: "%s"?', + 'Remove project' => 'Hapus proyek', + 'Edit the board for "%s"' => 'Edit papan untuk "%s"', + 'Add a new column' => 'Tambah kolom baru', + 'Title' => 'Judul', + 'Assigned to %s' => 'Ditugaskan kepada %s', + 'Remove a column' => 'Hapus kolom', + 'Unable to remove this column.' => 'Tidak dapat menghapus kolom ini.', + 'Do you really want to remove this column: "%s"?' => 'Apakah Anda yakin mau menghapus kolom ini: "%s"?', + 'Settings' => 'Pengaturan', + 'Application settings' => 'Pengaturan aplikasi', + 'Language' => 'Bahasa', + 'Webhook token:' => 'Token Webhook:', + 'API token:' => 'Token API:', + 'Database size:' => 'Ukuran database:', + 'Download the database' => 'Unduh database', + 'Optimize the database' => 'Optimasi database', + '(VACUUM command)' => '(Perintah VACUUM)', + '(Gzip compressed Sqlite file)' => '(File Sqlite yang terkompress Gzip)', + 'Close a task' => 'Tutup tugas', + 'Column' => 'Kolom', + 'Color' => 'Warna', + 'Assignee' => 'Orang yang ditugaskan', + 'Create another task' => 'Buat tugas lain', + 'New task' => 'Tugas baru', + 'Open a task' => 'Buka tugas', + 'Do you really want to open this task: "%s"?' => 'Apakah Anda yakin mau membuka tugas ini: "%s"?', + 'Back to the board' => 'Kembali ke papan', + 'There is nobody assigned' => 'Tidak ada orang yand ditugaskan', + 'Column on the board:' => 'Kolom di dalam papan:', + 'Close this task' => 'Tutup tugas ini', + 'Open this task' => 'Buka tugas ini', + 'There is no description.' => 'Tidak ada deskripsi.', + 'Add a new task' => 'Tambah tugas baru', + 'The username is required' => 'Nama pengguna dibutuhkan', + 'The maximum length is %d characters' => 'Panjang maksimum adalah %d karakter', + 'The minimum length is %d characters' => 'Panjang minimum adalah %d karakter', + 'The password is required' => 'Password dibutuhkan', + 'This value must be an integer' => 'Nilai ini harus integer', + 'The username must be unique' => 'Nama pengguna harus unik', + 'The user id is required' => 'ID pengguna diperlukan', + 'Passwords don\'t match' => 'Password tidak cocok', + 'The confirmation is required' => 'Konfirmasi diperlukan', + 'The project is required' => 'Proyek diperlukan', + 'The id is required' => 'ID diperlukan', + 'The project id is required' => 'ID proyek diperlukan', + 'The project name is required' => 'Nama proyek diperlukan', + 'The title is required' => 'Judul diperlukan', + 'Settings saved successfully.' => 'Pengaturan berhasil disimpan.', + 'Unable to save your settings.' => 'Tidak dapat menyimpan pengaturan anda.', + 'Database optimization done.' => 'Optimasi database selesai.', + 'Your project has been created successfully.' => 'Proyek anda berhasil dibuat.', + 'Unable to create your project.' => 'Tidak dapat membuat proyek anda.', + 'Project updated successfully.' => 'Proyek berhasil diperbarui.', + 'Unable to update this project.' => 'Tidak dapat memperbarui proyek ini.', + 'Unable to remove this project.' => 'Tidak dapat menghapus proyek ini.', + 'Project removed successfully.' => 'Proyek berhasil dihapus.', + 'Project activated successfully.' => 'Proyek berhasil diaktifkan.', + 'Unable to activate this project.' => 'Tidak dapat mengaktifkan proyek ini.', + 'Project disabled successfully.' => 'Proyek berhasil dinonaktifkan.', + 'Unable to disable this project.' => 'Tidak dapat menonaktifkan proyek ini.', + 'Unable to open this task.' => 'Tidak dapat membuka tugas ini.', + 'Task opened successfully.' => 'Tugas berhasil dibuka.', + 'Unable to close this task.' => 'Tidak dapat menutup tugas ini.', + 'Task closed successfully.' => 'Tugas berhasil ditutup.', + 'Unable to update your task.' => 'Tidak dapat memperbarui tugas ini.', + 'Task updated successfully.' => 'Tugas berhasil diperbarui.', + 'Unable to create your task.' => 'Tidak dapat membuat tugas anda.', + 'Task created successfully.' => 'Tugas berhasil dibuat.', + 'User created successfully.' => 'Pengguna berhasil dibuat.', + 'Unable to create your user.' => 'Tidak dapat membuat pengguna Anda.', + 'User updated successfully.' => 'Pengguna berhasil diperbarui.', + 'User removed successfully.' => 'Pengguna berhasil dihapus.', + 'Unable to remove this user.' => 'Tidak dapat menghapus pengguna ini.', + 'Board updated successfully.' => 'Papan berhasil diperbaharui.', + 'Ready' => 'Siap', + 'Backlog' => 'Tertunda', + 'Work in progress' => 'Sedang dalam pengerjaan', + 'Done' => 'Selesai', + 'Application version:' => 'Versi aplikasi:', + 'Id' => 'ID', + 'Public link' => 'Tautan publik', + 'Timezone' => 'Zona waktu', + 'Sorry, I didn\'t find this information in my database!' => 'Maaf, saya tidak dapat menemukan informasi ini dalam database saya!', + 'Page not found' => 'Halaman tidak ditemukan', + 'Complexity' => 'Kompleksitas', + 'Task limit' => 'Batas tugas', + 'Task count' => 'Jumlah tugas', + 'User' => 'Pengguna', + 'Comments' => 'Komentar', + 'Comment is required' => 'Komentar dibutuhkan', + 'Comment added successfully.' => 'Komentar berhasil ditambahkan.', + 'Unable to create your comment.' => 'Tidak dapat menambahkan komentar Anda.', + 'Due Date' => 'Batas Tanggal Terakhir', + 'Invalid date' => 'Tanggal tidak sesuai', + 'Automatic actions' => 'Tindakan otomatis', + 'Your automatic action has been created successfully.' => 'Tindakan otomatis Anda berhasil dibuat.', + 'Unable to create your automatic action.' => 'Tidak dapat membuat tindakan otomatis Anda.', + 'Remove an action' => 'Hapus tindakan', + 'Unable to remove this action.' => 'Tidak dapat menghapus tindakan ini.', + 'Action removed successfully.' => 'Tindakan berhasil dihapus.', + 'Automatic actions for the project "%s"' => 'Tindakan otomatis untuk proyek ini "%s"', + 'Add an action' => 'Tambah tindakan', + 'Event name' => 'Nama acara', + 'Action' => 'Tindakan', + 'Event' => 'Acara', + 'When the selected event occurs execute the corresponding action.' => 'Ketika acara yang dipilih terjadi, tindakan yang berhubungan dengan acara akan dieksekusi.', + 'Next step' => 'Langkah selanjutnya', + 'Define action parameters' => 'Definisi parameter tindakan', + 'Do you really want to remove this action: "%s"?' => 'Apakah Anda yakin mau menghapus tindakan ini: "%s"?', + 'Remove an automatic action' => 'Hapus tindakan otomatis', + 'Assign the task to a specific user' => 'Berikan tugas pada pengguna tertentu', + 'Assign the task to the person who does the action' => 'Berikan tugas pada orang yang melakukan tindakan', + 'Duplicate the task to another project' => 'Duplikasi tugas ke proyek lain', + 'Move a task to another column' => 'Pindahkan tugas ke kolom lain', + 'Task modification' => 'Modifikasi tugas', + 'Task creation' => 'Membuat tugas', + 'Closing a task' => 'Menutup tugas', + 'Assign a color to a specific user' => 'Menetapkan warna untuk pengguna tertentu', + 'Position' => 'Posisi', + 'Duplicate to project' => 'Duplikasi ke proyek lain', + 'Duplicate' => 'Duplikat', + 'Link' => 'tautan', + 'Comment updated successfully.' => 'Komentar berhasil diperbarui.', + 'Unable to update your comment.' => 'Tidak dapat memperbarui komentar Anda.', + 'Remove a comment' => 'Hapus komentar', + 'Comment removed successfully.' => 'Komentar berhasil dihapus.', + 'Unable to remove this comment.' => 'Tidak dapat menghapus komentar ini.', + 'Do you really want to remove this comment?' => 'Apakah Anda yakin mau menghapus komentar ini?', + 'Current password for the user "%s"' => 'Password saat ini untuk pengguna "%s"', + 'The current password is required' => 'Password saat ini diperlukan', + 'Wrong password' => 'Password salah', + 'Unknown' => 'Tidak diketahui', + 'Last logins' => 'Masuk terakhir', + 'Login date' => 'Tanggal masuk', + 'Authentication method' => 'Metode otentifikasi', + 'IP address' => 'Alamat IP', + 'User agent' => 'Agen pengguna', + 'Persistent connections' => 'Koneksi tetap', + 'No session.' => 'Tidak ada sesi.', + 'Expiration date' => 'Tanggal kadaluarsa', + 'Remember Me' => 'Ingat Saya', + 'Creation date' => 'Tanggal pembuatan', + 'Everybody' => 'Semua orang', + 'Open' => 'Terbuka', + 'Closed' => 'Ditutup', + 'Search' => 'Cari', + 'Nothing found.' => 'Tidak ditemukan.', + 'Due date' => 'Batas tanggal terakhir', + 'Description' => 'Deskripsi', + '%d comments' => '%d komentar', + '%d comment' => '%d komentar', + 'Email address invalid' => 'Alamat email tidak sesuai', + 'Your external account is not linked anymore to your profile.' => 'Akun eksternal Anda tidak lagi terhubung ke profil anda.', + 'Unable to unlink your external account.' => 'Tidak dapat memutuskan akun eksternal Anda.', + 'External authentication failed' => 'Otentifikasi eksternal gagal', + 'Your external account is linked to your profile successfully.' => 'Akun eksternal Anda berhasil dihubungkan ke profil anda.', + 'Email' => 'Email', + 'Task removed successfully.' => 'Tugas berhasil dihapus.', + 'Unable to remove this task.' => 'Tidak dapat menghapus tugas ini.', + 'Remove a task' => 'Hapus tugas', + 'Do you really want to remove this task: "%s"?' => 'Apakah Anda yakin mau menghapus tugas ini: "%s"?', + 'Assign automatically a color based on a category' => 'Otomatis menetapkan warna berdasarkan kategori', + 'Assign automatically a category based on a color' => 'Otomatis menetapkan kategori berdasarkan warna', + 'Task creation or modification' => 'Tugas dibuat atau di modifikasi', + 'Category' => 'Kategori', + 'Category:' => 'Kategori:', + 'Categories' => 'Kategori', + 'Your category has been created successfully.' => 'Kategori Anda berhasil dibuat.', + 'This category has been updated successfully.' => 'Kategori Anda berhasil diperbarui.', + 'Unable to update this category.' => 'Tidak dapat memperbarui kategori Anda.', + 'Remove a category' => 'Hapus kategori', + 'Category removed successfully.' => 'Kategori berhasil dihapus.', + 'Unable to remove this category.' => 'Tidak dapat menghapus kategori ini.', + 'Category modification for the project "%s"' => 'Modifikasi kategori untuk proyek "%s"', + 'Category Name' => 'Nama Kategori', + 'Add a new category' => 'Tambah kategori baru', + 'Do you really want to remove this category: "%s"?' => 'Apakah Anda yakin mau menghapus kategori ini: "%s"?', + 'All categories' => 'Semua kategori', + 'No category' => 'Tidak ada kategori', + 'The name is required' => 'Nama diperlukan', + 'Remove a file' => 'Hapus berkas', + 'Unable to remove this file.' => 'Tidak dapat menghapus berkas ini.', + 'File removed successfully.' => 'Berkas berhasil dihapus.', + 'Attach a document' => 'Lampirkan dokumen', + 'Do you really want to remove this file: "%s"?' => 'Apakah Anda yakin akan menghapus berkas ini: "%s"?', + 'Attachments' => 'Lampiran', + 'Edit the task' => 'Edit tugas', + 'Add a comment' => 'Tambahkan komentar', + 'Edit a comment' => 'Edit komentar', + 'Summary' => 'Ringkasan', + 'Time tracking' => 'Pelacakan waktu', + 'Estimate:' => 'Estimasi:', + 'Spent:' => 'Menghabiskan:', + 'Do you really want to remove this sub-task?' => 'Apakah Anda yakin mau menghapus sub-tugas ini?', + 'Remaining:' => 'Tersisa:', + 'hours' => 'jam', + 'estimated' => 'perkiraan', + 'Sub-Tasks' => 'Sub-tugas', + 'Add a sub-task' => 'Tambahkan sub-tugas', + 'Original estimate' => 'Perkiraan semula', + 'Create another sub-task' => 'Tambahkan sub-tugas lainnya', + 'Time spent' => 'Waktu yang dihabiskan', + 'Edit a sub-task' => 'Edit sub-tugas', + 'Remove a sub-task' => 'Hapus sub-tugas', + 'The time must be a numeric value' => 'Waktu harus berupa angka', + 'Todo' => 'Yang harus dilakukan', + 'In progress' => 'Dalam proses', + 'Sub-task removed successfully.' => 'Sub-tugas berhasil dihapus.', + 'Unable to remove this sub-task.' => 'Tidak dapat menghapus sub-tugas.', + 'Sub-task updated successfully.' => 'Sub-tugas berhasil diperbarui.', + 'Unable to update your sub-task.' => 'Tidak dapat memperbarui sub-tugas Anda.', + 'Unable to create your sub-task.' => 'Tidak dapat membuat sub-tugas Anda.', + 'Maximum size: ' => 'Ukuran maksimum: ', + 'Display another project' => 'Lihat proyek lain', + 'Created by %s' => 'Dibuat oleh %s', + 'Tasks Export' => 'Ekspor Tugas', + 'Start Date' => 'Tanggal Mulai', + 'Execute' => 'Eksekusi', + 'Task Id' => 'ID Tugas', + 'Creator' => 'Pembuat', + 'Modification date' => 'Tanggal modifikasi', + 'Completion date' => 'Tanggal penyelesaian', + 'Clone' => 'Klon', + 'Project cloned successfully.' => 'Kloning proyek berhasil.', + 'Unable to clone this project.' => 'Tidak dapat mengkloning proyek.', + 'Enable email notifications' => 'Aktifkan pemberitahuan dari email', + 'Task position:' => 'Posisi tugas:', + 'The task #%d has been opened.' => 'Tugas #%d telah dibuka.', + 'The task #%d has been closed.' => 'Tugas #%d telah ditutup.', + 'Sub-task updated' => 'Sub-tugas diperbarui', + 'Title:' => 'Judul:', + 'Status:' => 'Status:', + 'Assignee:' => 'Ditugaskan ke:', + 'Time tracking:' => 'Pelacakan waktu:', + 'New sub-task' => 'Sub-tugas baru', + 'New attachment added "%s"' => 'Lampiran baru ditambahkan "%s"', + 'New comment posted by %s' => 'Komentar baru ditambahkan oleh %s', + 'New comment' => 'Komentar baru', + 'Comment updated' => 'Komentar diperbarui', + 'New subtask' => 'Sub-tugas baru', + 'I only want to receive notifications for these projects:' => 'Saya ingin menerima pemberitahuan hanya untuk proyek-proyek yang dipilih :', + 'view the task on Kanboard' => 'lihat tugas di Kanboard', + 'Public access' => 'Akses publik', + 'Disable public access' => 'Nonaktifkan akses publik', + 'Enable public access' => 'Aktifkan akses publik', + 'Public access disabled' => 'Akses publik dinonaktifkan', + 'Move the task to another project' => 'Pindahkan tugas ke proyek lain', + 'Move to project' => 'Pindahkan ke proyek lain', + 'Do you really want to duplicate this task?' => 'Apakah Anda yakin mau menduplikasi tugas ini?', + 'Duplicate a task' => 'Duplikasi tugas', + 'External accounts' => 'Akun eksternal', + 'Account type' => 'Tipe akun', + 'Local' => 'Lokal', + 'Remote' => 'Jarak Jauh', + 'Enabled' => 'Aktif', + 'Disabled' => 'Nonaktif', + 'Login:' => 'Masuk:', + 'Full Name:' => 'Nama Lengkap:', + 'Email:' => 'Email:', + 'Notifications:' => 'Pemberitahuan:', + 'Notifications' => 'Pemberitahuan', + 'Account type:' => 'Tipe akun:', + 'Edit profile' => 'Edit profil', + 'Change password' => 'Ganti password', + 'Password modification' => 'Modifikasi password', + 'External authentications' => 'Otentifikasi eksternal', + 'Never connected.' => 'Tidak pernah terhubung.', + 'No external authentication enabled.' => 'Tidak ada otentifikasi eksternal yang aktif.', + 'Password modified successfully.' => 'Password berhasil dimodifikasi.', + 'Unable to change the password.' => 'Tidak dapat mengganti kata sandi.', + 'Change category' => 'Ganti kategori', + '%s updated the task %s' => '%s memperbarui tugas %s', + '%s opened the task %s' => '%s membuka tugas %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s memindahkan tugas %s ke posisi #%d dalam kolom "%s"', + '%s moved the task %s to the column "%s"' => '%s memindahkan tugas %s ke kolom "%s"', + '%s created the task %s' => '%s membuat tugas %s', + '%s closed the task %s' => '%s menutup tugas %s', + '%s created a subtask for the task %s' => '%s membuat sub-tugas untuk tugas %s', + '%s updated a subtask for the task %s' => '%s memperbarui sub-tugas untuk tugas %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Ditugaskan pada %s dengan perkiraan %s/%sh', + 'Not assigned, estimate of %sh' => 'Tidak ada yang ditugaskan, perkiraan %sh', + '%s updated a comment on the task %s' => '%s memperbarui komentar pada tugas %s', + '%s commented the task %s' => '%s memberikan komentar pada tugas %s', + '%s\'s activity' => 'Aktifitas dari %s', + 'RSS feed' => 'Umpan RSS', + '%s updated a comment on the task #%d' => '%s memperbarui komentar pada tugas #%d', + '%s commented on the task #%d' => '%s memberikan komentar pada tugas #%d', + '%s updated a subtask for the task #%d' => '%s memperbarui sub-tugas untuk tugas #%d', + '%s created a subtask for the task #%d' => '%s membuat sub-tugas untuk tugas #%d', + '%s updated the task #%d' => '%s memperbarui tugas #%d', + '%s created the task #%d' => '%s membuat tugas #%d', + '%s closed the task #%d' => '%s menutup tugas #%d', + '%s opened the task #%d' => '%s membuka tugas #%d', + 'Activity' => 'Aktifitas', + 'Default values are "%s"' => 'Nilai default adalah "%s"', + 'Default columns for new projects (Comma-separated)' => 'Kolom default untuk proyek baru (dipisahkan dengan koma)', + 'Task assignee change' => 'Ganti orang yang ditugaskan', + '%s changed the assignee of the task #%d to %s' => '%s mengganti orang yang ditugaskan dari tugas #%d ke %s', + '%s changed the assignee of the task %s to %s' => '%s mengganti orang yang ditugaskan dari tugas %s ke %s', + 'New password for the user "%s"' => 'Password baru untuk pengguna "%s"', + 'Choose an event' => 'Pilih acara', + 'Create a task from an external provider' => 'Buat tugas dari penyedia eksternal', + 'Change the assignee based on an external username' => 'Ganti penugasan berdasarkan nama pengguna eksternal', + 'Change the category based on an external label' => 'Ganti kategori berdasarkan label eksternal', + 'Reference' => 'Referensi', + 'Label' => 'Label', + 'Database' => 'Database', + 'About' => 'Tentang', + 'Database driver:' => 'Driver database:', + 'Board settings' => 'Pengaturan papan', + 'Webhook settings' => 'Pengaturan Webhook', + 'Reset token' => 'Reset token', + 'API endpoint:' => 'API endpoint :', + 'Refresh interval for personal board' => 'Interval pembaruan untuk papan pribadi', + 'Refresh interval for public board' => 'Interval pembaruan untuk papan publik', + 'Task highlight period' => 'Periode penyorotan tugas', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Periode (dalam detik) untuk mempertimbangkan tugas yang baru dimodifikasi (0 untuk menonaktifkan, default 2 hari)', + 'Frequency in second (60 seconds by default)' => 'Frekuensi dalam detik (default 60 detik)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frekuensi dalam detik (0 untuk menonaktifkan fitur ini, default 10 detik)', + 'Application URL' => 'URL Aplikasi', + 'Token regenerated.' => 'Token diregenerasi.', + 'Date format' => 'Format tanggal', + 'ISO format is always accepted, example: "%s" and "%s"' => 'Format ISO selalu diterima, contoh: "%s" dan "%s"', + 'New personal project' => 'Proyek pribadi baru', + 'This project is personal' => 'Proyek ini pribadi', + 'Add' => 'Tambah', + 'Start date' => 'Tanggal mulai', + 'Time estimated' => 'Perkiraan waktu', + 'There is nothing assigned to you.' => 'Tidak ada tugas yang diberikan pada Anda.', + 'My tasks' => 'Tugas saya', + 'Activity stream' => 'Arus aktifitas', + 'Dashboard' => 'Dasbor', + 'Confirmation' => 'Konfirmasi', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Create a comment from an external provider' => 'Buat komentar dari penyedia eksternal', + 'Project management' => 'Manajemen proyek', + 'Columns' => 'Kolom', + 'Task' => 'Tugas', + 'Percentage' => 'Persentasi', + 'Number of tasks' => 'Jumlah dari tugas', + 'Task distribution' => 'Pembagian tugas', + 'Analytics' => 'Analitik', + 'Subtask' => 'Sub-tugas', + 'User repartition' => 'Partisi ulang pengguna', + 'Clone this project' => 'Klon proyek ini', + 'Column removed successfully.' => 'Kolom berhasil dihapus.', + 'Not enough data to show the graph.' => 'Tidak cukup data untuk menampilkan grafik.', + 'Previous' => 'Sebelumnya', + 'The id must be an integer' => 'ID harus integer', + 'The project id must be an integer' => 'ID proyek harus integer', + 'The status must be an integer' => 'Status harus integer', + 'The subtask id is required' => 'ID sub-tugas diperlukan', + 'The subtask id must be an integer' => 'ID sub-tugas harus integer', + 'The task id is required' => 'ID tugas diperlukan', + 'The task id must be an integer' => 'ID tugas harus integer', + 'The user id must be an integer' => 'ID user harus integer', + 'This value is required' => 'Nilai ini diperlukan', + 'This value must be numeric' => 'Nilai ini harus angka', + 'Unable to create this task.' => 'Tidak dapat membuat tugas ini', + 'Cumulative flow diagram' => 'Diagram alir kumulatif', + 'Daily project summary' => 'Ringkasan proyek harian', + 'Daily project summary export' => 'Ekspor ringkasan proyek harian', + 'Exports' => 'Ekspor', + 'This export contains the number of tasks per column grouped per day.' => 'Ekspor ini berisi jumlah dari tugas per kolom yang dikelompokan per hari.', + 'Active swimlanes' => 'Swimlanes aktif', + 'Add a new swimlane' => 'Tambah swimlane baru', + 'Default swimlane' => 'Swimlane default', + 'Do you really want to remove this swimlane: "%s"?' => 'Apakah Anda yakin mau menghapus swimlane ini: "%s"?', + 'Inactive swimlanes' => 'Swimlanes tidak aktif', + 'Remove a swimlane' => 'Hapus swimlane', + 'Swimlane modification for the project "%s"' => 'Modifikasi swimlane untuk proyek "%s"', + 'Swimlane removed successfully.' => 'Swimlane berhasil dihapus.', + 'Swimlanes' => 'Swimlanes', + 'Swimlane updated successfully.' => 'Swimlane berhasil diperbarui.', + 'Unable to remove this swimlane.' => 'Tidak dapat menghapus swimlane ini.', + 'Unable to update this swimlane.' => 'Tidak dapat memperbarui swimlane ini.', + 'Your swimlane has been created successfully.' => 'Swimlane Anda berhasil dibuat.', + 'Example: "Bug, Feature Request, Improvement"' => 'Contoh: "Bug, Permintaan Fitur, Peningkatan"', + 'Default categories for new projects (Comma-separated)' => 'Kategori default untuk proyek baru (dipisahkan dengan koma)', + 'Integrations' => 'Integrasi', + 'Integration with third-party services' => 'Integrasi dengan layanan pihak ketiga', + 'Subtask Id' => 'ID Sub-tugas', + 'Subtasks' => 'Sub-tugas', + 'Subtasks Export' => 'Ekspor Sub-tugas', + 'Task Title' => 'Judul Tugas', + 'Untitled' => 'Tanpa Nama', + 'Application default' => 'Default aplikasi', + 'Language:' => 'Bahasa:', + 'Timezone:' => 'Zona waktu:', + 'All columns' => 'Semua kolom', + 'Next' => 'Selanjutnya', + '#%d' => '#%d', + 'All swimlanes' => 'Semua swimlane', + 'All colors' => 'Semua warna', + 'Moved to column %s' => 'Pindah ke kolom %s', + 'User dashboard' => 'Dasbor pengguna', + 'Allow only one subtask in progress at the same time for a user' => 'Izinkan hanya satu sub-tugas dalam proses secara bersamaan untuk satu pengguna', + 'Edit column "%s"' => 'Edit kolom "%s"', + 'Select the new status of the subtask: "%s"' => 'Pilih status baru untuk sub-tugas: "%s"', + 'Subtask timesheet' => 'Absen sub-tugas', + 'There is nothing to show.' => 'Tidak ada yang bisa diperlihatkan.', + 'Time Tracking' => 'Pelacakan Waktu', + 'You already have one subtask in progress' => 'Anda sudah memiliki satu sub-tugas dalam proses', + 'Which parts of the project do you want to duplicate?' => 'Bagian proyek mana yang ingin Anda duplikasi?', + 'Disallow login form' => 'Larang formulir masuk', + 'Start' => 'Mulai', + 'End' => 'Selesai', + 'Task age in days' => 'Usia tugas dalam hari', + 'Days in this column' => 'Hari dalam kolom ini', + '%dd' => '%dd', + 'Add a new link' => 'Tambah tautan baru', + 'Do you really want to remove this link: "%s"?' => 'Apakah Anda yakin mau menghapus tautan ini: "%s"?', + 'Do you really want to remove this link with task #%d?' => 'Apakah Anda yakin mau menghapus tautan ini dengan tugas #%d ?', + 'Field required' => 'Bidang dibutuhkan', + 'Link added successfully.' => 'Tautan berhasil ditambahkan.', + 'Link updated successfully.' => 'Tautan berhasil diperbarui.', + 'Link removed successfully.' => 'Tautan berhasil dihapus.', + 'Link labels' => 'Label tautan', + 'Link modification' => 'Modifikasi tautan', + 'Opposite label' => 'Label berlawanan', + 'Remove a link' => 'Hapus tautan', + 'The labels must be different' => 'Label harus berbeda', + 'There is no link.' => 'Tidak ada tautan.', + 'This label must be unique' => 'Label ini harus unik', + 'Unable to create your link.' => 'Tidak dapat membuat tautan Anda.', + 'Unable to update your link.' => 'Tidak dapat memperbarui tautan Anda.', + 'Unable to remove this link.' => 'Tidak dapat menghapus tautan ini.', + 'relates to' => 'berhubungan dengan', + 'blocks' => 'blokir', + 'is blocked by' => 'diblokir oleh', + 'duplicates' => 'duplikat', + 'is duplicated by' => 'diduplikasi oleh', + 'is a child of' => 'anak dari', + 'is a parent of' => 'induk dari', + 'targets milestone' => 'target batu pijakan', + 'is a milestone of' => 'adalah batu pijakan dari', + 'fixes' => 'perbaikan', + 'is fixed by' => 'diperbaiki oleh', + 'This task' => 'Tugas ini', + '<1h' => '<1h', + '%dh' => '%dh', + 'Expand tasks' => 'Perluas tugas', + 'Collapse tasks' => 'Tutup tugas', + 'Expand/collapse tasks' => 'Perluas/tutup tugas', + 'Close dialog box' => 'Tutup kotak dialog', + 'Submit a form' => 'Kirim formulir', + 'Board view' => 'Tampilan papan', + 'Keyboard shortcuts' => 'Pintasan keyboard', + 'Open board switcher' => 'Buka switcher papan', + 'Application' => 'Aplikasi', + 'Compact view' => 'Tampilan kompak', + 'Horizontal scrolling' => 'Gulir horizontal', + 'Compact/wide view' => 'Tampilan kompak/lebar', + 'Currency' => 'Mata uang', + 'Personal project' => 'Proyek pribadi', + 'AUD - Australian Dollar' => 'AUD - Dolar Australia', + 'CAD - Canadian Dollar' => 'CAD - Dolar Kanada', + 'CHF - Swiss Francs' => 'CHF - Francs Swiss', + 'Custom Stylesheet' => 'Kustomisasi CSS', + 'EUR - Euro' => 'EUR - Euro', + 'GBP - British Pound' => 'GBP - Poundsterling Inggris', + 'INR - Indian Rupee' => 'INR - Rupe India', + 'JPY - Japanese Yen' => 'JPY - Yen Jepang', + 'NZD - New Zealand Dollar' => 'NZD - Dolar Selandia baru', + 'PEN - Peruvian Sol' => 'PEN - Sol Peru', + 'RSD - Serbian dinar' => 'RSD - Dinar Serbia', + 'CNY - Chinese Yuan' => 'CNY - Yuan Tiongkok', + 'USD - US Dollar' => 'USD - Dolar Amerika', + 'VES - Venezuelan Bolívar' => 'VES - Bolivar Venezuela', + 'Destination column' => 'Kolom tujuan', + 'Move the task to another column when assigned to a user' => 'Pindahkan tugas ke kolom lain saat ditugaskan ke pengguna', + 'Move the task to another column when assignee is cleared' => 'Pindahkan tugas ke kolom lain saat orang yang ditugaskan kosong', + 'Source column' => 'Sumber kolom', + 'Transitions' => 'Transisi', + 'Executer' => 'Eksekusi', + 'Time spent in the column' => 'Waktu yang dihabiskan dalam kolom', + 'Task transitions' => 'Transisi tugas', + 'Task transitions export' => 'Ekspor transisi tugas', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Laporan ini berisi semua kolom yang pindah untuk setiap tugas dengan tanggal, pengguna dan waktu yang dihabiskan untuk setiap transisi.', + 'Currency rates' => 'Nilai tukar mata uang', + 'Rate' => 'Tarif', + 'Change reference currency' => 'Ganti referensi mata uang', + 'Reference currency' => 'Referensi mata uang', + 'The currency rate has been added successfully.' => 'Nilai tukar mata uang berhasil ditambahkan.', + 'Unable to add this currency rate.' => 'Tidak dapat menambahkan nilai tukar mata uang', + 'Webhook URL' => 'URL Webhook', + '%s removed the assignee of the task %s' => '%s menghapus penugasan dari tugas %s', + 'Information' => 'Informasi', + 'Check two factor authentication code' => 'Cek dua faktor kode otentifikasi', + 'The two factor authentication code is not valid.' => 'Kode dua faktor kode otentifikasi tidak sesuai.', + 'The two factor authentication code is valid.' => 'Kode dua faktor kode otentifikasi sesuai.', + 'Code' => 'Kode', + 'Two factor authentication' => 'Dua faktor otentifikasi', + 'This QR code contains the key URI: ' => 'Kode QR ini mengandung kunci URI: ', + 'Check my code' => 'Periksa kode saya', + 'Secret key: ' => 'Kunci rahasia: ', + 'Test your device' => 'Uji perangkat Anda', + 'Assign a color when the task is moved to a specific column' => 'Tetapkan warna ketika tugas tersebut dipindahkan ke kolom tertentu', + '%s via Kanboard' => '%s via Kanboard', + 'Burndown chart' => 'Grafik Burndown', + 'This chart show the task complexity over the time (Work Remaining).' => 'Grafik ini menunjukkan kompleksitas tugas dari waktu ke waktu (Sisa Pekerjaan).', + 'Screenshot taken %s' => 'Screenshot diambil %s', + 'Add a screenshot' => 'Tambah screenshot', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Ambil screenshot dan tekan CTRL+V atau ⌘+V untuk ditempel di sini.', + 'Screenshot uploaded successfully.' => 'Screenshot berhasil diunggah.', + 'SEK - Swedish Krona' => 'SEK - Krona Swedia', + 'Identifier' => 'Identifier', + 'Disable two factor authentication' => 'Matikan dua faktor otentifikasi', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Apakah Anda yakin mau mematikan dua faktor otentifikasi untuk pengguna ini: "%s"?', + 'Edit link' => 'Edit tautan', + 'Start to type task title...' => 'Mulai mengetik judul tugas...', + 'A task cannot be linked to itself' => 'Tugas tidak dapat dikaitkan dengan dirinya sendiri', + 'The exact same link already exists' => 'Tautan yang sama persis sudah ada', + 'Recurrent task is scheduled to be generated' => 'Tugas berulang dijadwalkan untuk di generate', + 'Score' => 'Skor', + 'The identifier must be unique' => 'Identifier harus unik', + 'This linked task id doesn\'t exists' => 'ID tugas terkait tidak ada', + 'This value must be alphanumeric' => 'Nilai harus alfanumerik', + 'Edit recurrence' => 'Edit pengulangan', + 'Generate recurrent task' => 'Generate tugas berulang', + 'Trigger to generate recurrent task' => 'Pemicu untuk menghasilkan tugas berulang', + 'Factor to calculate new due date' => 'Faktor untuk menghitung tanggal jatuh tempo baru', + 'Timeframe to calculate new due date' => 'Jangka waktu untuk menghitung tanggal jatuh tempo baru', + 'Base date to calculate new due date' => 'Tanggal dasar untuk menghitung tanggal jatuh tempo baru', + 'Action date' => 'Tanggal aksi', + 'Base date to calculate new due date: ' => 'Tanggal dasar untuk menghitung tanggal jatuh tempo baru: ', + 'This task has created this child task: ' => 'Tugas ini telah membuat tugas anak ini: ', + 'Day(s)' => 'Hari', + 'Existing due date' => 'Batas waktu yang ada', + 'Factor to calculate new due date: ' => 'Faktor untuk menghitung tanggal jatuh tempo baru: ', + 'Month(s)' => 'Bulan', + 'This task has been created by: ' => 'Tugas ini telah dibuat oleh: ', + 'Recurrent task has been generated:' => 'Tugas berulang telah di generate:', + 'Timeframe to calculate new due date: ' => 'Jangka waktu untuk menghitung tanggal jatuh tempo baru: ', + 'Trigger to generate recurrent task: ' => 'Pemicu untuk menghasilkan tugas berulang: ', + 'When task is closed' => 'Saat tugas ditutup', + 'When task is moved from first column' => 'Saat tugas dipindahkan dari kolom pertama', + 'When task is moved to last column' => 'Saat tugas dipindahkan ke kolom terakhir', + 'Year(s)' => 'Tahun', + 'Project settings' => 'Pengaturan proyek', + 'Automatically update the start date' => 'Otomatis memperbarui tanggal permulaan', + 'iCal feed' => 'Umpan iCal', + 'Preferences' => 'Preferensi', + 'Security' => 'Keamanan', + 'Two factor authentication disabled' => 'Otentifikasi dua faktor dimatikan', + 'Two factor authentication enabled' => 'Otentifikasi dua faktor dihidupkan', + 'Unable to update this user.' => 'Tidak dapat memperbarui pengguna ini.', + 'There is no user management for personal projects.' => 'Tidak ada manajemen pengguna untuk proyek-proyek pribadi.', + 'User that will receive the email' => 'Pengguna yang akan menerima email', + 'Email subject' => 'Subyek Email', + 'Date' => 'Tanggal', + 'Add a comment log when moving the task between columns' => 'Menambahkan log komentar ketika memindahkan tugas antara kolom', + 'Move the task to another column when the category is changed' => 'Pindahkan tugas ke kolom lain ketika kategori berubah', + 'Send a task by email to someone' => 'Kirim tugas melalui email ke seseorang', + 'Reopen a task' => 'Buka kembali tugas', + 'Notification' => 'Pemberitahuan', + '%s moved the task #%d to the first swimlane' => '%s memindahkan tugas #%d ke swimlane pertama', + 'Swimlane' => 'Swimlane', + '%s moved the task %s to the first swimlane' => '%s memindahkan tugas %s ke swimlane pertama', + '%s moved the task %s to the swimlane "%s"' => '%s memindahkan tugas %s ke swimlane "%s"', + 'This report contains all subtasks information for the given date range.' => 'Laporan ini berisi semua informasi sub-tugas untuk rentang tanggal tertentu.', + 'This report contains all tasks information for the given date range.' => 'Laporan ini berisi semua informasi tugas untuk rentang tanggal tertentu.', + 'Project activities for %s' => 'Aktifitas proyek untuk "%s"', + 'view the board on Kanboard' => 'lihat papan di Kanboard', + 'The task has been moved to the first swimlane' => 'Tugas telah dipindahkan ke swimlane pertama', + 'The task has been moved to another swimlane:' => 'Tugas telah dipindahkan ke swimlane lain:', + 'New title: %s' => 'Judul baru: %s', + 'The task is not assigned anymore' => 'Tugas tidak ditugaskan lagi', + 'New assignee: %s' => 'Penerima baru: %s', + 'There is no category now' => 'Tidak ada kategori untuk saat ini', + 'New category: %s' => 'Kategori baru: %s', + 'New color: %s' => 'Warna baru: %s', + 'New complexity: %d' => 'Kompleksitas baru: %d', + 'The due date has been removed' => 'Tanggal jatuh tempo telah dihapus', + 'There is no description anymore' => 'Tidak ada deskripsi lagi', + 'Recurrence settings has been modified' => 'Pengaturan pengulangan telah dimodifikasi', + 'Time spent changed: %sh' => 'Waktu yang dihabiskan telah diganti: %sh', + 'Time estimated changed: %sh' => 'Perkiraan waktu telah diganti: %sh', + 'The field "%s" has been updated' => 'Bidang "%s" telah diperbarui', + 'The description has been modified:' => 'Deskripsi telah dimodifikasi', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Apakah Anda yakin mau menutup tugas "%s" beserta semua sub-tugasnya?', + 'I want to receive notifications for:' => 'Saya ingin menerima pemberitahuan untuk:', + 'All tasks' => 'Semua tugas', + 'Only for tasks assigned to me' => 'Hanya untuk tugas yang ditugaskan ke saya', + 'Only for tasks created by me' => 'Hanya untuk tugas yang dibuat oleh saya', + 'Only for tasks created by me and tasks assigned to me' => 'Hanya untuk tugas yang dibuat oleh saya dan ditugaskan ke saya', + '%%Y-%%m-%%d' => '%%d/%%m/%%Y', + 'Total for all columns' => 'Total untuk semua kolom', + 'You need at least 2 days of data to show the chart.' => 'Anda memerlukan setidaknya 2 hari dari data yang menunjukkan grafik.', + '<15m' => '<15m', + '<30m' => '<30m', + 'Stop timer' => 'Hentikan timer', + 'Start timer' => 'Mulai timer', + 'My activity stream' => 'Aliran kegiatan saya', + 'Search tasks' => 'Cari tugas', + 'Reset filters' => 'Reset saringan', + 'My tasks due tomorrow' => 'Tugas saya yang berakhir besok', + 'Tasks due today' => 'Tugas yang berakhir hari ini', + 'Tasks due tomorrow' => 'Tugas yang berakhir besok', + 'Tasks due yesterday' => 'Tugas yang berakhir kemarin', + 'Closed tasks' => 'Tugas yang ditutup', + 'Open tasks' => 'Tugas terbuka', + 'Not assigned' => 'Tidak ditugaskan', + 'View advanced search syntax' => 'Lihat sintaks pencarian lanjutan', + 'Overview' => 'Ringkasan', + 'Board/Calendar/List view' => 'Tampilan Papan/Kalender/Daftar', + 'Switch to the board view' => 'Beralih ke tampilan papan', + 'Switch to the list view' => 'Beralih ke tampilan daftar', + 'Go to the search/filter box' => 'Pergi ke kotak pencarian/saringan', + 'There is no activity yet.' => 'Belum ada aktifitas.', + 'No tasks found.' => 'Tidak ada tugas yang ditemukan.', + 'Keyboard shortcut: "%s"' => 'Pintasan keyboard: "%s"', + 'List' => 'Daftar', + 'Filter' => 'Saringan', + 'Advanced search' => 'Pencarian lanjutan', + 'Example of query: ' => 'Contoh dari query : ', + 'Search by project: ' => 'Cari berdasarkan proyek: ', + 'Search by column: ' => 'Cari berdasarkan kolom: ', + 'Search by assignee: ' => 'Cari berdasarkan penerima tugas: ', + 'Search by color: ' => 'Cari berdasarkan warna: ', + 'Search by category: ' => 'Cari berdasarkan kategori: ', + 'Search by description: ' => 'Cari berdasarkan deskripsi: ', + 'Search by due date: ' => 'Cari berdasarkan tanggal jatuh tempo: ', + 'Average time spent in each column' => 'Rata-rata waktu yang dihabiskan dalam setiap kolom', + 'Average time spent' => 'Rata-rata waktu yang dihabiskan', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Grafik ini menunjukkan rata-rata waktu yang dihabiskan dalam setiap kolom untuk %d tugas terakhir.', + 'Average Lead and Cycle time' => 'Rata-rata Lead dan Cycle time', + 'Average lead time: ' => 'Rata-rata lead time: ', + 'Average cycle time: ' => 'Rata-rata cycle time: ', + 'Cycle Time' => 'Cycle Time', + 'Lead Time' => 'Lead Time', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Grafik ini menunjukkan rata-rata waktu lead dan cycle time untuk %d tugas terakhir dari waktu ke waktu.', + 'Average time into each column' => 'Rata-rata waktu ke setiap kolom', + 'Lead and cycle time' => 'Lead dan cycle time', + 'Lead time: ' => 'Lead time: ', + 'Cycle time: ' => 'Cycle time: ', + 'Time spent in each column' => 'Waktu yang dihabiskan di setiap kolom', + 'The lead time is the duration between the task creation and the completion.' => 'Lead time adalah durasi antara pembuatan tugas dan penyelesaian.', + 'The cycle time is the duration between the start date and the completion.' => 'Cycle time adalah durasi antara tanggal mulai dan tanggal penyelesaian.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Jika tugas tidak ditutup, waktu saat ini akan digunakan sebagai pengganti tanggal penyelesaian.', + 'Set the start date automatically' => 'Secara otomatis mengatur tanggal mulai', + 'Edit Authentication' => 'Edit Otentifikasi', + 'Remote user' => 'Pengguna jauh', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Pengguna jauh tidak menyimpan kata sandi mereka dalam basis data Kanboard, contoh: akun LDAP, Google dan Github.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Jika anda mencentang kotak "Larang formulir login", kredensial masuk ke formulis login akan diabaikan.', + 'Default task color' => 'Warna tugas default', + 'This feature does not work with all browsers.' => 'Fitur ini tidak dapat digunakan di semua peramban', + 'There is no destination project available.' => 'Tidak ada tujuan proyek yang tersedia.', + 'Trigger automatically subtask time tracking' => 'Otomatis memicu pelacakan waktu untuk sub-tugas', + 'Include closed tasks in the cumulative flow diagram' => 'Sertakan tugas yang ditutup dalam diagram alir kumulatif', + 'Current swimlane: %s' => 'Swimlane saat ini: %s', + 'Current column: %s' => 'Kolom saat ini: %s', + 'Current category: %s' => 'Kategori saat ini: %s', + 'no category' => 'tidak ada kategori', + 'Current assignee: %s' => 'Orang yang ditugaskan saat ini: %s', + 'not assigned' => 'belum ditugaskan', + 'Author:' => 'Penulis:', + 'contributors' => 'kontributor', + 'License:' => 'Lisensi:', + 'License' => 'Lisensi', + 'Enter the text below' => 'Masukkan teks di bawah', + 'Start date:' => 'Tanggal mulai:', + 'Due date:' => 'Batas waktu:', + 'People who are project managers' => 'Orang-orang yang menjadi manajer proyek', + 'People who are project members' => 'Orang-orang yang menjadi anggota proyek', + 'NOK - Norwegian Krone' => 'NOK - Krone Norwegia', + 'Show this column' => 'Perlihatkan kolom ini', + 'Hide this column' => 'Sembunyikan kolom ini', + 'End date' => 'Waktu berakhir', + 'Users overview' => 'Ringkasan pengguna', + 'Members' => 'Anggota', + 'Shared project' => 'Proyek bersama', + 'Project managers' => 'Manajer proyek', + 'Projects list' => 'Daftar proyek', + 'End date:' => 'Waktu berakhir:', + 'Change task color when using a specific task link' => 'Ganti warna tugas ketika menggunakan tautan tugas yang spesifik', + 'Task link creation or modification' => 'Tautan pembuatan atau modifikasi tugas ', + 'Milestone' => 'Milestone', + 'Reset the search/filter box' => 'Reset kotak pencarian/saringan', + 'Documentation' => 'Dokumentasi', + 'Author' => 'Penulis', + 'Version' => 'Versi', + 'Plugins' => 'Plugin', + 'There is no plugin loaded.' => 'Tidak ada plugin yang dimuat', + 'My notifications' => 'Notifikasi saya', + 'Custom filters' => 'Saringan kustom', + 'Your custom filter has been created successfully.' => 'Saringan kustom Anda berhasil dibuat', + 'Unable to create your custom filter.' => 'Tidak dapat membuat saringan kustom', + 'Custom filter removed successfully.' => 'Saringan kustom berhasil dihapus', + 'Unable to remove this custom filter.' => 'Tidak dapat menghapus saringan kustom', + 'Edit custom filter' => 'Edit saringan kustom', + 'Your custom filter has been updated successfully.' => 'Saringan kustom Anda berhasil diperbarui', + 'Unable to update custom filter.' => 'Tidak dapat memperbarui saringan kustom', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Lampiran baru pada tugas #%d: %s', + 'New comment on task #%d' => 'Komentar baru pada tugas #%d', + 'Comment updated on task #%d' => 'Komentar diperbarui pada tugas #%d', + 'New subtask on task #%d' => 'Sub-tugas baru pada tugas #%d', + 'Subtask updated on task #%d' => 'Sub-tugas diperbarui pada tugas #%d', + 'New task #%d: %s' => 'Tugas baru #%d: %s', + 'Task updated #%d' => 'Tugas diperbarui #%d', + 'Task #%d closed' => 'Tugas #%d ditutup', + 'Task #%d opened' => 'Tugas #%d dibuka', + 'Column changed for task #%d' => 'Kolom diganti untuk tugas #%d', + 'New position for task #%d' => 'Posisi baru untuk tugas #%d', + 'Swimlane changed for task #%d' => 'Swimlane diganti untuk tugas #%d', + 'Assignee changed on task #%d' => 'Orang yang ditugaskan diganti pada tugas #%d', + '%d overdue tasks' => '%d tugas kadaluarsa', + 'No notification.' => 'Tidak ada notifikasi baru', + 'Mark all as read' => 'Tandai semua sebagai sudah dibaca', + 'Mark as read' => 'Tandai sebagai sudah dibaca', + 'Total number of tasks in this column across all swimlanes' => 'Total tugas di kolom ini untuk semua swimlane', + 'Collapse swimlane' => 'Tutup swimlane', + 'Expand swimlane' => 'Perluas swimlane', + 'Add a new filter' => 'Tambah saringan baru', + 'Share with all project members' => 'Bagikan dengan semua anggota proyek', + 'Shared' => 'Dibagikan', + 'Owner' => 'Pemilik', + 'Unread notifications' => 'Notifikasi belum terbaca', + 'Notification methods:' => 'Metode pemberitahuan', + 'Unable to read your file' => 'Tidak dapat membaca berkas Anda', + '%d task(s) have been imported successfully.' => '%d tugas telah berhasil di impor', + 'Nothing has been imported!' => 'Tidak ada yang dapat di impor', + 'Import users from CSV file' => 'Impor pengguna dari berkas CSV', + '%d user(s) have been imported successfully.' => '%d pengguna telah berhasil di impor', + 'Comma' => 'Koma', + 'Semi-colon' => 'Titik Koma', + 'Tab' => 'Tab', + 'Vertical bar' => 'Bar vertikal', + 'Double Quote' => 'Kutip Ganda', + 'Single Quote' => 'Kutip Satu', + '%s attached a file to the task #%d' => '%s berkas dilampirkan untuk tugas #%d', + 'There is no column or swimlane activated in your project!' => 'Tidak ada kolom atau swimlane aktif untuk proyek Anda', + 'Append filter (instead of replacement)' => 'Tambahkan saringan (ketimbang pengganti)', + 'Append/Replace' => 'Tambah/Ganti', + 'Append' => 'Tambahkan', + 'Replace' => 'Ganti', + 'Import' => 'Impor', + 'Change sorting' => 'ubah pengurutan', + 'Tasks Importation' => 'Importasi Tugas', + 'Delimiter' => 'Pembatas', + 'Enclosure' => 'Lampiran', + 'CSV File' => 'Berkas CSV', + 'Instructions' => 'Intruksi', + 'Your file must use the predefined CSV format' => 'Berkas Anda harus menggunakan format CSV yang telah ditetapkan', + 'Your file must be encoded in UTF-8' => 'Berkas anda harus di kodekan dalam bentuk UTF-8', + 'The first row must be the header' => 'Baris pertama harus header', + 'Duplicates are not verified for you' => 'Duplikasi tidak diverifikasikan untuk Anda', + 'The due date must use the ISO format: YYYY-MM-DD' => 'Tanggal jatuh tempo harus menggunakan format ISO: YYYY-MM-DD', + 'Download CSV template' => 'Unduh template CSV', + 'No external integration registered.' => 'Tidak ada integrasi eksternal yang terdaftar', + 'Duplicates are not imported' => 'Duplikasi tidak diimpor', + 'Usernames must be lowercase and unique' => 'Nama pengguna harus huruf kecil dan unik', + 'Passwords will be encrypted if present' => 'Kata sandi akan di enkripsi jika ada', + '%s attached a new file to the task %s' => '%s melampirkan berkas baru untuk tugas %s', + 'Link type' => 'Tipe tautan', + 'Assign automatically a category based on a link' => 'Otomatis menetapkan kategori berdasarkan tautan', + 'BAM - Konvertible Mark' => 'BAM - Konvertible Mark', + 'Assignee Username' => 'Nama pengguna orang yang ditugaskan', + 'Assignee Name' => 'Nama orang yang ditugaskan', + 'Groups' => 'Grup', + 'Members of %s' => 'Anggota dari %s', + 'New group' => 'Grup baru', + 'Group created successfully.' => 'Grup berhasil dibuat', + 'Unable to create your group.' => 'Tidak dapat membuat grup Anda', + 'Edit group' => 'Edit grup', + 'Group updated successfully.' => 'Grup berhasil diperbarui', + 'Unable to update your group.' => 'Tidak dapat memperbarui grup anda', + 'Add group member to "%s"' => 'Tambahkan anggota grup ke "%s"', + 'Group member added successfully.' => 'Anggota grup berhasil ditambahkan', + 'Unable to add group member.' => 'Tidak dapat menambahkan anggota grup', + 'Remove user from group "%s"' => 'Hapus pengguna dari grup "%s"', + 'User removed successfully from this group.' => 'Pengguna berhasil dihapus dari grup ini', + 'Unable to remove this user from the group.' => 'Tidak dapat menghapus pengguna ini dari grup', + 'Remove group' => 'Hapus grup', + 'Group removed successfully.' => 'Grup berhasil dihapus', + 'Unable to remove this group.' => 'Tidak dapat menghapus grup ini', + 'Project Permissions' => 'Izin Proyek', + 'Manager' => 'Manajer', + 'Project Manager' => 'Manajer Proyek', + 'Project Member' => 'Anggota Proyek', + 'Project Viewer' => 'Penonton Proyek', + 'Your account is locked for %d minutes' => 'Akun anda dikunci untuk %d menit', + 'Invalid captcha' => 'Captcha tidak sesuai', + 'The name must be unique' => 'Nama harus unik', + 'View all groups' => 'Lihat semua grup', + 'There is no user available.' => 'Tidak ada pengguna yang tersedia', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Anda yakin mau menghapus pengguna "%s" dari grup "%s"?', + 'There is no group.' => 'Tidak ada grup', + 'Add group member' => 'Tambah anggota grup', + 'Do you really want to remove this group: "%s"?' => 'Anda yakin mau menghapus grup ini: "%s"?', + 'There is no user in this group.' => 'Tidak ada pengguna dalam grup ini', + 'Permissions' => 'Izin', + 'Allowed Users' => 'Pengguna Yang Diizinkan', + 'No specific user has been allowed.' => 'Tidak ada user yang diperbolehkan secara khusus', + 'Role' => 'Peran', + 'Enter user name...' => 'Masukkan nama pengguna...', + 'Allowed Groups' => 'Grup Yang Diizinkan', + 'No group has been allowed.' => 'Tidak ada grup yang diperbolehkan secara khusus', + 'Group' => 'Grup', + 'Group Name' => 'Nama Grup', + 'Enter group name...' => 'Masukkan nama grup...', + 'Role:' => 'Peran:', + 'Project members' => 'Anggota proyek', + '%s mentioned you in the task #%d' => '%s menyebut Anda dalam tugas #%d', + '%s mentioned you in a comment on the task #%d' => '%s menyebut Anda dalam komentar pada tugas #%d', + 'You were mentioned in the task #%d' => 'Anda disebutkan dalam tugas #%d', + 'You were mentioned in a comment on the task #%d' => 'Anda disebutkan dalam komentar pada tugas #%d', + 'Estimated hours: ' => 'Estimasi jam: ', + 'Actual hours: ' => 'Jam sebenarnya: ', + 'Hours Spent' => 'Jam dihabiskan', + 'Hours Estimated' => 'Jam diperkirakan', + 'Estimated Time' => 'Waktu Estimasi', + 'Actual Time' => 'Waktu Sebenarnya', + 'Estimated vs actual time' => 'Estimasi vs waktu sebenarnya', + 'RUB - Russian Ruble' => 'RUB - Rubel Rusia', + 'Assign the task to the person who does the action when the column is changed' => 'Berikan tugas pada orang yang melakukan tindakan saat kolom diganti', + 'Close a task in a specific column' => 'Tutup tugas di kolom tertentu', + 'Time-based One-time Password Algorithm' => 'Algoritma Password Satu-Kali Berbasis-Waktu', + 'Two-Factor Provider: ' => 'Penyedia Dua-Faktor', + 'Disable two-factor authentication' => 'Nonaktifkan otentikasi dua-faktor', + 'Enable two-factor authentication' => 'Aktifkan otentikasi dua-faktor', + 'There is no integration registered at the moment.' => 'Tidak ada integrasi yang didaftarkan untuk saat ini', + 'Password Reset for Kanboard' => 'Reset Password untuk Kanboard', + 'Forgot password?' => 'Lupa password?', + 'Enable "Forget Password"' => 'Aktifkan "Lupa Password"', + 'Password Reset' => 'Reset Password', + 'New password' => 'Password baru', + 'Change Password' => 'Ganti Password', + 'To reset your password click on this link:' => 'Untuk reset password Anda klik tautan ini:', + 'Last Password Reset' => 'Reset Password Terakhir', + 'The password has never been reinitialized.' => 'Password tidak pernah di inisialisasi ulang', + 'Creation' => 'Pembuatan', + 'Expiration' => 'Kadaluarsa', + 'Password reset history' => 'Sejarah reset password', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Semua tugas dalam kolom "%s" dan swimlane "%s" telah berhasil ditutup', + 'Do you really want to close all tasks of this column?' => 'Apakah Anda yakin mau menutup semua tugas dalam kolom ini?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d tugas dalam kolom "%s" dan swimlane "%s" akan ditutup.', + 'Close all tasks in this column and this swimlane' => 'Tutup semua tugas dalam kolom ini', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Tidak ada plugin yang mendaftarkan metode pemberitahuan proyek. Anda masih bisa mengatur pemberitahuan individu di dalam profil pengguna Anda.', + 'My dashboard' => 'Dasbor saya', + 'My profile' => 'Profil saya', + 'Project owner: ' => 'Pemilik proyek', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'Identifier proyek adalah opsional dan harus alfanumerik, contoh: MYPROJECT.', + 'Project owner' => 'Pemilik proyek', + 'Personal projects do not have users and groups management.' => 'Proyek pribadi tidak memiliki manajemen pengguna dan grup', + 'There is no project member.' => 'Tidak ada anggota proyek', + 'Priority' => 'Prioritas', + 'Task priority' => 'Prioritas tugas', + 'General' => 'Umum', + 'Dates' => 'Tanggal', + 'Default priority' => 'Prioritas default', + 'Lowest priority' => 'Prioritas terendah', + 'Highest priority' => 'Prioritas tertinggi', + 'Close a task when there is no activity' => 'Tutup tugas jika tidak ada aktifitas', + 'Duration in days' => 'Durasi dalam hari', + 'Send email when there is no activity on a task' => 'Kirim email jika tidak ada aktifitas dalam tugas', + 'Unable to fetch link information.' => 'Tidak dapat mengambil informasi tautan', + 'Daily background job for tasks' => 'Tugas latar belakang harian untuk tugas', + 'Auto' => 'Otomatis', + 'Related' => 'Terkait', + 'Attachment' => 'Lampiran', + 'Web Link' => 'Tautan web', + 'External links' => 'Tautan eksternal', + 'Add external link' => 'Tambah tautan eksternal', + 'Type' => 'Tipe', + 'Dependency' => 'Ketergantungan', + 'Add internal link' => 'Tambah tautan internal', + 'Add a new external link' => 'Tambah tautan eksternal baru', + 'Edit external link' => 'Rubah tautan eksternal', + 'External link' => 'Tautan eksternal', + 'Copy and paste your link here...' => 'Copy dan paste tautan anda di sini...', + 'URL' => 'URL', + 'Internal links' => 'Tautan internal', + 'Assign to me' => 'Tugaskan ke saya', + 'Me' => 'Saya', + 'Do not duplicate anything' => 'Jangan menduplikasi apapun', + 'Projects management' => 'Manajemen proyek', + 'Users management' => 'Manajemen pengguna', + 'Groups management' => 'Manajemen grup', + 'Create from another project' => 'Buat dari proyek lain', + 'open' => 'buka', + 'closed' => 'tutup', + 'Priority:' => 'Prioritas', + 'Reference:' => 'Referensi', + 'Complexity:' => 'Kompleksitas', + 'Swimlane:' => 'Swimlane', + 'Column:' => 'Kolom:', + 'Position:' => 'Posisi:', + 'Creator:' => 'Pembuat:', + 'Time estimated:' => 'Estimasi waktu:', + '%s hours' => '%s jam', + 'Time spent:' => 'Waktu yang dihabiskan', + 'Created:' => 'Dibuat:', + 'Modified:' => 'Dimodifikasi:', + 'Completed:' => 'Selesai:', + 'Started:' => 'Dimulai:', + 'Moved:' => 'Dipindahkan:', + 'Task #%d' => 'Tugas #%d', + 'Time format' => 'Format waktu', + 'Start date: ' => 'Tanggal mulai: ', + 'End date: ' => 'Tanggal berakhir: ', + 'New due date: ' => 'Tanggal jatuh tempo baru: ', + 'Start date changed: ' => 'Tanggal mulai diganti: ', + 'Disable personal projects' => 'Nonaktifkan proyek pribadi', + 'Do you really want to remove this custom filter: "%s"?' => 'Apakah Anda yakin mau menghapus saringan kustom: "%s"?', + 'Remove a custom filter' => 'Hapus saringan kustom', + 'User activated successfully.' => 'Pengguna berhasil diaktifkan.', + 'Unable to enable this user.' => 'Tidak dapat mengaktifkan pengguna ini.', + 'User disabled successfully.' => 'Pengguna berhasil dinonaktifkan.', + 'Unable to disable this user.' => 'Tidak dapat menonaktifkan pengguna ini.', + 'All files have been uploaded successfully.' => 'Semua berkas berhasil di unggah.', + 'The maximum allowed file size is %sB.' => 'Maksimum ukuran berkas yang diiziinkan adalah %sB.', + 'Drag and drop your files here' => 'Drag and drop file Anda di sini', + 'choose files' => 'pilih berkas', + 'View profile' => 'Lihat profil', + 'Two Factor' => 'Dua Faktor', + 'Disable user' => 'Nonaktifkan pengguna', + 'Do you really want to disable this user: "%s"?' => 'Anda yakin mau menonaktifkan pengguna ini: "%s"?', + 'Enable user' => 'Aktifkan pengguna', + 'Do you really want to enable this user: "%s"?' => 'Anda yakin mau mengaktifkan pengguna ini: "%s"?', + 'Download' => 'Unduh', + 'Uploaded: %s' => 'Diunggah: %s', + 'Size: %s' => 'Ukuran: %s', + 'Uploaded by %s' => 'Diunggah oleh %s', + 'Filename' => 'Nama berkas', + 'Size' => 'Ukuran', + 'Column created successfully.' => 'Kolom berhasil dibuat.', + 'Another column with the same name exists in the project' => 'Ada kolom lain dengan nama yang sama di proyek ini', + 'Default filters' => 'Saringan default', + 'Your board doesn\'t have any columns!' => 'Papan Anda tidak memiliki kolom!', + 'Change column position' => 'Ganti posisi kolom', + 'Switch to the project overview' => 'Pindah ke ringkasan proyek', + 'User filters' => 'Saringan pengguna', + 'Category filters' => 'Saringan kategori', + 'Upload a file' => 'Unggah berkas', + 'View file' => 'Lihat berkas', + 'Last activity' => 'Aktivitas terakhir', + 'Change subtask position' => 'Ganti posisi sub-tugas', + 'This value must be greater than %d' => 'Nilai ini harus lebih besar dari %d', + 'Another swimlane with the same name exists in the project' => 'Swimlane lain dengan nama yang sama sudah ada di proyek ini', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Contoh: https://contoh.kanboard.org/ (digunakan untuk menghasilkan URL yang absolut', + 'Actions duplicated successfully.' => 'Tindakan berhasil di duplikasi.', + 'Unable to duplicate actions.' => 'Tidak bisa menduplikasi tindakan.', + 'Add a new action' => 'Tambahkan tindakan baru', + 'Import from another project' => 'Impor dari proyek lain', + 'There is no action at the moment.' => 'Belum ada tindakan pada saat ini.', + 'Import actions from another project' => 'Impor tindakan dari proyek lain', + 'There is no available project.' => 'Tidak ada proyek yang tersedia', + 'Local File' => 'Berkas Lokal', + 'Configuration' => 'Konfigurasi', + 'PHP version:' => 'Versi PHP:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'Versi sistem operasi:', + 'Database version:' => 'Versi database:', + 'Browser:' => 'Peramban:', + 'Task view' => 'Tampilan Tugas', + 'Edit task' => 'Edit tugas', + 'Edit description' => 'Edit deskripsi', + 'New internal link' => 'Tautan internal baru', + 'Display list of keyboard shortcuts' => 'Tampilkan daftar pintasan keyboard', + 'Avatar' => 'Avatar', + 'Upload my avatar image' => 'Unggah foto avatar saya', + 'Remove my image' => 'Hapus foto saya', + 'The OAuth2 state parameter is invalid' => 'Status parameter OAuth2 tidak sesuai', + 'User not found.' => 'Pengguna tidak ditemukan.', + 'Search in activity stream' => 'Cari di saluran aktivitas', + 'My activities' => 'Aktivitas saya', + 'Activity until yesterday' => 'Aktivitas sampai kemarin', + 'Activity until today' => 'Aktivitas sampai hari ini', + 'Search by creator: ' => 'Cari berdasarkan pembuat: ', + 'Search by creation date: ' => 'Cari berdasarkan tanggal pembuatan: ', + 'Search by task status: ' => 'Cari berdasarkanstatus tugas: ', + 'Search by task title: ' => 'Cari berdasarkan judul tugas: ', + 'Activity stream search' => 'Pencarian saluran aktivitas', + 'Projects where "%s" is manager' => 'Proyek dimana "%s" adalah manajernya', + 'Projects where "%s" is member' => 'Proyek dimana "%s" adalah anggotanya', + 'Open tasks assigned to "%s"' => 'Tugas terbuka ditugaskan pada "%s"', + 'Closed tasks assigned to "%s"' => 'Tugas tertutup ditugaskan pada "%s"', + 'Assign automatically a color based on a priority' => 'Berikan warna otomatis berdasarkan prioritas', + 'Overdue tasks for the project(s) "%s"' => 'Tugas yang kadaluarsa untuk proyek "%s"', + 'Upload files' => 'Unggah berkas', + 'Installed Plugins' => 'Plugin terpasang', + 'Plugin Directory' => 'Direktori Plugin', + 'Plugin installed successfully.' => 'Plugin berhasil dipasang.', + 'Plugin updated successfully.' => 'Plugin berhasil diperbarui.', + 'Plugin removed successfully.' => 'Plugin berhasil dihapus.', + 'Subtask converted to task successfully.' => 'Sub-tugas berhasil diubah menjadi tugas.', + 'Unable to convert the subtask.' => 'Tidak dapat mengubah sub-tugas.', + 'Unable to extract plugin archive.' => 'Tidak bisa mengekstrak arsip plugin.', + 'Plugin not found.' => 'Plugin tidak ditemukan.', + 'You don\'t have the permission to remove this plugin.' => 'Anda tidak memiliki izin untuk menghapus plugin ini.', + 'Unable to download plugin archive.' => 'Tidak dapat mengunduh arsip plugin.', + 'Unable to write temporary file for plugin.' => 'Tidak dapat menulis berkas sementara untuk plugin.', + 'Unable to open plugin archive.' => 'Tidak dapat membuka arsip plugin.', + 'There is no file in the plugin archive.' => 'Tidak ada berkas di dalam arsip plugin.', + 'Create tasks in bulk' => 'Buat tugas sekaligus', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Instalasi Kanboard Anda tidak diatur untuk memasang plugin dari antar muka pengguna.', + 'There is no plugin available.' => 'Tidak ada plugin yang tersedia.', + 'Install' => 'Pasang', + 'Update' => 'Perbarui', + 'Up to date' => 'Terbaru', + 'Not available' => 'Tidak tersedia', + 'Remove plugin' => 'Hapus plugin', + 'Do you really want to remove this plugin: "%s"?' => 'Anda yakin ingin menghapus plugin ini: "%s"?', + 'Uninstall' => 'Lepaskan', + 'Listing' => 'Daftar', + 'Metadata' => 'Metadata', + 'Manage projects' => 'Atur proyek', + 'Convert to task' => 'Ubah menjadi tugas', + 'Convert sub-task to task' => 'Ubah sub-tugas menjadi tugas', + 'Do you really want to convert this sub-task to a task?' => 'Anda yakin ingin mengubah sub-tugas ini menjadi tugas?', + 'My task title' => 'Judul tugas saya', + 'Enter one task by line.' => 'Masukkan satu tugas berdasarkan baris.', + 'Number of failed login:' => 'Jumlah login yang gagal:', + 'Account locked until:' => 'Akun terkunci hingga:', + 'Email settings' => 'Pengaturan email', + 'Email sender address' => 'Alamat pengirim email', + 'Email transport' => 'Transportasi email', + 'Webhook token' => 'Token Webhook', + 'Project tags management' => 'Manajemen tag proyek', + 'Tag created successfully.' => 'Tag berhasil dibuat.', + 'Unable to create this tag.' => 'Tidak dapat membuat tag ini.', + 'Tag updated successfully.' => 'Tag berhasil diperbarui', + 'Unable to update this tag.' => 'Tidak dapat memperbarui tag ini.', + 'Tag removed successfully.' => 'Tag berhasil dihapus.', + 'Unable to remove this tag.' => 'Tidak dapat menghapus tag ini.', + 'Global tags management' => 'Manajemen tag global', + 'Tags' => 'Tag', + 'Tags management' => 'Manajemen tag', + 'Add new tag' => 'Tambah tag baru', + 'Edit a tag' => 'Edit tag', + 'Project tags' => 'Tag proyek', + 'There is no specific tag for this project at the moment.' => 'Saat ini tidak ada tag yang spesifik pada proyek ini.', + 'Tag' => 'Tag', + 'Remove a tag' => 'Hapus tag', + 'Do you really want to remove this tag: "%s"?' => 'Anda yakin ingin menghapus tag ini: "%s"?', + 'Global tags' => 'Tag global', + 'There is no global tag at the moment.' => 'Saat ini tidak ada tag global.', + 'This field cannot be empty' => 'Bidang ini tidak boleh kosong', + 'Close a task when there is no activity in a specific column' => 'Tutup tugas saat tidak ada aktivitas di kolom tertentu', + '%s removed a subtask for the task #%d' => '%s menghapus sub-tugas untuk tugas #%d', + '%s removed a comment on the task #%d' => '%s menghapus komentar pada tugas #%d', + 'Comment removed on task #%d' => 'Komentar dihapus pada tugas #%d', + 'Subtask removed on task #%d' => 'Sub-tugas dihapus pada tugas #%d', + 'Hide tasks in this column in the dashboard' => 'Sembunyikan tugas-tugas di kolom ini di dasbor', + '%s removed a comment on the task %s' => '%s menghapus komentar pada tugas %s', + '%s removed a subtask for the task %s' => '%s menghapus sub-tugas untuk tugas %s', + 'Comment removed' => 'Komentar dihapus', + 'Subtask removed' => 'Sub-tugas dihapus', + '%s set a new internal link for the task #%d' => '%s memasang tautan internal baru untuk tugas #%d', + '%s removed an internal link for the task #%d' => '%s menghapus tautan internal untuk tugas #%d', + 'A new internal link for the task #%d has been defined' => 'Tautan internal baru untuk tugas #%d telah ditentukan', + 'Internal link removed for the task #%d' => 'Tautan internal untuk tugas #%d telah dihapus', + '%s set a new internal link for the task %s' => '%s memasang tautan internal baru untuk tugas %s', + '%s removed an internal link for the task %s' => '%s menghapus tautan internal untuk tugas %s', + 'Automatically set the due date on task creation' => 'Otomatis memasang tanggal kadaluarsa saat pembuatan tugas', + 'Move the task to another column when closed' => 'Pindahkan tugas ke kolom lain saat ditutup', + 'Move the task to another column when not moved during a given period' => 'Pindahkan tugas ke kolom lain saat tidak dipindahkan selama periode yang diberikan', + 'Dashboard for %s' => 'Dasbor untuk %s', + 'Tasks overview for %s' => 'Ringkasan tugas-tugas untuk %s', + 'Subtasks overview for %s' => 'Ringkasan sub-tugas untuk %s', + 'Projects overview for %s' => 'Ringkasan proyek untuk %s', + 'Activity stream for %s' => 'Arus aktivitas untuk %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Berikan warna saat tugas dipindahkan ke swimlane tertentu', + 'Assign a priority when the task is moved to a specific swimlane' => 'Berikan prioritas saat tugas dipindahkan ke swimlane tertentu', + 'User unlocked successfully.' => 'Berhasil membuka blokir pengguna.', + 'Unable to unlock the user.' => 'Tidak bisa membuka blokir pengguna.', + 'Move a task to another swimlane' => 'Pindahkan tugas ke swimlane lain', + 'Creator Name' => 'Nama Pembuat', + 'Time spent and estimated' => 'Waktu yang dihabiskan dan diperkirakan', + 'Move position' => 'Pindahkan posisi', + 'Move task to another position on the board' => 'Pindahkan tugas ke posisi lain di dalam papan', + 'Insert before this task' => 'Masukkan sebelum tugas ini', + 'Insert after this task' => 'Masukkan setelah tugas ini', + 'Unlock this user' => 'Buka pengguna ini', + 'Custom Project Roles' => 'Peran Proyek Kustom', + 'Add a new custom role' => 'Tambahkan peran kustom baru', + 'Restrictions for the role "%s"' => 'Batasan untuk peran "%s"', + 'Add a new project restriction' => 'Tambahkan batasan proyek baru', + 'Add a new drag and drop restriction' => 'Tambahkan batasan drag and drop baru', + 'Add a new column restriction' => 'Tambahkan batasan kolom baru', + 'Edit this role' => 'Edit peran ini', + 'Remove this role' => 'Hapus peran ini', + 'There is no restriction for this role.' => 'Tidak ada batasan untuk peran ini.', + 'Only moving task between those columns is permitted' => 'Hanya diizinkan untuk memindahkan tugas diantara kolom-kolom tersebut', + 'Close a task in a specific column when not moved during a given period' => 'Tutup tugas pada kolom tertentu saat tidak dipindahkan pada periode yang diberikan', + 'Edit columns' => 'Edit kolom', + 'The column restriction has been created successfully.' => 'Batasan kolom berhasil dibuat.', + 'Unable to create this column restriction.' => 'Tidak dapat membuat batasan kolom ini.', + 'Column restriction removed successfully.' => 'Batasan kolom berhasil dihapus.', + 'Unable to remove this restriction.' => 'Gagal menghapus batasan ini.', + 'Your custom project role has been created successfully.' => 'Peran kustom proyek Anda berhasil dibuat.', + 'Unable to create custom project role.' => 'Tidak dapat membuat peran proyek kustom.', + 'Your custom project role has been updated successfully.' => 'Peran proyek kustom Anda berhasil diperbarui.', + 'Unable to update custom project role.' => 'Tidak dapat memperbarui peran kustom proyek', + 'Custom project role removed successfully.' => 'Peran kustom proyek berhasil dihapus.', + 'Unable to remove this project role.' => 'Tidak dapat menghapus peran proyek ini.', + 'The project restriction has been created successfully.' => 'Batasan proyek ini berhasil dibuat.', + 'Unable to create this project restriction.' => 'Tidak dapat membuat batasan proyek ini.', + 'Project restriction removed successfully.' => 'Batasan proyek berhasil dihapus.', + 'You cannot create tasks in this column.' => 'Anda tidak dapat membuat tugas di kolom ini.', + 'Task creation is permitted for this column' => 'Pembuatan tugas diizinkan untuk kolom ini', + 'Closing or opening a task is permitted for this column' => 'Penutupan atau pembukaan tugas diizinkan untuk kolom ini', + 'Task creation is blocked for this column' => 'Pembuatan tugas diblokir dari kolom ini', + 'Closing or opening a task is blocked for this column' => 'Penutupan atau pembukaan tugas diblokir di kolom ini', + 'Task creation is not permitted' => 'Oembuatan tugas tidak diizinkan', + 'Closing or opening a task is not permitted' => 'Penutupan atau pembukaan tugas tidak diizinkan', + 'New drag and drop restriction for the role "%s"' => 'Larangan drag and drop baru untuk peran "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Orang-orang dengan peran ini dapat memindahkan tugas diantara kolom sumber dan destinasi.', + 'Remove a column restriction' => 'Hapus pembatasan kolom', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Anda yakin ingin menghapus pembatasan kolom ini: "%s" ke "%s"?', + 'New column restriction for the role "%s"' => 'Kolom pembatasan baru untuk peran "%s"', + 'Rule' => 'Aturan', + 'Do you really want to remove this column restriction?' => 'Anda yakin ingin menghapus pembatasan kolom ini?', + 'Custom roles' => 'Peran kustom', + 'New custom project role' => 'Peran kustom proyek baru', + 'Edit custom project role' => 'Edit peran kustom proyek', + 'Remove a custom role' => 'Hapus peran kustom', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Anda yakin ingin menghapus peran kustom ini: "%s"? Semua orang yang memiliki peran ini akan berubah menjadi anggota proyek.', + 'There is no custom role for this project.' => 'Tidak ada peran kustom untuk proyek ini.', + 'New project restriction for the role "%s"' => 'Batasan proyek baru untuk peran "%s"', + 'Restriction' => 'Pembatasan', + 'Remove a project restriction' => 'Hapus batasan proyek', + 'Do you really want to remove this project restriction: "%s"?' => 'Anda yakin ingin menghapus pembatasan proyek ini: "%s"?', + 'Duplicate to multiple projects' => 'Duplikasikan ke banyak proyek', + 'This field is required' => 'Bidang ini dibutuhkan', + 'Moving a task is not permitted' => 'Memindahkan tugas tidak diizinkan', + 'This value must be in the range %d to %d' => 'Nilai ini harus berkisar antara %d hingga %d', + 'You are not allowed to move this task.' => 'Anda tidak diizinkan untuk memindahkan tugas ini.', + 'API User Access' => 'API Akses Pengguna', + 'Preview' => 'Pratinjau', + 'Write' => 'Tulis', + 'Write your text in Markdown' => 'Tuliskan teks Anda di Markdown', + 'No personal API access token registered.' => 'Tidak ada token akses API personal yang terdaftar.', + 'Your personal API access token is "%s"' => 'Token akses API personal Anda adalah "%s"', + 'Remove your token' => 'Hapus token Anda', + 'Generate a new token' => 'Generate token baru', + 'Showing %d-%d of %d' => 'Menampilkan %d-%d of %d', + 'Outgoing Emails' => 'Email Keluar', + 'Add or change currency rate' => 'Tambah atau ubah nilai mata uang', + 'Reference currency: %s' => 'referensi Mata uang: %s', + 'Add custom filters' => 'Tambahkan filter khusus', + 'Export' => 'Export', + 'Add link label' => 'Tambahkan label tautan', + 'Incompatible Plugins' => 'Plugin Tidak Kompatibel', + 'Compatibility' => 'Kesesuaian', + 'Permissions and ownership' => 'Izin dan kepemilikan', + 'Priorities' => 'Prioritas', + 'Close this window' => 'Tutup jendela ini', + 'Unable to upload this file.' => 'Tidak dapat mengupload file ini.', + 'Import tasks' => 'Impor tugas', + 'Choose a project' => 'Pilih sebuah proyek', + 'Profile' => 'Profil', + 'Application role' => 'Peran aplikasi', + '%d invitations were sent.' => '%d undangan telah dikirim.', + '%d invitation was sent.' => '%d undangan telah dikirim.', + 'Unable to create this user.' => 'Tidak dapat membuat pengguna.', + 'Kanboard Invitation' => 'Undangan Kanboard', + 'Visible on dashboard' => 'Terlihat pada dashboard', + 'Created at:' => 'Dibuat pada:', + 'Updated at:' => 'Diperbarui pada:', + 'There is no custom filter.' => 'Tidak ada filter khusus.', + 'New User' => 'Pengguna Baru', + 'Authentication' => 'Otentikasi', + 'If checked, this user will use a third-party system for authentication.' => 'Jika dicentang, pengguna ini akan menggunakan sistem pihak ketiga untuk otentikasi.', + 'The password is necessary only for local users.' => 'Kata sandi hanya diperlukan untuk pengguna lokal.', + 'You have been invited to register on Kanboard.' => 'Anda telah diundang untuk mendaftar di Kanboard.', + 'Click here to join your team' => 'Klik di sini untuk bergabung dengan tim Anda', + 'Invite people' => 'Undang orang', + 'Emails' => 'Email', + 'Enter one email address by line.' => 'Masukkan satu alamat email per baris.', + 'Add these people to this project' => 'Tambahkan orang-orang ini ke proyek ini', + 'Add this person to this project' => 'Tambahkan orang ini ke proyek ini', + 'Sign-up' => 'Daftar', + 'Credentials' => 'Credential', + 'New user' => 'Pengguna baru', + 'This username is already taken' => 'Nama pengguna ini sudah dipakai', + 'Your profile must have a valid email address.' => 'Profil Anda harus memiliki alamat email yang valid.', + 'TRL - Turkish Lira' => 'TRL - Lira Turki', + 'The project email is optional and could be used by several plugins.' => 'Email proyek adalah opsional dan dapat digunakan oleh beberapa plugin.', + 'The project email must be unique across all projects' => 'Email proyek harus unik di semua proyek', + 'The email configuration has been disabled by the administrator.' => 'Konfigurasi email telah dinonaktifkan oleh administrator.', + 'Close this project' => 'Tutup proyek ini', + 'Open this project' => 'Buka proyek ini', + 'Close a project' => 'Tutup proyek', + 'Do you really want to close this project: "%s"?' => 'Anda yakin ingin menutup proyek ini: "%s"?', + 'Reopen a project' => 'Buka kembali proyek', + 'Do you really want to reopen this project: "%s"?' => 'Anda yakin ingin membuka kembali proyek ini: "%s"?', + 'This project is open' => 'Proyek ini terbuka', + 'This project is closed' => 'Proyek ini ditutup', + 'Unable to upload files, check the permissions of your data folder.' => 'Tidak dapat mengunggah file, periksa izin folder data Anda.', + 'Another category with the same name exists in this project' => 'Kategori lain dengan nama yang sama ada dalam proyek ini', + 'Comment sent by email successfully.' => 'Komentar berhasil dikirim melalui email.', + 'Sent by email to "%s" (%s)' => 'Dikirim melalui email ke "%s" (%s)', + 'Unable to read uploaded file.' => 'Tidak dapat membaca file yang diunggah.', + 'Database uploaded successfully.' => 'Database berhasil diunggah.', + 'Task sent by email successfully.' => 'Tugas berhasil dikirim melalui email.', + 'There is no category in this project.' => 'Tidak ada kategori dalam proyek ini.', + 'Send by email' => 'Kirim melalui email', + 'Create and send a comment by email' => 'Buat dan kirim komentar melalui email', + 'Subject' => 'Subjek', + 'Upload the database' => 'Unggah database', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Anda dapat mengunggah basis data Sqlite yang diunduh sebelumnya (format Gzip).', + 'Database file' => 'File database', + 'Upload' => 'Unggah', + 'Your project must have at least one active swimlane.' => 'Proyek Anda harus memiliki setidaknya satu swimlane aktif.', + 'Project: %s' => 'Proyek: %s', + 'Automatic action not found: "%s"' => 'Tindakan otomatis tidak ditemukan: "%s', + '%d projects' => '%d proyek', + '%d project' => '%d proyek', + 'There is no project.' => 'Tidak ada proyek', + 'Sort' => 'Urutkan', + 'Project ID' => 'ID Proyek', + 'Project name' => 'Nama proyek', + 'Public' => 'Publik', + 'Personal' => 'Pribadi', + '%d tasks' => '%d tugas', + '%d task' => '%d tugas', + 'Task ID' => 'ID Tugas', + 'Assign automatically a color when due date is expired' => 'Tetapkan warna secara otomatis ketika tanggal jatuh tempo kadaluarsa', + 'Total score in this column across all swimlanes' => 'Skor total di kolom ini di semua swimlanes', + 'HRK - Kuna' => 'HRK - Kuna', + 'ARS - Argentine Peso' => 'ARS - Peso Argentina', + 'COP - Colombian Peso' => 'COP - Peso Kolombia', + '%d groups' => '%d grup', + '%d group' => '%d grup', + 'Group ID' => 'ID Grup', + 'External ID' => 'ID External', + '%d users' => '%d pengguna', + '%d user' => '%d pengguna', + 'Hide subtasks' => 'Sembunyikan subtugas', + 'Show subtasks' => 'Tampilkan subtugas', + 'Authentication Parameters' => 'Parameter Otentikasi', + 'API Access' => 'Akses API', + 'No users found.' => 'Tidak ada pengguna yang ditemukan.', + 'User ID' => 'ID Pengguna', + 'Notifications are activated' => 'Notifikasi diaktifkan', + 'Notifications are disabled' => 'Notifikasi dinonaktifkan', + 'User disabled' => 'Pengguna dinonaktifkan', + '%d notifications' => '%d notifikasi', + '%d notification' => '%d notifikasi', + 'There is no external integration installed.' => 'Tidak ada integrasi eksternal yang terpasang.', + 'You are not allowed to update tasks assigned to someone else.' => 'Anda tidak diizinkan untuk memperbarui tugas yang diberikan kepada orang lain.', + 'You are not allowed to change the assignee.' => 'Anda tidak diizinkan untuk mengubah penerima.', + 'Task suppression is not permitted' => 'Penindasan tugas tidak diizinkan', + 'Changing assignee is not permitted' => 'Tidak diizinkan mengubah penerima', + 'Update only assigned tasks is permitted' => 'Perbarui hanya tugas yang diizinkan', + 'Only for tasks assigned to the current user' => 'Hanya untuk tugas yang diberikan kepada pengguna saat ini', + 'My projects' => 'Proyek saya', + 'You are not a member of any project.' => 'Anda bukan anggota proyek mana pun.', + 'My subtasks' => 'Subtugas saya', + '%d subtasks' => '%d subtugas', + '%d subtask' => '%d subtugas', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Hanya memindahkan tugas di antara kolom-kolom yang diizinkan untuk tugas yang diberikan kepada pengguna saat ini', + '[DUPLICATE]' => '[DUPLIKAT]', + 'DKK - Danish Krona' => 'DKK - Krona Denmark', + 'Remove user from group' => 'Hapus pengguna dari grup', + 'Assign the task to its creator' => 'Tetapkan tugas untuk pembuatnya', + 'This task was sent by email to "%s" with subject "%s".' => 'Tugas ini dikirim melalui email ke "%s" dengan subjek "%s".', + 'Predefined Email Subjects' => 'Default Subjek Email', + 'Write one subject by line.' => 'Tulis satu subjek per baris.', + 'Create another link' => 'Buat tautan lain', + 'BRL - Brazilian Real' => 'BRL - Real Brasil', + 'Add a new Kanboard task' => 'Tambahkan tugas Kanboard baru', + 'Subtask not started' => 'Subtugas belum dimulai', + 'Subtask currently in progress' => 'Subtugas sedang dalam proses', + 'Subtask completed' => 'Subtugas selesai', + 'Subtask added successfully.' => 'Subtugas berhasil ditambahkan.', + '%d subtasks added successfully.' => '%d subtugas berhasil ditambahkan.', + 'Enter one subtask by line.' => 'Masukkan satu subtugas per baris.', + 'Predefined Contents' => 'Default konten', + 'Predefined contents' => 'Default konten', + 'Predefined Task Description' => 'Default Deskripsi Tugas', + 'Do you really want to remove this template? "%s"' => 'Anda yakin ingin menghapus template ini? "%s"', + 'Add predefined task description' => 'Tambahkan deskripsi tugas default', + 'Predefined Task Descriptions' => 'Default deskripsi tugas', + 'Template created successfully.' => 'Template berhasil dibuat.', + 'Unable to create this template.' => 'Tidak dapat membuat template ini.', + 'Template updated successfully.' => 'Template berhasil diperbarui.', + 'Unable to update this template.' => 'Tidak dapat memperbarui template ini.', + 'Template removed successfully.' => 'Template berhasil dihapus.', + 'Unable to remove this template.' => 'Tidak dapat menghapus template ini.', + 'Template for the task description' => 'Template untuk deskripsi tugas', + 'The start date is greater than the end date' => 'Tanggal mulai lebih besar dari tanggal akhir', + 'Tags must be separated by a comma' => 'Tag harus dipisahkan dengan koma', + 'Only the task title is required' => 'Hanya judul tugas yang dibutuhkan', + 'Creator Username' => 'Nama Pengguna Pembuat', + 'Color Name' => 'Nama Warna', + 'Column Name' => 'Nama Kolom', + 'Swimlane Name' => 'Nama Swimlane', + 'Time Estimated' => 'Perkiraan Waktu', + 'Time Spent' => 'Waktu yang dihabiskan', + 'External Link' => 'Tautan Eksternal', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Fitur ini mengaktifkan umpan iCal, umpan RSS, dan tampilan papan publik.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Hentikan timer semua subtugas saat memindahkan tugas ke kolom lain', + 'Subtask Title' => 'Judul Subtugas', + 'Add a subtask and activate the timer when moving a task to another column' => 'Tambahkan subtugas dan aktifkan pengatur waktu saat memindahkan tugas ke kolom lain', + 'days' => 'hari', + 'minutes' => 'menit', + 'seconds' => 'detik', + 'Assign automatically a color when preset start date is reached' => 'Tetapkan warna secara otomatis ketika tanggal mulai preset tercapai', + 'Move the task to another column once a predefined start date is reached' => 'Pindahkan tugas ke kolom lain setelah tanggal mulai yang ditentukan tercapai', + 'This task is now linked to the task %s with the relation "%s"' => 'Tugas ini sekarang ditautkan ke tugas %s dengan relasi "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'Tautan dengan relasi "%s" ke tugas %s telah dihapus', + 'Custom Filter:' => 'Kustomisasi Filter:', + 'Unable to find this group.' => 'Tidak dapat menemukan grup ini.', + '%s moved the task #%d to the column "%s"' => '%s memindahkan tugas #%d ke kolom "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => '%s memindahkan tugas #%d ke posisi %d di kolom "%s"', + '%s moved the task #%d to the swimlane "%s"' => '%s memindahkan tugas #%d ke swimlane "%s"', + '%sh spent' => '%sh menghabiskan', + '%sh estimated' => '%sh perkiraan', + 'Select All' => 'Pilih Semua', + 'Unselect All' => 'Batal Pilih Semua', + 'Apply action' => 'Terapkan tindakan', + 'Move selected tasks to another column or swimlane' => 'Pindahkan tugas yang dipilih ke kolom lain', + 'Edit tasks in bulk' => 'Edit tugas secara massal', + 'Choose the properties that you would like to change for the selected tasks.' => 'Pilih properti yang ingin Anda ubah untuk tugas yang dipilih.', + 'Configure this project' => 'Konfigurasi proyek ini', + 'Start now' => 'Mulai sekarang', + '%s removed a file from the task #%d' => '%s menghapus file dari tugas #%d', + 'Attachment removed from task #%d: %s' => 'Lampiran dihapus dari tugas #%d: %s', + 'No color' => 'Tanpa warna', + 'Attachment removed "%s"' => 'Lampiran dihapus "%s"', + '%s removed a file from the task %s' => '%s menghapus file dari tugas %s', + 'Move the task to another swimlane when assigned to a user' => 'Pindahkan tugas ke swimlane lain ketika ditugaskan ke pengguna', + 'Destination swimlane' => 'Swimlane tujuan', + 'Assign a category when the task is moved to a specific swimlane' => 'Tetapkan kategori ketika tugas dipindahkan ke swimlane tertentu', + 'Move the task to another swimlane when the category is changed' => 'Pindahkan tugas ke swimlane lain saat kategorinya diubah', + 'Reorder this column by priority (ASC)' => 'Susun ulang kolom ini berdasarkan prioritas (ASC)', + 'Reorder this column by priority (DESC)' => 'Susun ulang kolom ini berdasarkan prioritas (DESC)', + 'Reorder this column by assignee and priority (ASC)' => 'Susun ulang kolom ini menurut penerima tugas dan prioritas (ASC)', + 'Reorder this column by assignee and priority (DESC)' => 'Susun ulang kolom ini menurut penerima tugas dan prioritas (DESC)', + 'Reorder this column by assignee (A-Z)' => 'Susun ulang kolom ini oleh penerima tugas (A-Z)', + 'Reorder this column by assignee (Z-A)' => 'Susun ulang kolom ini oleh penerima tugas (Z-A)', + 'Reorder this column by due date (ASC)' => 'Susun ulang kolom ini sebelum tanggal jatuh tempo (ASC)', + 'Reorder this column by due date (DESC)' => 'Susun ulang kolom ini berdasarkan tanggal jatuh tempo (DESC)', + 'Reorder this column by id (ASC)' => 'Susun ulang kolom ini berdasarkan id (ASC)', + 'Reorder this column by id (DESC)' => 'Susun ulang kolom ini berdasarkan id (DESC)', + '%s moved the task #%d "%s" to the project "%s"' => '%s memindahkan tugas #%d "%s" ke proyek "%s"', + 'Task #%d "%s" has been moved to the project "%s"' => 'Task #%d "%s" telah dipindahkan ke proyek "%s"', + 'Move the task to another column when the due date is less than a certain number of days' => 'Pindahkan tugas ke kolom lain ketika batas waktu kurang dari jumlah hari tertentu', + 'Automatically update the start date when the task is moved away from a specific column' => 'Secara otomatis memperbarui tanggal mulai ketika tugas dipindahkan dari kolom tertentu', + 'HTTP Client:' => 'HTTP Client:', + 'Assigned' => 'Ditugaskan', + 'Task limits apply to each swimlane individually' => 'Batas tugas berlaku untuk setiap swimlane secara individual', + 'Column task limits apply to each swimlane individually' => 'Batas tugas kolom berlaku untuk setiap swimlane secara individual', + 'Column task limits are applied to each swimlane individually' => 'Batas tugas kolom diterapkan ke setiap swimlane secara individual', + 'Column task limits are applied across swimlanes' => 'Batas tugas kolom diterapkan di seluruh swimlanes', + 'Task limit: ' => 'Batas tugas:', + 'Change to global tag' => 'Ubah ke tag global', + 'Do you really want to make the tag "%s" global?' => 'Anda yakin ingin menjadikan tag "%s" global?', + 'Enable global tags for this project' => 'Aktifkan tag global untuk proyek ini', + 'Group membership(s):' => 'Keanggotaan grup:', + '%s is a member of the following group(s): %s' => '%s adalah anggota dari grup berikut: %s', + '%d/%d group(s) shown' => '%d/%d grup ditampilkan', + 'Subtask creation or modification' => 'Pembuatan atau modifikasi subtugas', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Tetapkan tugas ke pengguna tertentu saat tugas dipindahkan ke swimlane tertentu', + 'Comment' => 'Komentar', + 'Collapse vertically' => 'Ciutkan secara vertikal', + 'Expand vertically' => 'Bentangkan secara vertikal', + 'MXN - Mexican Peso' => 'MXN - Peso Meksiko', + 'Estimated vs actual time per column' => 'Estimasi vs waktu aktual per kolom', + 'HUF - Hungarian Forint' => 'HUF - Forint Hongaria', + 'XBT - Bitcoin' => 'XBT - Bitcoin', + 'You must select a file to upload as your avatar!' => 'Anda harus memilih berkas untuk diunggah sebagai avatar Anda!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'Berkas yang Anda unggah bukan gambar yang valid! (Hanya berkas *.gif, *.jpg, *.jpeg, dan *.png yang diizinkan!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Atur tenggat waktu secara otomatis ketika tugas dipindahkan dari kolom tertentu', + 'No other projects found.' => 'Tidak ada proyek lain yang ditemukan.', + 'Tasks copied successfully.' => 'Tugas berhasil disalin.', + 'Unable to copy tasks.' => 'Tidak dapat menyalin tugas.', + 'Theme' => 'Tema', + 'Theme:' => 'Tema:', + 'Light theme' => 'Tema terang', + 'Dark theme' => 'Tema gelap', + 'Automatic theme - Sync with system' => 'Tema otomatis - Sinkronkan dengan sistem', + 'Application managers or more' => 'Pengelola aplikasi atau lebih', + 'Administrators' => 'Administrator', + 'Visibility:' => 'Visibilitas:', + 'Standard users' => 'Pengguna standar', + 'Visibility is required' => 'Visibilitas diperlukan', + 'The visibility should be an app role' => 'Visibilitas harus berupa peran aplikasi', + 'Reply' => 'Balas', + '%s wrote: ' => '%s menulis: ', + 'Number of visible tasks in this column and swimlane' => 'Jumlah tugas yang terlihat di kolom dan lajur ini', + 'Number of tasks in this swimlane' => 'Jumlah tugas di lajur ini', + 'Unable to find another subtask in progress, you can close this window.' => 'Tidak dapat menemukan subtugas lain dalam proses, Anda dapat menutup jendela ini.', + 'This theme is invalid' => 'Tema ini tidak valid', + 'This role is invalid' => 'Peran ini tidak valid', + 'This timezone is invalid' => 'Zona waktu ini tidak valid', + 'This language is invalid' => 'Bahasa ini tidak valid', + 'This URL is invalid' => 'URL ini tidak valid', + 'Date format invalid' => 'Format tanggal tidak valid', + 'Time format invalid' => 'Format waktu tidak valid', + 'Invalid Mail transport' => 'Transportasi surel tidak valid', + 'Color invalid' => 'Warna tidak valid', + 'This value must be greater or equal to %d' => 'Nilai ini harus lebih besar dari atau sama dengan %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Tambahkan BOM di awal berkas (diperlukan untuk Microsoft Excel)', + 'Just add these tag(s)' => 'Cukup tambahkan tag ini', + 'Remove internal link(s)' => 'Hapus tautan internal', + 'Import tasks from another project' => 'Impor tugas dari proyek lain', + 'Select the project to copy tasks from' => 'Pilih proyek untuk menyalin tugas darinya', + 'The total maximum allowed attachments size is %sB.' => 'Ukuran total maksimum lampiran yang diizinkan adalah %sB.', + 'Add attachments' => 'Tambahkan lampiran', + 'Task #%d "%s" is overdue' => 'Tugas #%d "%s" sudah kadaluarsa', + 'Enable notifications by default for all new users' => 'Aktifkan notifikasi secara default untuk semua pengguna baru', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Tetapkan tugas kepada pembuatnya untuk kolom tertentu jika tidak ada penanggung jawab yang diatur secara manual', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Tetapkan tugas kepada pengguna yang masuk saat kolom berubah ke kolom yang ditentukan jika tidak ada pengguna yang ditetapkan', +]; diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php new file mode 100644 index 0000000..7fbd277 --- /dev/null +++ b/app/Locale/it_IT/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => '.', + 'None' => 'Nessuno', + 'Edit' => 'Modifica', + 'Remove' => 'Cancella', + 'Yes' => 'Si', + 'No' => 'No', + 'cancel' => 'annulla', + 'or' => 'o', + 'Yellow' => 'Giallo', + 'Blue' => 'Blu', + 'Green' => 'Verde', + 'Purple' => 'Viola', + 'Red' => 'Rosso', + 'Orange' => 'Arancione', + 'Grey' => 'Grigio', + 'Brown' => 'Marrone', + 'Deep Orange' => 'Arancio scuro', + 'Dark Grey' => 'Grigio scuro', + 'Pink' => 'Rosa', + 'Teal' => 'Verde foglia di tè', + 'Cyan' => 'Ciano', + 'Lime' => 'Verde lime', + 'Light Green' => 'Verde chiaro', + 'Amber' => 'Ambra', + 'Save' => 'Salva', + 'Login' => 'Accedi', + 'Official website:' => 'Sito web ufficiale:', + 'Unassigned' => 'Non assegnato', + 'View this task' => 'Visualizza questo compito', + 'Remove user' => 'Cancella un utente', + 'Do you really want to remove this user: "%s"?' => 'Veramente vuoi cancellare questo utente: "%s" ?', + 'All users' => 'Tutti gli utenti', + 'Username' => 'Nome utente', + 'Password' => 'Password', + 'Administrator' => 'Amministratore', + 'Sign in' => 'Accedi', + 'Users' => 'Utenti', + 'Forbidden' => 'Vietato', + 'Access Forbidden' => 'Accesso vietato', + 'Edit user' => 'Modifica un utente', + 'Logout' => 'Esci', + 'Bad username or password' => 'Utente o password errati', + 'Edit project' => 'Modifica progetto', + 'Name' => 'Nome', + 'Projects' => 'Progetti', + 'No project' => 'Nessun progetto', + 'Project' => 'Progetto', + 'Status' => 'Stato', + 'Tasks' => 'Task', + 'Board' => 'Bacheca', + 'Actions' => 'Azioni', + 'Inactive' => 'Inattivo', + 'Active' => 'Attivo', + 'Unable to update this board.' => 'Impossibile aggiornare questa bacheca.', + 'Disable' => 'Disattiva', + 'Enable' => 'Attiva', + 'New project' => 'Nuovo progetto', + 'Do you really want to remove this project: "%s"?' => 'Vuoi davvero eliminare il seguente progetto: "%s" ?', + 'Remove project' => 'Cancella il progetto', + 'Edit the board for "%s"' => 'Modifica la bacheca per "%s"', + 'Add a new column' => 'Aggiungi una nuova colonna', + 'Title' => 'Titolo', + 'Assigned to %s' => 'Assegnato a %s', + 'Remove a column' => 'Cancella questa colonna', + 'Unable to remove this column.' => 'Impossibile cancellare questa colonna.', + 'Do you really want to remove this column: "%s"?' => 'Desideri davvero cancellare questa colonna: "%s" ?', + 'Settings' => 'Impostazioni', + 'Application settings' => 'Impostazioni dell\'applicazione', + 'Language' => 'Lingua', + 'Webhook token:' => 'Identificatore (token) per i webhooks :', + 'API token:' => 'Token dell\'API:', + 'Database size:' => 'Dimensioni della base dati:', + 'Download the database' => 'Scaricare la base dati', + 'Optimize the database' => 'Ottimizare la base dati', + '(VACUUM command)' => '(Comando VACUUM)', + '(Gzip compressed Sqlite file)' => '(File Sqlite compresso in Gzip)', + 'Close a task' => 'Chiudi un compito', + 'Column' => 'Colonna', + 'Color' => 'Colore', + 'Assignee' => 'Assegnatario', + 'Create another task' => 'Crea un nuovo compito', + 'New task' => 'Nuovo compito', + 'Open a task' => 'Apri un compito', + 'Do you really want to open this task: "%s"?' => 'Desideri davvero aprire questo compito: "%s" ?', + 'Back to the board' => 'Torna alla bacheca', + 'There is nobody assigned' => 'Nessuno è assegnato a questo compito', + 'Column on the board:' => 'Colonna sulla bacheca: ', + 'Close this task' => 'Chiudi questo compito', + 'Open this task' => 'Apri questo compito', + 'There is no description.' => 'Nessuna descrizione presente.', + 'Add a new task' => 'Aggiungere un nuovo compito', + 'The username is required' => 'Si richiede un nome di utente', + 'The maximum length is %d characters' => 'La lunghezza massima è di %d caratteri', + 'The minimum length is %d characters' => 'La lunghezza minima è di %d caratteri', + 'The password is required' => 'Si richiede una password', + 'This value must be an integer' => 'questo valore deve essere un intero', + 'The username must be unique' => 'Il nome di utente deve essere unico', + 'The user id is required' => 'Si richiede l\'identificatore dell\'utente', + 'Passwords don\'t match' => 'Le password non corrispondono', + 'The confirmation is required' => 'Si richiede una conferma', + 'The project is required' => 'Si richiede il progetto', + 'The id is required' => 'Si richiede l\'identificatore', + 'The project id is required' => 'Si richiede l\'identificatore del progetto', + 'The project name is required' => 'Si richiede il nome del progetto', + 'The title is required' => 'Si richiede un titolo', + 'Settings saved successfully.' => 'Impostazioni salvate con successo.', + 'Unable to save your settings.' => 'Impossibile salvare le impostazioni.', + 'Database optimization done.' => 'Ottimizzazione della base dati conclusa.', + 'Your project has been created successfully.' => 'Il tuo progetto è stato creato con successo.', + 'Unable to create your project.' => 'Impossibile creare il progetto.', + 'Project updated successfully.' => 'Progetto aggiornato con successo.', + 'Unable to update this project.' => 'Impossibile aggiornare il progetto.', + 'Unable to remove this project.' => 'Impossibile cancellare questo progetto.', + 'Project removed successfully.' => 'Progetto cancellato con successo.', + 'Project activated successfully.' => 'Progetto attivato con successo.', + 'Unable to activate this project.' => 'Impossibile attivare il progetto.', + 'Project disabled successfully.' => 'Progetto disattivato con successo.', + 'Unable to disable this project.' => 'Impossibile disattivare il progetto.', + 'Unable to open this task.' => 'Impossibile aprire questo compito.', + 'Task opened successfully.' => 'Il compito è stato aperto con successo.', + 'Unable to close this task.' => 'Impossibile chiudere questo compito.', + 'Task closed successfully.' => 'Task chiuso con successo.', + 'Unable to update your task.' => 'Impossibile modificare questo compito.', + 'Task updated successfully.' => 'Task modificato con successo.', + 'Unable to create your task.' => 'Impossibile creare questo compito.', + 'Task created successfully.' => 'Task creato con successo.', + 'User created successfully.' => 'Utente creato con successo.', + 'Unable to create your user.' => 'Impossibile creare l\'utente.', + 'User updated successfully.' => 'Utente aggiornato con successo.', + 'User removed successfully.' => 'Utente cancellato con successo.', + 'Unable to remove this user.' => 'Impossibile cancellare questo utente.', + 'Board updated successfully.' => 'Bacheca aggiornata con successo.', + 'Ready' => 'Pronto', + 'Backlog' => 'In attesa', + 'Work in progress' => 'In corso', + 'Done' => 'Fatto', + 'Application version:' => 'Versione dell\'applicazione:', + 'Id' => 'ID', + 'Public link' => 'Link pubblico', + 'Timezone' => 'Fuso orario', + 'Sorry, I didn\'t find this information in my database!' => 'Spiacente, non ho trovato questa informazione sul database!', + 'Page not found' => 'Pagina non trovata', + 'Complexity' => 'Complessità', + 'Task limit' => 'Limite di compito', + 'Task count' => 'Numero di compito', + 'User' => 'Utente', + 'Comments' => 'Commenti', + 'Comment is required' => 'Si richiede un commento', + 'Comment added successfully.' => 'Commenti aggiunti con successo.', + 'Unable to create your comment.' => 'Impossibile creare questo commento.', + 'Due Date' => 'Data di scadenza', + 'Invalid date' => 'Data non valida', + 'Automatic actions' => 'Azioni automatiche', + 'Your automatic action has been created successfully.' => 'l\'azione automatica è stata creata con successo.', + 'Unable to create your automatic action.' => 'Impossibile creare quest\'azione automatica.', + 'Remove an action' => 'Cancellare un\'azione', + 'Unable to remove this action.' => 'Impossibile cancellare questa azione.', + 'Action removed successfully.' => 'Azione cancellata con successo.', + 'Automatic actions for the project "%s"' => 'Azioni automatiche per il progetto "%s"', + 'Add an action' => 'Aggiungi un\'azione', + 'Event name' => 'Nome dell\'evento', + 'Action' => 'Azione', + 'Event' => 'Evento', + 'When the selected event occurs execute the corresponding action.' => 'Quando si verifica l\'evento selezionato, eseguire l\'azione corrispondente.', + 'Next step' => 'Passo successivo', + 'Define action parameters' => 'Definire i parametri dell\'azione', + 'Do you really want to remove this action: "%s"?' => 'Vuoi davvero cancellare la seguente azione: "%s"?', + 'Remove an automatic action' => 'Cancella un\'azione automatica', + 'Assign the task to a specific user' => 'Assegna il compito ad un utente specifico', + 'Assign the task to the person who does the action' => 'Assegna il compito all\'utente che compie l\'azione', + 'Duplicate the task to another project' => 'Duplica il compito in altro progetto', + 'Move a task to another column' => 'Sposta un compito in un\'altra colonna', + 'Task modification' => 'Modifica di un compito', + 'Task creation' => 'Creazione di un compito', + 'Closing a task' => 'Chiusura di un compito', + 'Assign a color to a specific user' => 'Assegna un colore ad un utente specifico', + 'Position' => 'Posizione', + 'Duplicate to project' => 'Duplica in un altro progetto', + 'Duplicate' => 'Duplica', + 'Link' => 'Relazione', + 'Comment updated successfully.' => 'Commento aggiornato con successo.', + 'Unable to update your comment.' => 'Impossibile aggiornare questo commento.', + 'Remove a comment' => 'Cancella un commento', + 'Comment removed successfully.' => 'Commento cancellato con successo.', + 'Unable to remove this comment.' => 'Impossibile cancellare questo commento.', + 'Do you really want to remove this comment?' => 'Vuoi davvero cancellare questo commento?', + 'Current password for the user "%s"' => 'Password attuale per l\'utente "%s"', + 'The current password is required' => 'Si richiede la password attuale', + 'Wrong password' => 'Password errata', + 'Unknown' => 'Sconociuto', + 'Last logins' => 'Ultimi accessi', + 'Login date' => 'Data di accesso', + 'Authentication method' => 'Metodo di autenticazione', + 'IP address' => 'Indirizzo IP', + 'User agent' => 'User agent', + 'Persistent connections' => 'Connessioni persistenti', + 'No session.' => 'Non esiste sessione.', + 'Expiration date' => 'Data di scadenza', + 'Remember Me' => 'Ricordami', + 'Creation date' => 'Data di creazione', + 'Everybody' => 'Tutti', + 'Open' => 'Aperto', + 'Closed' => 'Chiuso', + 'Search' => 'Cerca', + 'Nothing found.' => 'Non si è trovato nulla.', + 'Due date' => 'Data di scadenza', + 'Description' => 'Descrizione', + '%d comments' => '%d commenti', + '%d comment' => '%d commento', + 'Email address invalid' => 'Indirizzo Email non valido', + 'Your external account is not linked anymore to your profile.' => 'Il tuo account esterno non è più collegato al tuo profilo.', + 'Unable to unlink your external account.' => 'Impossibile scollegare il tuo account esterno.', + 'External authentication failed' => 'Autenticazione esterna fallita', + 'Your external account is linked to your profile successfully.' => 'Il tuo account esterno è stato collegato al tuo profilo con successo.', + 'Email' => 'Email', + 'Task removed successfully.' => 'Task cancellato con successo.', + 'Unable to remove this task.' => 'Impossibile cancellare questo compito.', + 'Remove a task' => 'Cancella un compito', + 'Do you really want to remove this task: "%s"?' => 'Vuoi davvero cancellare questo compito: "%s"?', + 'Assign automatically a color based on a category' => 'Assegna un colore in modo automatico basandosi sulla categoria', + 'Assign automatically a category based on a color' => 'Assegna una categoria in modo automatico basandosi sul colore', + 'Task creation or modification' => 'Creazione o modifica di compito', + 'Category' => 'Categoria', + 'Category:' => 'Categoria:', + 'Categories' => 'Categorie', + 'Your category has been created successfully.' => 'La tua categoria è stata creata con successo.', + 'This category has been updated successfully.' => 'La tua categoria è stata aggiornata con successo.', + 'Unable to update this category.' => 'Impossibile aggiornare la tua categoria.', + 'Remove a category' => 'Cancella una categoria', + 'Category removed successfully.' => 'Categoria cancellata con successo.', + 'Unable to remove this category.' => 'Impossibile cancellare questa categoria.', + 'Category modification for the project "%s"' => 'Modifica della categoria per il progetto "%s"', + 'Category Name' => 'Nome della categoria', + 'Add a new category' => 'Aggiungere una nuova categoria', + 'Do you really want to remove this category: "%s"?' => 'Vuoi davvero cancellare la seguente categoria: "%s"?', + 'All categories' => 'Tutte le categorie', + 'No category' => 'Senza categoria', + 'The name is required' => 'Si richiede un nome', + 'Remove a file' => 'Cancella un file', + 'Unable to remove this file.' => 'Impossibile cancellare questo file.', + 'File removed successfully.' => 'File cancellato con successo.', + 'Attach a document' => 'Allega un documento', + 'Do you really want to remove this file: "%s"?' => 'Vuoi davvero cancellare questo file: "%s"?', + 'Attachments' => 'Allegati', + 'Edit the task' => 'Modifica il compito', + 'Add a comment' => 'Aggiungi un commento', + 'Edit a comment' => 'Modifica un commento', + 'Summary' => 'Sommario', + 'Time tracking' => 'Monitoraggio delle tempistiche', + 'Estimate:' => 'Stimato:', + 'Spent:' => 'Trascorso:', + 'Do you really want to remove this sub-task?' => 'Vuoi davvero cancellare questo sotto-compito?', + 'Remaining:' => 'Rimangono', + 'hours' => 'ore', + 'estimated' => 'stimate', + 'Sub-Tasks' => 'Sotto-compito', + 'Add a sub-task' => 'Aggiungi un sotto-compito', + 'Original estimate' => 'Stima originale', + 'Create another sub-task' => 'Crea un altro sotto-compito', + 'Time spent' => 'Tempo trascorso', + 'Edit a sub-task' => 'Modifica un sotto-compito', + 'Remove a sub-task' => 'Cancella un sotto-compito', + 'The time must be a numeric value' => 'Il tempo deve essere un valore numerico', + 'Todo' => 'Da fare', + 'In progress' => 'In corso', + 'Sub-task removed successfully.' => 'Sotto-compito cancellato con successo.', + 'Unable to remove this sub-task.' => 'Impossibile cancellare questo sotto-compito.', + 'Sub-task updated successfully.' => 'Sotto-compito aggiornato con successo.', + 'Unable to update your sub-task.' => 'Impossibile aggiornare il tuo sotto-compito.', + 'Unable to create your sub-task.' => 'Impossibile creare il tuo sotto-compito.', + 'Maximum size: ' => 'Dimensioni massime: ', + 'Display another project' => 'Mostra un altro progetto', + 'Created by %s' => 'Creato da %s', + 'Tasks Export' => 'Export dei compito', + 'Start Date' => 'Data di inizio', + 'Execute' => 'Esegui', + 'Task Id' => 'Id del compito', + 'Creator' => 'Creatore', + 'Modification date' => 'Data di modifica', + 'Completion date' => 'Data di termine', + 'Clone' => 'Clona', + 'Project cloned successfully.' => 'Progetto clonato con successo.', + 'Unable to clone this project.' => 'Impossibile clonare questo progetto', + 'Enable email notifications' => 'Abilita le notifiche via email', + 'Task position:' => 'Posizione del compito:', + 'The task #%d has been opened.' => 'Il compito #%d è stato aperto.', + 'The task #%d has been closed.' => 'Il compito #%d è stato chiuso.', + 'Sub-task updated' => 'Sotto-compito aggiornato', + 'Title:' => 'Titolo', + 'Status:' => 'Stato', + 'Assignee:' => 'Assegnatario:', + 'Time tracking:' => 'Monitoraggio delle tempistiche:', + 'New sub-task' => 'Nuovo sotto-compito', + 'New attachment added "%s"' => 'Nuovo allegato aggiunto "%s"', + 'New comment posted by %s' => 'Nuovo commento aggiunto da "%s"', + 'New comment' => 'Nuovo commento', + 'Comment updated' => 'Commento aggiornato', + 'New subtask' => 'Nuovo sotto-compito', + 'I only want to receive notifications for these projects:' => 'Vorrei ricevere le notifiche solo da questi progetti:', + 'view the task on Kanboard' => 'visualizza il compito su Kanboard', + 'Public access' => 'Accesso pubblico', + 'Disable public access' => 'Disabilita l\'accesso pubblico', + 'Enable public access' => 'Abilita l\'accesso pubblico', + 'Public access disabled' => 'Accesso pubblico disattivato', + 'Move the task to another project' => 'Sposta il compito in un altro progetto', + 'Move to project' => 'Sposta in un altro progetto', + 'Do you really want to duplicate this task?' => 'Vuoi davvero duplicare questo compito?', + 'Duplicate a task' => 'Duplica il compito', + 'External accounts' => 'Account esterni', + 'Account type' => 'Tipo di account', + 'Local' => 'Locale', + 'Remote' => 'Remoto', + 'Enabled' => 'Abilitato', + 'Disabled' => 'Disabilitato', + 'Login:' => 'Nome utente:', + 'Full Name:' => 'Nome:', + 'Email:' => 'Email:', + 'Notifications:' => 'Notifiche:', + 'Notifications' => 'Notifiche', + 'Account type:' => 'Tipo di account', + 'Edit profile' => 'Modifica profilo', + 'Change password' => 'Cambia password', + 'Password modification' => 'Modifica della password', + 'External authentications' => 'Autenticazione esterna', + 'Never connected.' => 'Mai connesso.', + 'No external authentication enabled.' => 'Nessuna autenticazione esterna abilitata.', + 'Password modified successfully.' => 'Password modificata con successo.', + 'Unable to change the password.' => 'Impossibile cambiare la password.', + 'Change category' => 'Cambia categoria', + '%s updated the task %s' => '%s ha aggiornato il compito %s', + '%s opened the task %s' => '%s ha aperto il compito %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s ha spostato il compito %s nella posizione #%d della colonna "%s"', + '%s moved the task %s to the column "%s"' => '%s ha spostato il compito %s nella colonna "%s"', + '%s created the task %s' => '%s ha creato il compito %s', + '%s closed the task %s' => '%s ha chiuso il compito %s', + '%s created a subtask for the task %s' => '%s ha creato un sotto-compito per il compito %s', + '%s updated a subtask for the task %s' => '%s ha aggiornato un sotto-compito per il compito %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Assegnato a %s con una stima di %s/%sh', + 'Not assigned, estimate of %sh' => 'Non assegnato, stima %sh', + '%s updated a comment on the task %s' => '%s ha aggiornato un commento nel compito %s', + '%s commented the task %s' => '%s ha commentato il compito %s', + '%s\'s activity' => 'Attività di %s', + 'RSS feed' => 'Feed RSS', + '%s updated a comment on the task #%d' => '%s ha aggiornato un commento del compito #%d', + '%s commented on the task #%d' => '%s ha commentato il compito #%d', + '%s updated a subtask for the task #%d' => '%s ha aggiornato un sotto-compito del compito #%d', + '%s created a subtask for the task #%d' => '%s ha creato un sotto-compito del compito #%d', + '%s updated the task #%d' => '%s ha aggiornato il compito #%d', + '%s created the task #%d' => '%s ha creato il compito #%d', + '%s closed the task #%d' => '%s ha chiuso il compito #%d', + '%s opened the task #%d' => '%s ha aperto il compito #%d', + 'Activity' => 'Attività', + 'Default values are "%s"' => 'Valori di default "%s"', + 'Default columns for new projects (Comma-separated)' => 'Colonne di default per i nuovi progetti (Separati da virgola)', + 'Task assignee change' => 'Cambia l\'assegnatario del compito', + '%s changed the assignee of the task #%d to %s' => '%s dai l\'assegnazione del compito #%d a %s', + '%s changed the assignee of the task %s to %s' => '%s ha cambiato l\'assegnatario del compito %s a %s', + 'New password for the user "%s"' => 'Nuova password per l\'utente "%s"', + 'Choose an event' => 'Scegli un evento', + 'Create a task from an external provider' => 'Crea un compito da un provider esterno', + 'Change the assignee based on an external username' => 'Cambia l\'assegnatario basandosi su un username esterno', + 'Change the category based on an external label' => 'Cambia la categoria basandosi su un\'etichetta esterna', + 'Reference' => 'Riferimento', + 'Label' => 'Etichetta', + 'Database' => 'Database', + 'About' => 'Informazioni', + 'Database driver:' => 'Driver per Database', + 'Board settings' => 'Impostazioni bacheca', + 'Webhook settings' => 'Impostazione Webhook', + 'Reset token' => 'Rigenera il token', + 'API endpoint:' => 'Endpoint dell\'API:', + 'Refresh interval for personal board' => 'Intervallo di refresh per le bacheche private', + 'Refresh interval for public board' => 'Intervallo di refresh per le bacheche pubbliche', + 'Task highlight period' => 'Periodo di evidenza per il compito', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Periodo (in secondi) per considerare un compito come modificato recentemente (0 per disabilitare, 2 giorni di default)', + 'Frequency in second (60 seconds by default)' => 'Frequenza in secondi (60 secondi di default)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frequenza in secondi (0 secondi di default)', + 'Application URL' => 'URL dell\'applicazione', + 'Token regenerated.' => 'Token rigenerato.', + 'Date format' => 'Formato data', + 'ISO format is always accepted, example: "%s" and "%s"' => 'Il formato ISO è sempre accettato, esempio: "%s" e "%s"', + 'New personal project' => 'Nuovo progetto privato', + 'This project is personal' => 'Questo progetto è privato', + 'Add' => 'Aggiungi', + 'Start date' => 'Data di inizio', + 'Time estimated' => 'Tempo stimato', + 'There is nothing assigned to you.' => 'Non c\'è nulla assegnato a te.', + 'My tasks' => 'I miei compiti', + 'Activity stream' => 'Flusso attività', + 'Dashboard' => 'Bacheca', + 'Confirmation' => 'Conferma', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Create a comment from an external provider' => 'Crea un commit da un provider esterno', + 'Project management' => 'Gestione progetti', + 'Columns' => 'Colonne', + 'Task' => 'Task', + 'Percentage' => 'Percentuale', + 'Number of tasks' => 'Numero di compiti', + 'Task distribution' => 'Distribuzione dei compiti', + 'Analytics' => 'Analitiche', + 'Subtask' => 'Sotto-compito', + 'User repartition' => 'Ripartizione per utente', + 'Clone this project' => 'Clona questo progetto', + 'Column removed successfully.' => 'Colonna rimossa con successo', + 'Not enough data to show the graph.' => 'Non ci sono abbastanza dati per visualizzare il grafico.', + 'Previous' => 'Precedente', + 'The id must be an integer' => 'L\'id deve essere un intero', + 'The project id must be an integer' => 'L\'id del progetto deve essere un intero', + 'The status must be an integer' => 'Lo status deve essere un intero', + 'The subtask id is required' => 'L\'id del sotto-compito è necessario', + 'The subtask id must be an integer' => 'L\'id del sotto-compito deve essere un intero', + 'The task id is required' => 'Richiesto l\'id del compito', + 'The task id must be an integer' => 'L\'id del compito deve essere un intero', + 'The user id must be an integer' => 'L\'id dell\'utente deve essere un intero', + 'This value is required' => 'Questo valore è necessario', + 'This value must be numeric' => 'Questo valore deve essere numerico', + 'Unable to create this task.' => 'Impossibile creare questo compito', + 'Cumulative flow diagram' => 'Diagramma di flusso cumulativo', + 'Daily project summary' => 'Sommario giornaliero del progetto', + 'Daily project summary export' => 'Export del sommario giornaliero del progetto', + 'Exports' => 'Esporta', + 'This export contains the number of tasks per column grouped per day.' => 'Questo export contiene il numero di compiti per colonna raggruppati per giorno', + 'Active swimlanes' => 'Corsie attive', + 'Add a new swimlane' => 'Aggiungi una corsia', + 'Default swimlane' => 'Corsia predefinita', + 'Do you really want to remove this swimlane: "%s"?' => 'Vuoi davvero rimuovere la seguente corsia: "%s"?', + 'Inactive swimlanes' => 'Corsie inattive', + 'Remove a swimlane' => 'Rimuovi una corsia', + 'Swimlane modification for the project "%s"' => 'Modifica corsia per il progetto "%s"', + 'Swimlane removed successfully.' => 'Corsia rimossa con successo.', + 'Swimlanes' => 'Corsie', + 'Swimlane updated successfully.' => 'Corsia aggiornata con successo.', + 'Unable to remove this swimlane.' => 'Impossibile rimuovere questa corsia.', + 'Unable to update this swimlane.' => 'Impossibile aggiornare questa corsia.', + 'Your swimlane has been created successfully.' => 'La tua corsia è stata creata con successo', + 'Example: "Bug, Feature Request, Improvement"' => 'Esempi: "Bug, Richiesta di Funzioni, Migliorie"', + 'Default categories for new projects (Comma-separated)' => 'Categorie di default per i progetti (Separati da virgola)', + 'Integrations' => 'Integrazioni', + 'Integration with third-party services' => 'Integrazione con servizi di terze parti', + 'Subtask Id' => 'Id del sotto-compito', + 'Subtasks' => 'Sotto-compiti', + 'Subtasks Export' => 'Esporta i sotto-compiti', + 'Task Title' => 'Titolo del compito', + 'Untitled' => 'Senza titolo', + 'Application default' => 'Default dell\'applicazione', + 'Language:' => 'Lingua', + 'Timezone:' => 'Fuso Orario', + 'All columns' => 'Tutte le colonne', + 'Next' => 'Prossimo', + '#%d' => '#%d', + 'All swimlanes' => 'Tutte le corsie', + 'All colors' => 'Tutti i colori', + 'Moved to column %s' => 'Spostato sulla colonna "%s"', + 'User dashboard' => 'Bacheca utente', + 'Allow only one subtask in progress at the same time for a user' => 'Permetti un solo sotto-compito in corso per utente alla volta', + 'Edit column "%s"' => 'Modifica la colonna "%s"', + 'Select the new status of the subtask: "%s"' => 'Seleziona il nuovo status per il sotto-compito: "%s"', + 'Subtask timesheet' => 'Timesheet del sotto-compito', + 'There is nothing to show.' => 'Nulla da mostrare.', + 'Time Tracking' => 'Monitoraggio delle tempistiche', + 'You already have one subtask in progress' => 'Hai già un sotto-compito in corso', + 'Which parts of the project do you want to duplicate?' => 'Quali parti del progetto vuoi duplicare?', + 'Disallow login form' => 'Disabilita il form di login', + 'Start' => 'Inizio', + 'End' => 'Fine', + 'Task age in days' => 'Anzianità del compito in giorni', + 'Days in this column' => 'Giorni in questa colonna', + '%dd' => '%dg', + 'Add a new link' => 'Aggiungi una nuova relazione', + 'Do you really want to remove this link: "%s"?' => 'Vuoi davvero rimuovere la seguente relazione: "%s"?', + 'Do you really want to remove this link with task #%d?' => 'Vuoi davvero rimuovere questa relazione dal compito #%d?', + 'Field required' => 'Campo necessario', + 'Link added successfully.' => 'Relazione aggiunta con successo.', + 'Link updated successfully.' => 'Relazione aggiornata con successo.', + 'Link removed successfully.' => 'Relazione rimossa con successo.', + 'Link labels' => 'Etichette delle relazioni', + 'Link modification' => 'Modifica relazione', + 'Opposite label' => 'Etichetta contraria', + 'Remove a link' => 'Rimuovi una relazione', + 'The labels must be different' => 'Le etichette devono essere diverse', + 'There is no link.' => 'Nessuna relazione presente.', + 'This label must be unique' => 'Questa etichetta deve essere univoca', + 'Unable to create your link.' => 'Impossibile creare la relazione.', + 'Unable to update your link.' => 'Impossibile aggiornare la relazione.', + 'Unable to remove this link.' => 'Impossibile rimuovere la relazione.', + 'relates to' => 'si riferisce a', + 'blocks' => 'blocca', + 'is blocked by' => 'è bloccato da', + 'duplicates' => 'duplica', + 'is duplicated by' => 'è duplicato da', + 'is a child of' => 'è un figlio di', + 'is a parent of' => 'è un genitore di', + 'targets milestone' => 'punta alla milestone', + 'is a milestone of' => 'è una milestone di', + 'fixes' => 'sistema', + 'is fixed by' => 'è sistemato da', + 'This task' => 'Questo compito', + '<1h' => '<1h', + '%dh' => '%dh', + 'Expand tasks' => 'Espandi i compiti', + 'Collapse tasks' => 'Minimizza i compiti', + 'Expand/collapse tasks' => 'Espandi/minimizza i compiti', + 'Close dialog box' => 'Chiudi la finestra di dialogo', + 'Submit a form' => 'Invia i dati', + 'Board view' => 'Vista bacheca', + 'Keyboard shortcuts' => 'Scorciatoie da tastiera', + 'Open board switcher' => 'Apri il selettore di bacheche', + 'Application' => 'Applicazione', + 'Compact view' => 'Vista compatta', + 'Horizontal scrolling' => 'Scrolling orizzontale', + 'Compact/wide view' => 'Vista compatta/estesa', + 'Currency' => 'Valuta', + 'Personal project' => 'Progetto privato', + 'AUD - Australian Dollar' => 'AUD - Dollari Australiani', + 'CAD - Canadian Dollar' => 'CAD - Dollari Canadesi', + 'CHF - Swiss Francs' => 'CHF - Franchi Svizzeri', + 'Custom Stylesheet' => 'Foglio di stile personalizzato', + 'EUR - Euro' => 'EUR - Euro', + 'GBP - British Pound' => 'GBP - Pound Inglesi', + 'INR - Indian Rupee' => 'INR - Rupie Indiani', + 'JPY - Japanese Yen' => 'JPY - Yen Giapponesi', + 'NZD - New Zealand Dollar' => 'NZD - Dollari della Nuova Zelanda', + 'PEN - Peruvian Sol' => 'PEN - Sol peruviano', + 'RSD - Serbian dinar' => 'RSD - Dinar serbo', + 'CNY - Chinese Yuan' => 'CNY - Yuan cinese', + 'USD - US Dollar' => 'USD - Dollari Americani', + 'VES - Venezuelan Bolívar' => 'VES - Bolívar venezuelano', + 'Destination column' => 'Colonna destinazione', + 'Move the task to another column when assigned to a user' => 'Sposta il compito in un\'altra colonna quando viene assegnato ad un utente', + 'Move the task to another column when assignee is cleared' => 'Sposta il compito in un\'altra colonna quando l\'assegnatario viene cancellato', + 'Source column' => 'Colonna sorgente', + 'Transitions' => 'Transizioni', + 'Executer' => 'Esecutore', + 'Time spent in the column' => 'Tempo trascorso nella colonna', + 'Task transitions' => 'Transizioni dei compiti', + 'Task transitions export' => 'Export delle transizioni dei compiti', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Questo report contiene tutti gli spotamenti di colonna per ogni compito con le date, l\'utente ed il tempo trascorso per ogni transizione', + 'Currency rates' => 'Tassi di cambio', + 'Rate' => 'Cambio', + 'Change reference currency' => 'Cambia la valuta di riferimento', + 'Reference currency' => 'Valuta di riferimento', + 'The currency rate has been added successfully.' => 'Il tasso di cambio è stato aggiunto con successo.', + 'Unable to add this currency rate.' => 'Impossibile aggiungere questo tasso di cambio.', + 'Webhook URL' => 'URL Webhook', + '%s removed the assignee of the task %s' => '%s rimuove l\'assegnatario del compito %s', + 'Information' => 'Informazioni', + 'Check two factor authentication code' => 'Controlla il codice di autenticazione "two-factor"', + 'The two factor authentication code is not valid.' => 'Il codice di autenticazione "two-factor" non è valido', + 'The two factor authentication code is valid.' => 'Il codice di autenticazione "two-factor" è valido', + 'Code' => 'Codice', + 'Two factor authentication' => 'Autenticazione "two-factor"', + 'This QR code contains the key URI: ' => 'Questo QR code contiene l\'URI: ', + 'Check my code' => 'Controlla il mio codice', + 'Secret key: ' => 'Chiave privata:', + 'Test your device' => 'Testa il tuo dispositivo', + 'Assign a color when the task is moved to a specific column' => 'Assegna un colore quando il compito viene spostato in una colonna specifica', + '%s via Kanboard' => '%s tramite Kanboard', + 'Burndown chart' => 'Grafico Burndown', + 'This chart show the task complexity over the time (Work Remaining).' => 'Questo grafico mostra la complessità dei compiti nel tempo (Lavoro residuo).', + 'Screenshot taken %s' => 'Schermata catturata %s', + 'Add a screenshot' => 'Aggiungi una schermata', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Cattura una schermata e premi CTRL+V o ⌘+V per incollarla qui.', + 'Screenshot uploaded successfully.' => 'Schermata caricata con successo.', + 'SEK - Swedish Krona' => 'SEK - Corona svedese', + 'Identifier' => 'Identificatore', + 'Disable two factor authentication' => 'Disabilita l\'autenticazione "two-factor"', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Vuoi davvero disabilitare l\'autenticazione "two-factor" per questo utente: "%s"?', + 'Edit link' => 'Modifica relazione', + 'Start to type task title...' => 'Inzia a digitare il titolo di un compito...', + 'A task cannot be linked to itself' => 'Un compito non può essere correlato a se stesso', + 'The exact same link already exists' => 'La stessa relazione risulta già esistente', + 'Recurrent task is scheduled to be generated' => 'Il compito ricorrente è pianificato per essere generato', + 'Score' => 'Punteggio', + 'The identifier must be unique' => 'L\'identificatore deve essere univoco', + 'This linked task id doesn\'t exists' => 'L\'id del compito correlato non esiste', + 'This value must be alphanumeric' => 'Questo valore deve essere alfanumerico', + 'Edit recurrence' => 'Modifica ricorrenza', + 'Generate recurrent task' => 'Genera compito ricorrente', + 'Trigger to generate recurrent task' => 'Trigger per generare il compito ricorrente', + 'Factor to calculate new due date' => 'Fattore numerico per calcolare la nuova data di scadenza', + 'Timeframe to calculate new due date' => 'Lasso temporale per calcolare la nuova data di scadenza', + 'Base date to calculate new due date' => 'Data di partenza per calcolare la nuova data di scadenza', + 'Action date' => 'Data di azione (action date)', + 'Base date to calculate new due date: ' => 'Data di base per calcolare la nuova data di scadenza: ', + 'This task has created this child task: ' => 'Questo compito ha creato il seguente compito figlio: ', + 'Day(s)' => 'Giorno/i', + 'Existing due date' => 'Data di scadenza esistente', + 'Factor to calculate new due date: ' => 'Fattore numerico per calcolare la nuova data di scadenza: ', + 'Month(s)' => 'Mese/i', + 'This task has been created by: ' => 'Questo compito è stato creato da: ', + 'Recurrent task has been generated:' => 'Il compito ricorrente è stato generato:', + 'Timeframe to calculate new due date: ' => 'Lasso temporale per calcolare la nuova data di scadenza: ', + 'Trigger to generate recurrent task: ' => 'Trigger per generare il compito ricorrente: ', + 'When task is closed' => 'Quando un compito è chiuso', + 'When task is moved from first column' => 'Quando un compito è spostato dalla prima colonna', + 'When task is moved to last column' => 'Quando un compito è spostato nell\'ultima colonna', + 'Year(s)' => 'Anno/i', + 'Project settings' => 'Impostazioni di progetto', + 'Automatically update the start date' => 'Aggiorna automaticamente la data di inizio', + 'iCal feed' => 'feed iCal', + 'Preferences' => 'Preferenze', + 'Security' => 'Sicurezza', + 'Two factor authentication disabled' => 'Two factor authentication disabilitata', + 'Two factor authentication enabled' => 'Two factor authentication abilitata', + 'Unable to update this user.' => 'Impossibile aggiornare questo utente.', + 'There is no user management for personal projects.' => 'Non è prevista la gestione di utenti per i progetti privati.', + 'User that will receive the email' => 'Utente che riceverà l\'email', + 'Email subject' => 'Soggetto dell\'email', + 'Date' => 'Data', + 'Add a comment log when moving the task between columns' => 'Aggiungi un log quando si sposta un compito tra colonne', + 'Move the task to another column when the category is changed' => 'Sposta il compito in un\'altra colonna quando la categoria viene modificata', + 'Send a task by email to someone' => 'Invia un compito via email a qualcuno', + 'Reopen a task' => 'Riapri un compito', + 'Notification' => 'Notifica', + '%s moved the task #%d to the first swimlane' => '%s ha spostato il compito #%d nella prima corsia', + 'Swimlane' => 'Corsia', + '%s moved the task %s to the first swimlane' => '%s ha spostato il compito %s nella prima corsia', + '%s moved the task %s to the swimlane "%s"' => '%s ha spostato il compito %s nella corsia %s', + 'This report contains all subtasks information for the given date range.' => 'Questo report contiente tutte le informazioni sui sotto-compiti nell\'arco temporale indicato.', + 'This report contains all tasks information for the given date range.' => 'Questo report contiente tutte le informazioni sui compiti nell\'arco temporale indicato.', + 'Project activities for %s' => 'Attività di progetto per %s', + 'view the board on Kanboard' => 'guarda la bacheca su Kanboard', + 'The task has been moved to the first swimlane' => 'Il compito è stato spostato nella prima corsia', + 'The task has been moved to another swimlane:' => 'Il compito è stato spostato in un\'altra corsia:', + 'New title: %s' => 'Nuovo titolo: %s', + 'The task is not assigned anymore' => 'Il compito non è più assegnato a nessuno', + 'New assignee: %s' => 'Nuovo assegnatario: %s', + 'There is no category now' => 'Non è presente più nessuna categoria', + 'New category: %s' => 'Nuova categoria: %s', + 'New color: %s' => 'Nuovo colore: %s', + 'New complexity: %d' => 'Nuova complessità: %d', + 'The due date has been removed' => 'La data di scadenza è stata rimossa', + 'There is no description anymore' => 'Non è presente più alcuna descrizione.', + 'Recurrence settings has been modified' => 'Le impostazioni di ricorrenza sono state modificate', + 'Time spent changed: %sh' => 'Tempo trascorso modificato: %sh', + 'Time estimated changed: %sh' => 'Tempo stimato modificato: %sh', + 'The field "%s" has been updated' => 'Il campo %s è stato aggiornato', + 'The description has been modified:' => 'La descrizione è stata modificata', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Vuoi veramente chiudere il compito "%s" e i relativi sotto-compiti?', + 'I want to receive notifications for:' => 'Voglio ricevere le notifiche per:', + 'All tasks' => 'Tutti i compiti', + 'Only for tasks assigned to me' => 'Solo per i compiti assegnati a me', + 'Only for tasks created by me' => 'Solo per i compiti creati da me', + 'Only for tasks created by me and tasks assigned to me' => 'Solo per i compiti creati da me o assegnati a me', + '%%Y-%%m-%%d' => '%%Y-%%m-%%d', + 'Total for all columns' => 'Totale per tutte le colonne', + 'You need at least 2 days of data to show the chart.' => 'Hai bisogno di almeno 2 giorni di dati per mostrare il grafico.', + '<15m' => '<15m', + '<30m' => '<30m', + 'Stop timer' => 'Ferma il timer', + 'Start timer' => 'Avvia il timer', + 'My activity stream' => 'Il mio flusso attività', + 'Search tasks' => 'Ricerca compiti', + 'Reset filters' => 'Annulla filtri', + 'My tasks due tomorrow' => 'I miei compiti da completare per domani', + 'Tasks due today' => 'Task da completare oggi', + 'Tasks due tomorrow' => 'Task da completare per domani', + 'Tasks due yesterday' => 'Task da completare ieri', + 'Closed tasks' => 'Task chiusi', + 'Open tasks' => 'Task aperti', + 'Not assigned' => 'Non assegnato', + 'View advanced search syntax' => 'Visualizza la sintassi di ricerca avanzata', + 'Overview' => 'Panoramica', + 'Board/Calendar/List view' => 'Vista Bacheca/Calendario/Lista', + 'Switch to the board view' => 'Passa alla vista "bacheca"', + 'Switch to the list view' => 'Passa alla vista "elenco"', + 'Go to the search/filter box' => 'Vai alla casella di ricerca/filtro', + 'There is no activity yet.' => 'Non è presente ancora nessuna attività.', + 'No tasks found.' => 'Nessun compito trovato.', + 'Keyboard shortcut: "%s"' => 'Scorciatoia da tastiera: "%s"', + 'List' => 'Lista', + 'Filter' => 'Filtro', + 'Advanced search' => 'Ricerca avanzata', + 'Example of query: ' => 'Esempio di query: ', + 'Search by project: ' => 'Ricerca per progetto: ', + 'Search by column: ' => 'Ricerca per colonna: ', + 'Search by assignee: ' => 'Ricerca per assegnatario: ', + 'Search by color: ' => 'Ricerca per colore: ', + 'Search by category: ' => 'Ricerca per categoria: ', + 'Search by description: ' => 'Ricerca per descrizione: ', + 'Search by due date: ' => 'Ricerca per data di scadenza: ', + 'Average time spent in each column' => 'Tempo medio trascorso in ogni colonna', + 'Average time spent' => 'Tempo medio trascorso', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Questo grafico mostra il tempo medio trascorso in ogni colonna per gli ultimi %d compiti.', + 'Average Lead and Cycle time' => 'Tempo medio di consegna (Lead Time) e lavorazione (Cycle Time)', + 'Average lead time: ' => 'Tempo medio di consegna (Lead Time): ', + 'Average cycle time: ' => 'Tempo medio di lavorazione (Cycle Time): ', + 'Cycle Time' => 'Tempo di lavorazione (Cycle Time)', + 'Lead Time' => 'Tempo di consegna (Lead Time)', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Questo grafico mostra i tempi medi di consegna (Lead Time) e lavorazione (Cycle Time) per gli ultimi %d compiti.', + 'Average time into each column' => 'Tempo medio in ogni colonna', + 'Lead and cycle time' => 'Tempo di consegna e lavorazione', + 'Lead time: ' => 'Tempo di consegna (Lead Time): ', + 'Cycle time: ' => 'Tempo di lavorazione (Cycle Time): ', + 'Time spent in each column' => 'Tempo trascorso in ogni colonna', + 'The lead time is the duration between the task creation and the completion.' => 'Il tempo di consegna (Lead Time) è la durata tra la creazione di un compito ed il suo completamento.', + 'The cycle time is the duration between the start date and the completion.' => 'Il tempo di lavorazione (Cycle Time) è la durata tra la data di inizio della lavorazione di un compito ed il suo completamento.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Se il compito non è chiuso sarà usata la data attuale invece della data di completamento.', + 'Set the start date automatically' => 'Imposta automaticamente la data di inizio', + 'Edit Authentication' => 'Modifica Autenticazione', + 'Remote user' => 'Utente remoto', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'La password degli utenti remoti (ad esempio: LDAP, account Google e Github) non è salvata nel database di Kanboard', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Se selezioni la casella "Disabilita il form di login", le credenziali immesse nel modulo di accesso verranno ignorate.', + 'Default task color' => 'Colore predefinito dei compiti', + 'This feature does not work with all browsers.' => 'Questa feature non funziona con tutti i browser.', + 'There is no destination project available.' => 'Non ci sono progetti disponbili come destinazione.', + 'Trigger automatically subtask time tracking' => 'Attiva automaticamente il time-tracking per i sotto-compiti', + 'Include closed tasks in the cumulative flow diagram' => 'Includi i compiti chiusi nel diagramma di flusso cumulativo', + 'Current swimlane: %s' => 'Corsia attuale: %s', + 'Current column: %s' => 'Colonna attuale: %s', + 'Current category: %s' => 'Categoria attuale: %s', + 'no category' => 'nessuna categoria', + 'Current assignee: %s' => 'Assegnatario attuale: %s', + 'not assigned' => 'non assegnato', + 'Author:' => 'Autore', + 'contributors' => 'contributori', + 'License:' => 'Licenza:', + 'License' => 'Licenza', + 'Enter the text below' => 'Inserisci il testo qui sotto', + 'Start date:' => 'Data di inizio:', + 'Due date:' => 'Data di completamento:', + 'People who are project managers' => 'Persone che sono manager di progetto', + 'People who are project members' => 'Persone che sono membri di progetto', + 'NOK - Norwegian Krone' => 'NOK - Corone norvegesi', + 'Show this column' => 'Mostra questa colonna', + 'Hide this column' => 'Nascondi questa colonna', + 'End date' => 'Data di fine', + 'Users overview' => 'Panoramica utenti', + 'Members' => 'Membri', + 'Shared project' => 'Progetto condiviso', + 'Project managers' => 'Manager di progetto', + 'Projects list' => 'Elenco progetti', + 'End date:' => 'Data di fine:', + 'Change task color when using a specific task link' => 'Cambia colore del compito quando si un utilizza una determinata relazione di compito', + 'Task link creation or modification' => 'Creazione o modifica di relazione di compito', + 'Milestone' => 'Milestone', + 'Reset the search/filter box' => 'Resetta la riceca/filtro', + 'Documentation' => 'Documentazione', + 'Author' => 'Autore', + 'Version' => 'Versione', + 'Plugins' => 'Plugin', + 'There is no plugin loaded.' => 'Nessun plugin è stato caricato.', + 'My notifications' => 'Le mie notifiche', + 'Custom filters' => 'Filtri personalizzati', + 'Your custom filter has been created successfully.' => 'Il filtro personalizzato è stato creato con successo.', + 'Unable to create your custom filter.' => 'Impossibile creare il filtro personalizzato.', + 'Custom filter removed successfully.' => 'Filtro personalizzato rimosso con successo.', + 'Unable to remove this custom filter.' => 'Impossibile rimuovere questo filtro personalizzato', + 'Edit custom filter' => 'Modifica il filtro personalizzato', + 'Your custom filter has been updated successfully.' => 'Il filtro personalizzato è stato aggiornato con successo.', + 'Unable to update custom filter.' => 'Impossibile aggiornare il filtro personalizzato.', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Nuovo allegato nel compito #%d: %s', + 'New comment on task #%d' => 'Nuovo commento nel compito #%d', + 'Comment updated on task #%d' => 'Commento aggiornato nel compito #%d', + 'New subtask on task #%d' => 'Nuovo sotto-compito nel compito #%d', + 'Subtask updated on task #%d' => 'Sotto-compito aggiornato nel compito #%d', + 'New task #%d: %s' => 'Nuovo compito #%d: %s', + 'Task updated #%d' => 'Task #%d aggiornato', + 'Task #%d closed' => 'Task #%d chiuso', + 'Task #%d opened' => 'Task #%d aperto', + 'Column changed for task #%d' => 'Colonna modificata per il compito #%d', + 'New position for task #%d' => 'Nuova posizione per il compito #%d', + 'Swimlane changed for task #%d' => 'Corsia modificata per il compito #%d', + 'Assignee changed on task #%d' => 'Assegnatario modificato per il compito #%d', + '%d overdue tasks' => '%d compiti scaduti', + 'No notification.' => 'Nessuna nuova notifica.', + 'Mark all as read' => 'Segna tutti come letti', + 'Mark as read' => 'Segna come letto', + 'Total number of tasks in this column across all swimlanes' => 'Numero totale di compito in questa colonna per tutte le corsie', + 'Collapse swimlane' => 'Minimizza corsia', + 'Expand swimlane' => 'Espandi corsia', + 'Add a new filter' => 'Aggiungi un nuovo filtro', + 'Share with all project members' => 'Condividi con tutti i membri del progetto', + 'Shared' => 'Condiviso', + 'Owner' => 'Proprietario', + 'Unread notifications' => 'Notifiche non lette', + 'Notification methods:' => 'Metodi di notifica', + 'Unable to read your file' => 'Impossibile leggere il file', + '%d task(s) have been imported successfully.' => '%d compito/i sono stati importati con successo.', + 'Nothing has been imported!' => 'Non è stato importato nulla!', + 'Import users from CSV file' => 'Importa utenti da file CSV', + '%d user(s) have been imported successfully.' => '%d utenti importati con successo.', + 'Comma' => 'Virgola', + 'Semi-colon' => 'Punto e virgola', + 'Tab' => 'Tabulazione', + 'Vertical bar' => 'Barra verticale', + 'Double Quote' => 'Apice singolo', + 'Single Quote' => 'Doppio apice', + '%s attached a file to the task #%d' => '%s ha allegato un file al compito #%d', + 'There is no column or swimlane activated in your project!' => 'Non ci sono colonne o corsie attive all\'interno del tuo progetto!', + 'Append filter (instead of replacement)' => 'Aggiungi filtro (anzichè sostituirlo)', + 'Append/Replace' => 'Aggiungi/Sostituisci', + 'Append' => 'Aggiungi', + 'Replace' => 'Sostituisci', + 'Import' => 'Importa', + 'Change sorting' => 'cambia ordinamento', + 'Tasks Importation' => 'Importazione compito', + 'Delimiter' => 'Delimitatore', + 'Enclosure' => 'Contenitore', + 'CSV File' => 'File CSV', + 'Instructions' => 'Istruzioni', + 'Your file must use the predefined CSV format' => 'Il file deve rispettare il formato CSV predefinito', + 'Your file must be encoded in UTF-8' => 'Il file deve essere codificato in formato UTF-8', + 'The first row must be the header' => 'La prima riga deve contenere l\'intestazione', + 'Duplicates are not verified for you' => 'Le righe duplicate non verranno controllate', + 'The due date must use the ISO format: YYYY-MM-DD' => 'La data di scadenza deve usare il formato ISO: YYYY-MM-DD', + 'Download CSV template' => 'Scarica il template CSV', + 'No external integration registered.' => 'Nessuna integrazione esterna presente.', + 'Duplicates are not imported' => 'I duplicati non veranno importati', + 'Usernames must be lowercase and unique' => 'I nomi utente devono essere in minuscolo e univoci', + 'Passwords will be encrypted if present' => 'Se presenti, le password verranno criptate', + '%s attached a new file to the task %s' => '%s ha allegato un nuovo file al compito %s', + 'Link type' => 'Tipo di link', + 'Assign automatically a category based on a link' => 'Assegna automaticamente una categoria sulla base di una relazione', + 'BAM - Konvertible Mark' => 'BAM - Marco bosniaco', + 'Assignee Username' => 'Nome utente dell\'assegnatario', + 'Assignee Name' => 'Nome dell\'assegnatario', + 'Groups' => 'Gruppi', + 'Members of %s' => 'Membri di %s', + 'New group' => 'Nuovo gruppo', + 'Group created successfully.' => 'Gruppo creato con successo', + 'Unable to create your group.' => 'Impossibile creare il gruppo', + 'Edit group' => 'Modifica gruppo', + 'Group updated successfully.' => 'Gruppo aggiornato con successo.', + 'Unable to update your group.' => 'Impossibile aggiornare il gruppo', + 'Add group member to "%s"' => 'Aggiungi un membro al gruppo "%s"', + 'Group member added successfully.' => 'Membro del gruppo aggiunto con successo.', + 'Unable to add group member.' => 'Impossibile aggiungere un membro del gruppo', + 'Remove user from group "%s"' => 'Rimuovi utente dal gruppo "%s"', + 'User removed successfully from this group.' => 'Utente rimosso dal gruppo con successo.', + 'Unable to remove this user from the group.' => 'Impossibile rimuovere l\'utentei dal gruppo.', + 'Remove group' => 'Rimuovi gruppo', + 'Group removed successfully.' => 'Gruppo rimosso con successo.', + 'Unable to remove this group.' => 'Impossibile rimuovere questo gruppo.', + 'Project Permissions' => 'Permessi del progetto', + 'Manager' => 'Manager', + 'Project Manager' => 'Manager del progetto', + 'Project Member' => 'Membro del progetto', + 'Project Viewer' => 'Osservatore del progetto', + 'Your account is locked for %d minutes' => 'Il tuo account è bloccato per %d minuti', + 'Invalid captcha' => 'Captcha non valido', + 'The name must be unique' => 'Il nome deve essere univoco', + 'View all groups' => 'Visualiza tutti i gruppi', + 'There is no user available.' => 'Nessun utente disponibile.', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Vuoi davvero rimuovere l\'utente "%s" dal gruppo "%s"?', + 'There is no group.' => 'Nessun gruppo presente', + 'Add group member' => 'Aggiungi un membro del gruppo', + 'Do you really want to remove this group: "%s"?' => 'Vuoi davvero rimuovere questo gruppo: "%s"?', + 'There is no user in this group.' => 'Nessun utente in questo gruppo.', + 'Permissions' => 'Permessi', + 'Allowed Users' => 'Utenti autorizzati', + 'No specific user has been allowed.' => 'Nessun utente è stato esplicitamente autorizzato.', + 'Role' => 'Ruolo', + 'Enter user name...' => 'Inserisci il nome utente...', + 'Allowed Groups' => 'Gruppi autorizzati', + 'No group has been allowed.' => 'Nessun gruppo è stato esplicitamente autorizzato.', + 'Group' => 'Gruppo', + 'Group Name' => 'Nome del gruppo', + 'Enter group name...' => 'Inserisci il nome del gruppo...', + 'Role:' => 'Ruolo:', + 'Project members' => 'Membri di progetto', + '%s mentioned you in the task #%d' => '%s ti ha menzionato nel compito #%d', + '%s mentioned you in a comment on the task #%d' => '%s ti ha menzionato in un commento del compito #%d', + 'You were mentioned in the task #%d' => 'Sei stato menzionato nel compito #%d', + 'You were mentioned in a comment on the task #%d' => 'Sei stato menzionato in un commento del compito #%d', + 'Estimated hours: ' => 'Ore stimate: ', + 'Actual hours: ' => 'Ore effettive: ', + 'Hours Spent' => 'Ore impiegate', + 'Hours Estimated' => 'Ore stimate', + 'Estimated Time' => 'Tempo stimato', + 'Actual Time' => 'Tempo effettivo', + 'Estimated vs actual time' => 'Tempo stimato vs Tempo effettivo', + 'RUB - Russian Ruble' => 'RUB - Rublo russo', + 'Assign the task to the person who does the action when the column is changed' => 'Assegna il compito alla persona che esegue l\'azione quando la colonna viene modificata', + 'Close a task in a specific column' => 'Chiudi un compito in una specifica colonna', + 'Time-based One-time Password Algorithm' => 'Algoritmo per la Time-based One-time Password', + 'Two-Factor Provider: ' => 'Provider autenticazione a due fattori: ', + 'Disable two-factor authentication' => 'Disabilita l\'autenticazione "two-factor"', + 'Enable two-factor authentication' => 'Abilita l\'autenticazione "two-factor"', + 'There is no integration registered at the moment.' => 'Nessuna integrazione disponibile al momento.', + 'Password Reset for Kanboard' => 'Reimposta password per Kanboard', + 'Forgot password?' => 'Password dimenticata?', + 'Enable "Forget Password"' => 'Abilita funzione "Password dimenticata"', + 'Password Reset' => 'Reimposta password', + 'New password' => 'Nuova password', + 'Change Password' => 'Cambia password', + 'To reset your password click on this link:' => 'Per reimpostare la tua password clicca su questo link:', + 'Last Password Reset' => 'Ultimo cambio password', + 'The password has never been reinitialized.' => 'La password non è mai stata reimpostata.', + 'Creation' => 'Creazione', + 'Expiration' => 'Scadenza', + 'Password reset history' => 'Storico cambio password', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Tutti i compiti della colonna "%s" e della corsia "%s" sono stati chiusi con successo.', + 'Do you really want to close all tasks of this column?' => 'Vuoi davvero chiudere tutti i compiti di questa colonna?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d compito/i della colonna "%s" e della corsia "%s" saranno chiusi.', + 'Close all tasks in this column and this swimlane' => 'Chiudi tutti i compiti di questa colonna', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Nessun plugin ha caricato un metodo di notifica di progetto. Puoi tuttavia configurare le notifiche personali dal tuo profilo utente.', + 'My dashboard' => 'La mia bacheca', + 'My profile' => 'Il mio profilo', + 'Project owner: ' => 'Proprietario del progetto: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'L\'identificativo del progetto è opzionale e deve essere alfanumerico, ad esempio: MIOPROGETTO.', + 'Project owner' => 'Proprietario del progetto', + 'Personal projects do not have users and groups management.' => 'Per i progetti privati non è prevista la gestione di utenti e gruppi.', + 'There is no project member.' => 'Non è impostato un membro del progetto.', + 'Priority' => 'Priorità', + 'Task priority' => 'Priorità del compito', + 'General' => 'Generale', + 'Dates' => 'Date', + 'Default priority' => 'Priorità predefinita', + 'Lowest priority' => 'Priorità minima', + 'Highest priority' => 'Priorità massima', + 'Close a task when there is no activity' => 'Chiudi un compito quando non c\'è nessuna attività', + 'Duration in days' => 'Durata in giorni', + 'Send email when there is no activity on a task' => 'Invia un\'email quando non c\'è attività sul compito', + 'Unable to fetch link information.' => 'Impossibile recuperare informazioni sul link.', + 'Daily background job for tasks' => 'Job giornaliero in background per i compiti', + 'Auto' => 'Automatico', + 'Related' => 'Correlato', + 'Attachment' => 'Allegato', + 'Web Link' => 'Link Web', + 'External links' => 'Link esterni', + 'Add external link' => 'Aggiungi link esterno', + 'Type' => 'Tipo', + 'Dependency' => 'Dipendenza', + 'Add internal link' => 'Aggiungi link interno', + 'Add a new external link' => 'Aggiungi un nuovo link esterno', + 'Edit external link' => 'Modifica link esterno', + 'External link' => 'Link esterno', + 'Copy and paste your link here...' => 'Copia e incolla il tuo link qui...', + 'URL' => 'URL', + 'Internal links' => 'Link interni', + 'Assign to me' => 'Assegna a me', + 'Me' => 'Me stesso', + 'Do not duplicate anything' => 'Non duplicare nulla', + 'Projects management' => 'Gestione progetti', + 'Users management' => 'Gestione utenti', + 'Groups management' => 'Gestione gruppi', + 'Create from another project' => 'Crea da un\'altro progetto', + 'open' => 'aperto', + 'closed' => 'chiuso', + 'Priority:' => 'Priorità:', + 'Reference:' => 'Riferimento:', + 'Complexity:' => 'Complessità:', + 'Swimlane:' => 'Corsia:', + 'Column:' => 'Colonna:', + 'Position:' => 'Posizione:', + 'Creator:' => 'Creatore:', + 'Time estimated:' => 'Tempo stimato:', + '%s hours' => '%s ore', + 'Time spent:' => 'Tempo trascorso:', + 'Created:' => 'Creato:', + 'Modified:' => 'Modificato:', + 'Completed:' => 'Completato:', + 'Started:' => 'Iniziato:', + 'Moved:' => 'Spostato:', + 'Task #%d' => 'Task #%d', + 'Time format' => 'Formato ora', + 'Start date: ' => 'Data di inizio: ', + 'End date: ' => 'Data di fine: ', + 'New due date: ' => 'Nuova data di scadenza: ', + 'Start date changed: ' => 'Data di inizio cambiata: ', + 'Disable personal projects' => 'Disabilita progetti privati', + 'Do you really want to remove this custom filter: "%s"?' => 'Vuoi davvero rimuovere questo filtro personalizato: "%s"?', + 'Remove a custom filter' => 'Rimuovi un filtro personalizzato', + 'User activated successfully.' => 'Utente attivato con successo.', + 'Unable to enable this user.' => 'Impossibile abilitare questo utente.', + 'User disabled successfully.' => 'Utente disabilitato con successo.', + 'Unable to disable this user.' => 'Impossibile disabilitare questo utente.', + 'All files have been uploaded successfully.' => 'Tutti i file sono stati caricati con successo.', + 'The maximum allowed file size is %sB.' => 'La dimensione massima consentita del file è %sB.', + 'Drag and drop your files here' => 'Trascina i tuoi file qui', + 'choose files' => 'seleziona i file', + 'View profile' => 'Guarda il profilo', + 'Two Factor' => 'Due fattori', + 'Disable user' => 'Disabilita utente', + 'Do you really want to disable this user: "%s"?' => 'Vuoi davvero disabilitare questo utente: "%s"?', + 'Enable user' => 'Abilita utente', + 'Do you really want to enable this user: "%s"?' => 'Vuoi davvero abilitare questo utente: "%s"?', + 'Download' => 'Scarica', + 'Uploaded: %s' => 'Caricato: %s', + 'Size: %s' => 'Dimensione: %s', + 'Uploaded by %s' => 'Caricato da %s', + 'Filename' => 'Nome del file', + 'Size' => 'Dimensione', + 'Column created successfully.' => 'Colonna creata con successo.', + 'Another column with the same name exists in the project' => 'Un\'altra colonna con lo stesso nome è già esistente in questo progetto', + 'Default filters' => 'Filtri predefiniti', + 'Your board doesn\'t have any columns!' => 'La tua bacheca non ha nessuna colonna!', + 'Change column position' => 'Modifica la posizione della colonna', + 'Switch to the project overview' => 'Passa alla panoramica di progetto', + 'User filters' => 'Filtri utente', + 'Category filters' => 'Filtri di categorie', + 'Upload a file' => 'Carica un file', + 'View file' => 'Visualizza file', + 'Last activity' => 'Attività recente', + 'Change subtask position' => 'Cambia la posizione del sotto-compito', + 'This value must be greater than %d' => 'Questo valore deve essere magiore di %d', + 'Another swimlane with the same name exists in the project' => 'Un\'altra corsia con lo stesso nome è già esistente in questo progetto', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Esempio: https:example.kanboard.org/ (usato per generare URL assolute)', + 'Actions duplicated successfully.' => 'Azioni duplicate con successo.', + 'Unable to duplicate actions.' => 'Impossibile duplicare le azioni.', + 'Add a new action' => 'Aggiungi una nuova azione', + 'Import from another project' => 'Importa da un altro progetto', + 'There is no action at the moment.' => 'Nessuna azione disponibile al momento.', + 'Import actions from another project' => 'Importa azioni da un altro progetto', + 'There is no available project.' => 'Nessun progetto disponibile.', + 'Local File' => 'File locale', + 'Configuration' => 'Configurazione', + 'PHP version:' => 'Versione PHP:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'Versione OS:', + 'Database version:' => 'Versione database:', + 'Browser:' => 'Browser:', + 'Task view' => 'Vista dei compiti', + 'Edit task' => 'Modifica compito', + 'Edit description' => 'Modifica descrizione', + 'New internal link' => 'Nuovo link interno', + 'Display list of keyboard shortcuts' => 'Mostra una lista di scorciatoie da tastiera', + 'Avatar' => 'Avatar', + 'Upload my avatar image' => 'Carica l\'immagine del mio avatar', + 'Remove my image' => 'Rimuovi la mia immagine', + 'The OAuth2 state parameter is invalid' => 'Il parametro di stato OAuth2 non è valido.', + 'User not found.' => 'Utente non trovato.', + 'Search in activity stream' => 'Ricerca nel mio flusso attività', + 'My activities' => 'Le mie attività', + 'Activity until yesterday' => 'Attività ad oggi', + 'Activity until today' => 'Attività fino a ieri', + 'Search by creator: ' => 'Ricerca per creatore: ', + 'Search by creation date: ' => 'Ricerca per data di creazione: ', + 'Search by task status: ' => 'Ricerca per stato del compito: ', + 'Search by task title: ' => 'Ricerca per titolo del compito: ', + 'Activity stream search' => 'Ricerca nel flusso attività', + 'Projects where "%s" is manager' => 'Progetti all\'interno dei quali "%s" è manager', + 'Projects where "%s" is member' => 'Progetti all\'interno dei quali "%s" è membro', + 'Open tasks assigned to "%s"' => 'Compiti aperti assegnati a "%s"', + 'Closed tasks assigned to "%s"' => 'Compiti chiusi assegnati a "%s"', + 'Assign automatically a color based on a priority' => 'Assegna automaticamente un colore in base alla priorità', + 'Overdue tasks for the project(s) "%s"' => 'Task scaduti per il progetto "%s"', + 'Upload files' => 'Carica file', + 'Installed Plugins' => 'Plugin installati', + 'Plugin Directory' => 'Directory dei plugin', + 'Plugin installed successfully.' => 'Plugin installato con successo.', + 'Plugin updated successfully.' => 'Plugin aggiornato con successo.', + 'Plugin removed successfully.' => 'Plugin rimosso con successo.', + 'Subtask converted to task successfully.' => 'Sotto-compito converito in compito con successo.', + 'Unable to convert the subtask.' => 'Impossibile convertire il sotto-compito.', + 'Unable to extract plugin archive.' => 'Impossibile estrarre l\' archivio del plugin.', + 'Plugin not found.' => 'Plugin non trovato.', + 'You don\'t have the permission to remove this plugin.' => 'Non hai i permessi per rimuovere questo plugin.', + 'Unable to download plugin archive.' => 'Impossibile scaricare l\'archivo del plugin', + 'Unable to write temporary file for plugin.' => 'Impossibile scrivere il file temporaneo per il plugin.', + 'Unable to open plugin archive.' => 'Impossibile aprire l\'archivio del plugin.', + 'There is no file in the plugin archive.' => 'Non ci sono file nell\' archivio del plugin.', + 'Create tasks in bulk' => 'Creazione massiva di compiti', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'La tua installazione Kanboard non è configurata per installare plugin tramite l\'interfaccia utente.', + 'There is no plugin available.' => 'Non ci sono plugin disponibili.', + 'Install' => 'Installa', + 'Update' => 'Aggiorna', + 'Up to date' => 'Aggiornato', + 'Not available' => 'Non disponibile', + 'Remove plugin' => 'Disinstalla plugin', + 'Do you really want to remove this plugin: "%s"?' => 'Vuoi davvero rimuovere questo plugin: "%s"?', + 'Uninstall' => 'Disinstalla', + 'Listing' => 'Elenco', + 'Metadata' => 'Metadati', + 'Manage projects' => 'Gestione progetti', + 'Convert to task' => 'Converti in compito', + 'Convert sub-task to task' => 'Converti il sotto-compito in compito', + 'Do you really want to convert this sub-task to a task?' => 'Vuoi davvero convertire questo sotto-compito in un compito?', + 'My task title' => 'Titolo del mio compito', + 'Enter one task by line.' => 'Inserisci un compito per ogni riga.', + 'Number of failed login:' => 'Numero di login falliti:', + 'Account locked until:' => 'Account bloccato fino al:', + 'Email settings' => 'Impostazioni Email', + 'Email sender address' => 'Indirizzo Email mittente', + 'Email transport' => 'Trasporto Email', + 'Webhook token' => 'Token Webhook', + 'Project tags management' => 'Gestione tag di progetto', + 'Tag created successfully.' => 'Tag creato con successo.', + 'Unable to create this tag.' => 'Impossibile creare questo tag.', + 'Tag updated successfully.' => 'Tag aggiornato con successo.', + 'Unable to update this tag.' => 'Impossibile aggiornare questo tag.', + 'Tag removed successfully.' => 'Tag rimosso con successo.', + 'Unable to remove this tag.' => 'Impossibile rimuovere questo tag.', + 'Global tags management' => 'Gestione dei tag globali', + 'Tags' => 'Tag', + 'Tags management' => 'Gestione dei tag', + 'Add new tag' => 'Aggiungi un nuovo tag', + 'Edit a tag' => 'Modifica un tag', + 'Project tags' => 'Tag di progetto', + 'There is no specific tag for this project at the moment.' => 'Non è definito nessun tag specifico per questo progetto al momento.', + 'Tag' => 'Tag', + 'Remove a tag' => 'Rimuovi un tag', + 'Do you really want to remove this tag: "%s"?' => 'Vuoi davvero rimuovere questo tag: "%s"?', + 'Global tags' => 'Tag globali', + 'There is no global tag at the moment.' => 'Non sono definiti tag globali al momento.', + 'This field cannot be empty' => 'Questo campo non può essere vuoto', + 'Close a task when there is no activity in a specific column' => 'Chiudi un compito quando non vi è alcuna attività in una specifica colonna', + '%s removed a subtask for the task #%d' => '%s rimosso un sotto-compito per il compito #%d', + '%s removed a comment on the task #%d' => '%s rimosso un commento nel compito #%d', + 'Comment removed on task #%d' => 'Commento rimosso nel compito #%d', + 'Subtask removed on task #%d' => 'Sotto-compito rimosso nel compito #%d', + 'Hide tasks in this column in the dashboard' => 'Nascondi i compiti in questa colonna nella dashboard', + '%s removed a comment on the task %s' => '%s rimosso un commento nel compito %s', + '%s removed a subtask for the task %s' => '%s rimosso un sotto-compito per il compito %s', + 'Comment removed' => 'Commento rimosso', + 'Subtask removed' => 'Sotto-compito rimosso', + '%s set a new internal link for the task #%d' => '%s imposta un nuovo link interno per il compito #%d', + '%s removed an internal link for the task #%d' => '%s rimosso un link interno per il compito #%d', + 'A new internal link for the task #%d has been defined' => 'E\' stato definito un nuovo link interno per il compito #%d', + 'Internal link removed for the task #%d' => 'Link interno rimosso per il compito #%d', + '%s set a new internal link for the task %s' => '%s imposta un nuovo link interno per il compito %s', + '%s removed an internal link for the task %s' => '%s rimosso un link interno per il compito %s', + 'Automatically set the due date on task creation' => 'Imposta automaticamente la data di scadenza alla creazione del compito', + 'Move the task to another column when closed' => 'Sposta il compito su un\'altra colonna quando viene chiuso', + 'Move the task to another column when not moved during a given period' => 'Sposta il compito su un\'altra colonna quando non viene spostato entro un certo periodo', + 'Dashboard for %s' => 'Dashboard per %s', + 'Tasks overview for %s' => 'Panoramica dei compiti per %s', + 'Subtasks overview for %s' => 'Panoramica dei compiti per %s', + 'Projects overview for %s' => 'Panoramica dei progetti per %s', + 'Activity stream for %s' => 'Flusso attività per %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Assegna un colore quando il compito viene spostato su una specifica corsia', + 'Assign a priority when the task is moved to a specific swimlane' => 'Assegna una priorità quando il compito viene spostato su una specifica corsia', + 'User unlocked successfully.' => 'Utente sbloccato con successo.', + 'Unable to unlock the user.' => 'Impossibile sbloccare l\'utente.', + 'Move a task to another swimlane' => 'Sposta un compito su un\'altra corsia', + 'Creator Name' => 'Nome del creatore', + 'Time spent and estimated' => 'Tempo trascorso e stima', + 'Move position' => 'Cambia posizione', + 'Move task to another position on the board' => 'Sposta compito su un\'altra posizione nella board', + 'Insert before this task' => 'Inserisci prima di questo compito', + 'Insert after this task' => 'Inserisci dopo questo compito', + 'Unlock this user' => 'Sblocca quest\'utente', + 'Custom Project Roles' => 'Personalizza Ruoli del Progetto', + 'Add a new custom role' => 'Aggiungi un nuovo ruolo personalizzato', + 'Restrictions for the role "%s"' => 'Restrizioni per il ruolo "%s"', + 'Add a new project restriction' => 'Aggiungi una nuova restrizione per il progetto', + 'Add a new drag and drop restriction' => 'Aggiungi una nuova restrizione per il trascinamento', + 'Add a new column restriction' => 'Aggiungi una nuova restrizione per la colonna', + 'Edit this role' => 'Modifica questo ruolo', + 'Remove this role' => 'Cancella questo ruolo', + 'There is no restriction for this role.' => 'Non ci sono restrizioni per questo ruolo.', + 'Only moving task between those columns is permitted' => 'È permesso solo muovere i compiti tra queste colonne', + 'Close a task in a specific column when not moved during a given period' => 'Chiudi un compito in una colonna specifica quando non viene mosso per un determinato periodo', + 'Edit columns' => 'Modifica colonne', + 'The column restriction has been created successfully.' => 'La restrizione per le colonne è stata creata correttamente.', + 'Unable to create this column restriction.' => 'Impossibile creare questa restrizione per colonne.', + 'Column restriction removed successfully.' => 'Restrizione per colonne rimossa correttamente.', + 'Unable to remove this restriction.' => 'Impossibile rimuovere questa restrizione.', + 'Your custom project role has been created successfully.' => 'La regola personalizzata per il progetto è stata creata correttamente.', + 'Unable to create custom project role.' => 'Impossibile creare la regola personalizzata per il progetto.', + 'Your custom project role has been updated successfully.' => 'La regola personalizzata per il progetto è stata modificata correttamente.', + 'Unable to update custom project role.' => 'Impossibile modificare correttamente la regola personalizzata per il progetto.', + 'Custom project role removed successfully.' => 'Regola personalizzata per il progetto rimossa correttamente.', + 'Unable to remove this project role.' => 'Impossibile rimuovere questa regola per il progetto.', + 'The project restriction has been created successfully.' => 'La restrizione di progetto è stata creata con successo.', + 'Unable to create this project restriction.' => 'Impossibile creare la restrizione di progetto.', + 'Project restriction removed successfully.' => 'Restrizione di progetto rimossa correttamente.', + 'You cannot create tasks in this column.' => 'Non puoi creare dei compiti in questa colonna.', + 'Task creation is permitted for this column' => 'La creazione di compiti è permessa per questa colonna', + 'Closing or opening a task is permitted for this column' => 'Chiudere o aprire compito è permesso per questa colonna', + 'Task creation is blocked for this column' => 'La creazione di compiti è bloccata per questa colonna', + 'Closing or opening a task is blocked for this column' => 'Chiudere o aprire compito è bloccato per questa colonna', + 'Task creation is not permitted' => 'Creazione compito non permessa', + 'Closing or opening a task is not permitted' => 'Chiudere o aprire compito non è permesso', + 'New drag and drop restriction for the role "%s"' => 'Nuova restrizione "drag and drop" per la regola "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Gli utenti che appartengono a questa regola saranno in grado di spostare i compiti solo tra la colonna di origine e quella di destinazione', + 'Remove a column restriction' => 'Rimuovere una restrizione di colonna', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Vuoi davvero rimuovere questa restrizione di colonna: "%s" a "%s"?', + 'New column restriction for the role "%s"' => 'Nuova restrizione di colonna per la regola "%s"', + 'Rule' => 'Regola', + 'Do you really want to remove this column restriction?' => 'Vuoi davvero rimuovere questa restrizione di colonna?', + 'Custom roles' => 'Regole personalizzate', + 'New custom project role' => 'Nuova regola per progetto personalizzato', + 'Edit custom project role' => 'Modifica regola per progetto personalizzato', + 'Remove a custom role' => 'Rimuovi regola personalizzata', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Vuoi davvero rimuovere questa regola personalizzata: "%s"? Tutti gli utenti assegnati a questa regola diventeranno membri del progetto', + 'There is no custom role for this project.' => 'Non c\'è regola personalizzata per questo progetto.', + 'New project restriction for the role "%s"' => 'Nuova restrizione di progetto per la regola "%s"', + 'Restriction' => 'Restrizione', + 'Remove a project restriction' => 'Rimuovi restrizione di progetto', + 'Do you really want to remove this project restriction: "%s"?' => 'Vuoi davvero rimuovere questa restrizione di progetto: "%s"?', + 'Duplicate to multiple projects' => 'Duplica su più progetti', + 'This field is required' => 'Questo campo è obbligatorio', + 'Moving a task is not permitted' => 'Spostare un compito non è permesso', + 'This value must be in the range %d to %d' => 'Questo valore deve essere compreso tra %d e %d', + 'You are not allowed to move this task.' => 'Non ti è permesso spostare questo compito.', + 'API User Access' => 'API per l\'accesso utente', + 'Preview' => 'Anteprima', + 'Write' => 'Scrivi', + 'Write your text in Markdown' => 'Scrivi il tuo testo in Markdown', + 'No personal API access token registered.' => 'Nessun token di accesso API personale registrato.', + 'Your personal API access token is "%s"' => 'Il tuo token di accesso API personale è "%s"', + 'Remove your token' => 'Rimuovi il tuo token', + 'Generate a new token' => 'Genera un nuovo token', + 'Showing %d-%d of %d' => 'Visualizzazione %d-%d di %d', + 'Outgoing Emails' => 'Email in uscita', + 'Add or change currency rate' => 'Aggiungi o modifica la valuta di riferimento', + 'Reference currency: %s' => 'Valuta di riferimento: %s', + 'Add custom filters' => 'Aggiungi filtri personalizzati', + 'Export' => 'Esporta', + 'Add link label' => 'Aggiungi un\'etichetta al link', + 'Incompatible Plugins' => 'Plugin incompatibili', + 'Compatibility' => 'Compatibilità', + 'Permissions and ownership' => 'Permessi e assegnazioni', + 'Priorities' => 'Priorità', + 'Close this window' => 'Chiudi questa finestra', + 'Unable to upload this file.' => 'Non è stato possibile caricare questo file.', + 'Import tasks' => 'Importa compiti', + 'Choose a project' => 'Scegli un progetto', + 'Profile' => 'Profilo', + 'Application role' => 'Ruolo applicativo', + '%d invitations were sent.' => 'Sono stati spediti %d inviti.', + '%d invitation was sent.' => 'E\' stato spedito %d invito.', + 'Unable to create this user.' => 'Non è stato possibile creare questo utente.', + 'Kanboard Invitation' => 'Invito a Kanboard', + 'Visible on dashboard' => 'Visibile sulla dashboard', + 'Created at:' => 'Creato il:', + 'Updated at:' => 'Aggiornato il:', + 'There is no custom filter.' => 'Non c\'è alcun filtro personalizzato.', + 'New User' => 'Nuovo Utente', + 'Authentication' => 'Autenticazione', + 'If checked, this user will use a third-party system for authentication.' => 'Quando selezionato, l\'utente utilizzerà un sistema di terza parte per l\'autenticazione.', + 'The password is necessary only for local users.' => 'La password è obbligatoria solo per gli utenti locali.', + 'You have been invited to register on Kanboard.' => 'Sei stato invitato a registrarti su Kanboard.', + 'Click here to join your team' => 'Clicca qui per accedere al tuo gruppo di lavoro', + 'Invite people' => 'Invita altre persone', + 'Emails' => 'Emails', + 'Enter one email address by line.' => 'Inserisci un indirizzo email per riga.', + 'Add these people to this project' => 'Aggiungi queste persone al progetto', + 'Add this person to this project' => 'Aggiungi questa persona al progetto', + 'Sign-up' => 'Registrati', + 'Credentials' => 'Credenziali', + 'New user' => 'Nuovo utente', + 'This username is already taken' => 'Questo nome utente esiste già', + 'Your profile must have a valid email address.' => 'Il tuo profilo deve avere un indirizzo email valido.', + 'TRL - Turkish Lira' => 'TRL - Lira Turca', + 'The project email is optional and could be used by several plugins.' => 'L\'email del progetto è opzionale e può essere utilizzata da plugin differenti.', + 'The project email must be unique across all projects' => 'L\'email del progetto deve essere univoca tra tutti i progetti', + 'The email configuration has been disabled by the administrator.' => 'La configurazione della email è stata disabilitata dall\'amministratore', + 'Close this project' => 'Chiudi questo progetto', + 'Open this project' => 'Apri questo progetto', + 'Close a project' => 'Chiudi un progetto', + 'Do you really want to close this project: "%s"?' => 'Vuoi chiudere veramente questo progetto: "%s"? ', + 'Reopen a project' => 'Riapri un progetto', + 'Do you really want to reopen this project: "%s"?' => 'Vuoi riaprire veramente questo progetto: "%s"?', + 'This project is open' => 'Questo progetto è aperto', + 'This project is closed' => 'Questo progetto è chiuso', + 'Unable to upload files, check the permissions of your data folder.' => 'Non è stato possibile caricare i file, verifica i permessi della cartella contenente i dati.', + 'Another category with the same name exists in this project' => 'Esiste già una categoria con lo stesso nome in questo progetto', + 'Comment sent by email successfully.' => 'Commento inviato per email con successo.', + 'Sent by email to "%s" (%s)' => 'Inviato per email a "%s" (%s)', + 'Unable to read uploaded file.' => 'Non è stato possibile leggere il file caricato.', + 'Database uploaded successfully.' => 'Database caricato con successo.', + 'Task sent by email successfully.' => 'Task inviato per email con successo.', + 'There is no category in this project.' => 'Non esiste alcuna categoria in questo progetto.', + 'Send by email' => 'Invia per email', + 'Create and send a comment by email' => 'Crea ed invia un commento per email', + 'Subject' => 'Oggetto', + 'Upload the database' => 'Carica il database', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Puoi caricare il database Sqlite precedentemente scaricato (Gzip format).', + 'Database file' => 'File del database', + 'Upload' => 'Carica', + 'Your project must have at least one active swimlane.' => 'Il tuo progetto deve avere almeno una corsia attiva.', + 'Project: %s' => 'Progetto: %s', + 'Automatic action not found: "%s"' => 'Azione automatica non trovata: "%s"', + '%d projects' => '%d progetti', + '%d project' => '%d progetto', + 'There is no project.' => 'Non esiste alcun progetto.', + 'Sort' => 'Ordina', + 'Project ID' => 'ID progetto', + 'Project name' => 'Nome del progetto', + 'Public' => 'Pubblico', + 'Personal' => 'Privato', + '%d tasks' => '%d dei compiti', + '%d task' => '%d del compito', + 'Task ID' => 'ID del compito', + 'Assign automatically a color when due date is expired' => 'Assegna automaticamente un colore quando la data di scadenza è stata raggiunta', + 'Total score in this column across all swimlanes' => 'Punteggio totale di questa colonna in tutte le corsie', + 'HRK - Kuna' => 'HRK - Kuna', + 'ARS - Argentine Peso' => 'ARS - Peso Argentino', + 'COP - Colombian Peso' => 'COP - Peso Colombiano', + '%d groups' => '%d gruppi', + '%d group' => '%d gruppo', + 'Group ID' => 'ID gruppo', + 'External ID' => 'ID esterno', + '%d users' => '%d utenti', + '%d user' => '%d utente', + 'Hide subtasks' => 'Nascondi sotto-compiti', + 'Show subtasks' => 'Mostra sotto-compiti', + 'Authentication Parameters' => 'Parametri di Autenticazione', + 'API Access' => 'Accesso API', + 'No users found.' => 'Nessun utente trovato.', + 'User ID' => 'ID utente', + 'Notifications are activated' => 'Le notifiche sono attivate', + 'Notifications are disabled' => 'Le notifiche sono disattivate', + 'User disabled' => 'Utente disabilitato', + '%d notifications' => '%d notifiche', + '%d notification' => '%d notifica', + 'There is no external integration installed.' => 'Nessuna integrazione esterna installata.', + 'You are not allowed to update tasks assigned to someone else.' => 'Non sei abilitato ad aggiornare i compiti assegnati a qualcun altro.', + 'You are not allowed to change the assignee.' => 'Non sei abilitato a modificare l\'assegnatario', + 'Task suppression is not permitted' => 'L\'eliminazione del compito non è permessa', + 'Changing assignee is not permitted' => 'Non è permesso modificare l\'assegnatario', + 'Update only assigned tasks is permitted' => 'E\' permesso aggiornare solo i compiti assegnati', + 'Only for tasks assigned to the current user' => 'Solo per i compiti assegnati all\'utente corrente', + 'My projects' => 'I miei progetti', + 'You are not a member of any project.' => 'Non sei membro di alcun progetto.', + 'My subtasks' => 'I miei sotto-compiti', + '%d subtasks' => '%d sotto-compiti', + '%d subtask' => '%d sotto-compiti', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'I compiti assegnati all\'utente corrente possono essere solo spostati in quelle colonne', + '[DUPLICATE]' => '[DUPLICA]', + 'DKK - Danish Krona' => 'DKK - Corona Danese', + 'Remove user from group' => 'Rimuovi utente dal gruppo', + 'Assign the task to its creator' => 'Assegna il compito al suo creatore', + 'This task was sent by email to "%s" with subject "%s".' => 'Questo compito è stato inviato per email a "%s" con oggetto "%s".', + 'Predefined Email Subjects' => 'Oggetti email predefiniti', + 'Write one subject by line.' => 'Scrivi un oggetto per riga.', + 'Create another link' => 'Crea un altro link', + 'BRL - Brazilian Real' => 'BRL - Real Brasiliano', + 'Add a new Kanboard task' => 'Crea un nuovo compito Kanboard', + 'Subtask not started' => 'Sotto-compito non iniziato', + 'Subtask currently in progress' => 'Sotto-compito attualmente in corso', + 'Subtask completed' => 'Sotto-compito completato', + 'Subtask added successfully.' => 'Sotto-compito aggiunto con successo.', + '%d subtasks added successfully.' => '%d sotto-compiti aggiunti con successo.', + 'Enter one subtask by line.' => 'Inserisci un sotto-compito per riga.', + 'Predefined Contents' => 'Contenuti Predefiniti', + 'Predefined contents' => 'Contenuti predefiniti', + 'Predefined Task Description' => 'Descrizione predefinita del compito', + 'Do you really want to remove this template? "%s"' => 'Vuoi eliminare veramente questo template? "%s"', + 'Add predefined task description' => 'Aggiungi una descrizione predefinita del compito', + 'Predefined Task Descriptions' => 'Descrizioni predefinite dei compito', + 'Template created successfully.' => 'Il template è stato creato correttamente.', + 'Unable to create this template.' => 'Non è stato possibile creare questo template.', + 'Template updated successfully.' => 'Template aggiornato con successo.', + 'Unable to update this template.' => 'Non è stato possibile aggiornare questo template.', + 'Template removed successfully.' => 'Template eliminato con successo.', + 'Unable to remove this template.' => 'Non è stato possibile eliminare questo template.', + 'Template for the task description' => 'Template per la descrizione del compito', + 'The start date is greater than the end date' => 'La data di inizio è superiore alla data di fine', + 'Tags must be separated by a comma' => 'I tag devono essere separati da una virgola', + 'Only the task title is required' => 'Solo il titolo del compito è obbligatorio', + 'Creator Username' => 'Nome utente del creatore', + 'Color Name' => 'Nome del colore', + 'Column Name' => 'Nome della colonna', + 'Swimlane Name' => 'Nome della corsia', + 'Time Estimated' => 'Tempo stimato', + 'Time Spent' => 'Tempo effettivo', + 'External Link' => 'Link esterno', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Questa funzionalità abilita il feed iCal, il feed RSS e la visualizzazione pubblica della dashboard', + 'Stop the timer of all subtasks when moving a task to another column' => 'Interrompi il timer di tutti i sotto-compiti quando un compito viene spostato in un\'altra colonna', + 'Subtask Title' => 'Titolo del sotto-compito', + 'Add a subtask and activate the timer when moving a task to another column' => 'Aggiungi un sotto-compito e attiva il timer quando un compito viene spostato in un\'altra colonna', + 'days' => 'giorni', + 'minutes' => 'minuti', + 'seconds' => 'secondi', + 'Assign automatically a color when preset start date is reached' => 'Assegna automaticamente un colore quando la data di inzio prestabilita viene raggiunta', + 'Move the task to another column once a predefined start date is reached' => 'Sposta il compito in un\'altra colonna quando si raggiunge una data predefinita', + 'This task is now linked to the task %s with the relation "%s"' => 'Questo compito è ora collegato al compito %s tramite la relazione "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'Il collegamento con la relazione "%s" al compito %s è stato rimosso', + 'Custom Filter:' => 'Filtro personalizzato', + 'Unable to find this group.' => 'Non è stato possibile trovare questo gruppo', + '%s moved the task #%d to the column "%s"' => '%s ha spostato il compito #%d nella colonna "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => '%s ha spostato il compito #%d a posizione %d nella colonna "%s"', + '%s moved the task #%d to the swimlane "%s"' => '%s ha spostato il compito #%d nella corsia "%s"', + '%sh spent' => '%sh effettive', + '%sh estimated' => '%sh stimate', + 'Select All' => 'Seleziona tutti', + 'Unselect All' => 'Deseleziona tutti', + 'Apply action' => 'Applica azione', + 'Move selected tasks to another column or swimlane' => 'Sposta i compiti selezionati in un\'altra colonna', + 'Edit tasks in bulk' => 'Modifica i compiti massivamente', + 'Choose the properties that you would like to change for the selected tasks.' => 'Scegli le proprietà che vorresti modificare per i compiti selezionato.', + 'Configure this project' => 'Configura questo progetto', + 'Start now' => 'Inizia ora', + '%s removed a file from the task #%d' => '%s ha rimosso un file dal compito #%d', + 'Attachment removed from task #%d: %s' => 'Allegato rimosso dal compito #%d: %s', + 'No color' => 'Nessun colore', + 'Attachment removed "%s"' => 'Allegato rimosso "%s"', + '%s removed a file from the task %s' => '%s ha rimosso un file dal compito %s', + 'Move the task to another swimlane when assigned to a user' => 'Sposta il compito in un\'altra corsia quando viene assegnato ad un utente', + 'Destination swimlane' => 'Corsia di destinazione', + 'Assign a category when the task is moved to a specific swimlane' => 'Assegna una categoria quando il compito viene spostato in una corsia specifica', + 'Move the task to another swimlane when the category is changed' => 'Sposta il compito in un\'altra corsia quando la categoria viene modificata', + 'Reorder this column by priority (ASC)' => 'Ordina questa colonna per priorità (ASC)', + 'Reorder this column by priority (DESC)' => 'Ordina questa colonna per priorità (DESC)', + 'Reorder this column by assignee and priority (ASC)' => 'Ordina questa colonna per assegnatario e per priorità (ASC)', + 'Reorder this column by assignee and priority (DESC)' => 'Ordina questa colonna per assegnatario e per priorità (DESC)', + 'Reorder this column by assignee (A-Z)' => 'Ordina questa colonna per assegnatario (ASC)', + 'Reorder this column by assignee (Z-A)' => 'Ordina questa colonna per assegnatario (DESC)', + 'Reorder this column by due date (ASC)' => 'Riordina questa colonna per dat di scadenza (ASCENDENTE)', + 'Reorder this column by due date (DESC)' => 'Riordina questa colonna per dat di scadenza (DISCENDENTE)', + 'Reorder this column by id (ASC)' => 'Riordina questa colonna per ID (ASC)', + 'Reorder this column by id (DESC)' => 'Riordina questa colonna per ID (DESC)', + '%s moved the task #%d "%s" to the project "%s"' => '%s ha spostato il compito #%d "%s" al progetto "%s"', + 'Task #%d "%s" has been moved to the project "%s"' => 'Il compito #%d "%s"è stato spostato al progetto "%s"', + 'Move the task to another column when the due date is less than a certain number of days' => 'Sposta il compito ad un\'altra colonna se la dat di scadenza è più vicina di un certo numero di giorni', + 'Automatically update the start date when the task is moved away from a specific column' => 'Aggiorna automaticamente la data di inizio quando il compito è tolto da una colonna specifica', + 'HTTP Client:' => 'Client HTTP', + 'Assigned' => 'Assegnato', + 'Task limits apply to each swimlane individually' => 'I limiti di compito si applicano individualmente ad ogni corsia', + 'Column task limits apply to each swimlane individually' => 'I limiti di compito per colonna si applicano individualmente ad ogni corsia', + 'Column task limits are applied to each swimlane individually' => 'I limiti di compito per colonna vengono applicati individualmente ad ogni corsia', + 'Column task limits are applied across swimlanes' => 'I limiti di compito per colonna vengono applicati a tutte le corsie', + 'Task limit: ' => 'Limite di compito', + 'Change to global tag' => 'Modifica all\'etichetta globale', + 'Do you really want to make the tag "%s" global?' => 'Vuoi davvero rendere globale l\'etichetta "%s"?', + 'Enable global tags for this project' => 'Abilita le etichette globali per questo progetto', + 'Group membership(s):' => 'Assegnazioni di gruppo', + '%s is a member of the following group(s): %s' => '%s è membro dei gruppi seguenti: %s', + '%d/%d group(s) shown' => '%d/%d gruppi mostrati', + 'Subtask creation or modification' => 'Creazione o modifica sotto-compiti', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Assegna il compito ad un utente specifico quando il compito viene spostato ad una corsia specifica', + 'Comment' => 'Commento', + 'Collapse vertically' => 'Riduci verticalmente', + 'Expand vertically' => 'Espandi verticalmente', + 'MXN - Mexican Peso' => 'MXN - Peso messicano', + 'Estimated vs actual time per column' => 'Tempo stimato vs reale per colonna', + 'HUF - Hungarian Forint' => 'HUF - Fiorino ungherese', + 'XBT - Bitcoin' => 'XBT - Bitcoin', + 'You must select a file to upload as your avatar!' => 'Devi selezionare un file da caricare come avatar!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'Il file caricato non è un\'immagine valida! (Sono consentiti solo *.gif, *.jpg, *.jpeg e *.png!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Imposta automaticamente la data di scadenza quando il task viene spostato da una colonna specifica', + 'No other projects found.' => 'Nessun altro progetto trovato.', + 'Tasks copied successfully.' => 'Attività copiate con successo.', + 'Unable to copy tasks.' => 'Impossibile copiare le attività.', + 'Theme' => 'Tema', + 'Theme:' => 'Tema:', + 'Light theme' => 'Tema chiaro', + 'Dark theme' => 'Tema scuro', + 'Automatic theme - Sync with system' => 'Tema automatico - Sincronizza con il sistema', + 'Application managers or more' => 'Gestori dell\'applicazione o più', + 'Administrators' => 'Amministratori', + 'Visibility:' => 'Visibilità:', + 'Standard users' => 'Utenti standard', + 'Visibility is required' => 'La visibilità è obbligatoria', + 'The visibility should be an app role' => 'La visibilità deve essere un ruolo dell\'applicazione', + 'Reply' => 'Rispondi', + '%s wrote: ' => '%s ha scritto: ', + 'Number of visible tasks in this column and swimlane' => 'Numero di attività visibili in questa colonna e swimlane', + 'Number of tasks in this swimlane' => 'Numero di attività in questa swimlane', + 'Unable to find another subtask in progress, you can close this window.' => 'Impossibile trovare un\'altra sotto-attività in corso, puoi chiudere questa finestra.', + 'This theme is invalid' => 'Questo tema non è valido', + 'This role is invalid' => 'Questo ruolo non è valido', + 'This timezone is invalid' => 'Questo fuso orario non è valido', + 'This language is invalid' => 'Questa lingua non è valida', + 'This URL is invalid' => 'Questo URL non è valido', + 'Date format invalid' => 'Formato data non valido', + 'Time format invalid' => 'Formato ora non valido', + 'Invalid Mail transport' => 'Trasporto email non valido', + 'Color invalid' => 'Colore non valido', + 'This value must be greater or equal to %d' => 'Questo valore deve essere maggiore o uguale a %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Aggiungi un BOM all\'inizio del file (richiesto per Microsoft Excel)', + 'Just add these tag(s)' => 'Aggiungi solo queste etichette', + 'Remove internal link(s)' => 'Rimuovi i collegamenti interni', + 'Import tasks from another project' => 'Importa attività da un altro progetto', + 'Select the project to copy tasks from' => 'Seleziona il progetto da cui copiare le attività', + 'The total maximum allowed attachments size is %sB.' => 'La dimensione massima totale consentita per gli allegati è %sB.', + 'Add attachments' => 'Aggiungi allegati', + 'Task #%d "%s" is overdue' => 'Il compito #%d "%s" è scaduto', + 'Enable notifications by default for all new users' => 'Abilita le notifiche per impostazione predefinita per tutti i nuovi utenti', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Assegna l\'attività al suo creatore per colonne specifiche se non è stato impostato manualmente alcun assegnatario', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Assegna un\'attività all\'utente connesso al cambio di colonna verso la colonna specificata se nessun utente è assegnato', +]; diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php new file mode 100644 index 0000000..c3614b1 --- /dev/null +++ b/app/Locale/ja_JP/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => '.', + 'number.thousands_separator' => ',', + 'None' => 'なし', + 'Edit' => '編集', + 'Remove' => '削除', + 'Yes' => 'はい', + 'No' => 'いいえ', + 'cancel' => 'キャンセル', + 'or' => 'または', + 'Yellow' => 'イエロー', + 'Blue' => 'ブルー', + 'Green' => 'グリーン', + 'Purple' => 'パープル', + 'Red' => 'レッド', + 'Orange' => 'オレンジ', + 'Grey' => 'グレー', + 'Brown' => 'ブラウン', + 'Deep Orange' => 'ディープオレンジ', + 'Dark Grey' => 'ダークグレー', + 'Pink' => 'ピンク', + 'Teal' => 'ティール', + 'Cyan' => 'シアン', + 'Lime' => 'ライム', + 'Light Green' => 'ライトグリーン', + 'Amber' => 'アンバー', + 'Save' => '保存', + 'Login' => 'ログイン', + 'Official website:' => '公式 Webサイト:', + 'Unassigned' => '担当なし', + 'View this task' => 'このタスクを見る', + 'Remove user' => 'ユーザーの削除', + 'Do you really want to remove this user: "%s"?' => 'ユーザー「%s」を削除しますか?', + 'All users' => 'すべてのユーザー', + 'Username' => 'ユーザー名', + 'Password' => 'パスワード', + 'Administrator' => 'システム管理者', + 'Sign in' => 'ログイン', + 'Users' => 'ユーザー', + 'Forbidden' => 'アクセス拒否', + 'Access Forbidden' => 'アクセスが拒否されました', + 'Edit user' => 'ユーザーを編集する', + 'Logout' => 'ログアウト', + 'Bad username or password' => 'ユーザー名またはパスワードが違います', + 'Edit project' => 'プロジェクトを編集', + 'Name' => '名前', + 'Projects' => 'プロジェクト', + 'No project' => 'プロジェクトがありません', + 'Project' => 'プロジェクト', + 'Status' => 'ステータス', + 'Tasks' => 'タスク', + 'Board' => 'ボード', + 'Actions' => 'アクション', + 'Inactive' => '無効', + 'Active' => '有効', + 'Unable to update this board.' => 'ボードを更新できませんでした', + 'Disable' => '無効にする', + 'Enable' => '有効にする', + 'New project' => 'プロジェクト作成', + 'Do you really want to remove this project: "%s"?' => 'プロジェクト「%s」を削除しますか?', + 'Remove project' => 'プロジェクトの削除', + 'Edit the board for "%s"' => 'ボード「%s」を編集', + 'Add a new column' => 'カラムの追加', + 'Title' => 'タイトル', + 'Assigned to %s' => '%sが担当', + 'Remove a column' => 'カラムの削除', + 'Unable to remove this column.' => 'カラムを削除できませんでした', + 'Do you really want to remove this column: "%s"?' => 'カラム「%s」を削除しますか?', + 'Settings' => '設定', + 'Application settings' => 'アプリケーション設定', + 'Language' => '言語', + 'Webhook token:' => 'Webhook トークン:', + 'API token:' => 'API トークン:', + 'Database size:' => 'データベースのサイズ:', + 'Download the database' => 'データベースのダウンロード', + 'Optimize the database' => 'データベースの最適化', + '(VACUUM command)' => '(VACUUM コマンド)', + '(Gzip compressed Sqlite file)' => '(GZip コマンドで圧縮された Sqlite ファイル)', + 'Close a task' => 'タスクを完了', + 'Column' => 'カラム', + 'Color' => '色', + 'Assignee' => '担当', + 'Create another task' => '続けて別のタスクを追加', + 'New task' => 'タスクを追加', + 'Open a task' => 'タスクを開く', + 'Do you really want to open this task: "%s"?' => 'タスク「%s」を作成しますか?', + 'Back to the board' => 'ボードに戻る', + 'There is nobody assigned' => '担当者がいません', + 'Column on the board:' => 'カラム:', + 'Close this task' => 'タスクを完了', + 'Open this task' => 'このタスクを開く', + 'There is no description.' => '説明がありません', + 'Add a new task' => 'タスクを追加', + 'The username is required' => 'ユーザー名が必要です', + 'The maximum length is %d characters' => '最大 %d 文字です', + 'The minimum length is %d characters' => '最小 %d 文字必要です', + 'The password is required' => 'パスワードが必要です', + 'This value must be an integer' => '整数で入力してください', + 'The username must be unique' => 'ユーザー名がすでに使用されています', + 'The user id is required' => 'ユーザー ID が必要です', + 'Passwords don\'t match' => 'パスワードが一致しません', + 'The confirmation is required' => '確認用のパスワードを入力してください', + 'The project is required' => 'プロジェクトが必要です', + 'The id is required' => 'ID が必要です', + 'The project id is required' => 'プロジェクト ID が必要です', + 'The project name is required' => 'プロジェクト名が必要です', + 'The title is required' => 'タイトルが必要です', + 'Settings saved successfully.' => '設定を保存しました', + 'Unable to save your settings.' => '設定の保存に失敗しました', + 'Database optimization done.' => 'データベースの最適化が終わりました', + 'Your project has been created successfully.' => 'プロジェクトを作成しました', + 'Unable to create your project.' => 'プロジェクトの作成に失敗しました', + 'Project updated successfully.' => 'プロジェクトを更新しました', + 'Unable to update this project.' => 'プロジェクトの更新に失敗しました', + 'Unable to remove this project.' => 'プロジェクトの削除に失敗しました', + 'Project removed successfully.' => 'プロジェクトを削除しました', + 'Project activated successfully.' => 'プロジェクトを有効にしました', + 'Unable to activate 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.' => 'タスクの更新に失敗しました', + 'Task updated successfully.' => 'タスクを更新しました', + 'Unable to create your task.' => 'タスクの追加に失敗しました', + 'Task created successfully.' => 'タスクを追加しました', + 'User created successfully.' => 'ユーザーを追加しました', + 'Unable to create your user.' => 'ユーザーの追加に失敗しました', + 'User updated successfully.' => 'ユーザーを更新しました', + 'User removed successfully.' => 'ユーザーを削除しました', + 'Unable to remove this user.' => 'ユーザーの削除に失敗しました', + 'Board updated successfully.' => 'ボードを更新しました', + 'Ready' => 'Ready', + 'Backlog' => 'Backlog', + 'Work in progress' => 'Work in progress', + 'Done' => 'Done', + 'Application version:' => 'アプリケーションのバージョン:', + 'Id' => 'ID', + 'Public link' => '公開アクセス用リンク', + 'Timezone' => 'タイムゾーン', + 'Sorry, I didn\'t find this information in my database!' => 'データベース上で情報が見つかりませんでした!', + 'Page not found' => 'ページが見つかりません', + 'Complexity' => '複雑さ', + 'Task limit' => 'タスク数制限', + 'Task count' => 'タスク数', + 'User' => 'ユーザー', + 'Comments' => 'コメント', + 'Comment is required' => 'コメントを入力してください', + 'Comment added successfully.' => 'コメントを追加しました', + 'Unable to create your comment.' => 'コメントの追加に失敗しました', + 'Due Date' => '期限', + 'Invalid date' => '日付が無効です', + 'Automatic actions' => '自動アクションの管理', + 'Your automatic action has been created successfully.' => '自動アクションを作成しました', + 'Unable to create your automatic action.' => '自動アクションの作成に失敗しました', + 'Remove an action' => '自動アクションの削除', + 'Unable to remove this action.' => '自動アクションの削除に失敗しました', + 'Action removed successfully.' => '自動アクションの削除に成功しました', + 'Automatic actions for the project "%s"' => 'プロジェクト「%s」の自動アクション', + 'Add an action' => '自動アクションの追加', + 'Event name' => 'イベント名', + 'Action' => 'アクション', + 'Event' => 'イベント', + 'When the selected event occurs execute the corresponding action.' => '選択されたイベントが発生した時、対応するアクションを実行', + 'Next step' => '次のステップ', + 'Define action parameters' => 'アクションのパラメーター', + 'Do you really want to remove this action: "%s"?' => '自動アクション「%s」を削除しますか?', + 'Remove an automatic action' => '自動アクションの削除', + 'Assign the task to a specific user' => 'タスクの担当者を割当てる', + 'Assign the task to the person who does the action' => 'アクションを起こしたユーザーを担当者にする', + 'Duplicate the task to another project' => '別のプロジェクトにタスクを複製', + 'Move a task to another column' => 'タスクを別のカラムに移動', + 'Task modification' => 'タスクの変更', + 'Task creation' => 'タスクを作る', + 'Closing a task' => 'タスクを完了', + 'Assign a color to a specific user' => '色をユーザーに割当てる', + 'Position' => '位置', + 'Duplicate to project' => '別プロジェクトに複製', + 'Duplicate' => '複製', + 'Link' => 'リンク', + 'Comment updated successfully.' => 'コメントを更新しました', + 'Unable to update your comment.' => 'コメントの更新に失敗しました', + 'Remove a comment' => 'コメントを削除', + 'Comment removed successfully.' => 'コメントを削除しました', + 'Unable to remove this comment.' => 'コメントの削除に失敗しました', + 'Do you really want to remove this comment?' => 'コメントを削除しますか?', + 'Current password for the user "%s"' => 'ユーザー「%s」の現在のパスワード', + 'The current password is required' => '現在のパスワードを入力してください', + 'Wrong password' => 'パスワードが違います', + 'Unknown' => '不明', + 'Last logins' => 'ログインの一覧', + 'Login date' => 'ログイン日時', + 'Authentication method' => '認証方法', + 'IP address' => 'IP アドレス', + 'User agent' => 'ユーザーエージェント', + 'Persistent connections' => '既存のコネクション', + 'No session.' => 'セッションなし', + 'Expiration date' => '有効期限', + 'Remember Me' => '次回から自動的にログイン', + 'Creation date' => '作成日', + 'Everybody' => '全員', + 'Open' => '作成', + 'Closed' => '完了', + 'Search' => '検索', + 'Nothing found.' => '結果なし', + 'Due date' => '期限', + 'Description' => '説明', + '%d comments' => '%d 個のコメント', + '%d comment' => '%d 個のコメント', + 'Email address invalid' => 'メールアドレスが正しくありません', + 'Your external account is not linked anymore to your profile.' => 'あなたの外部アカウントはプロフィールにリンクされていません', + 'Unable to unlink your external account.' => '外部アカウントのリンクを解除できません', + 'External authentication failed' => '外部認証に失敗しました', + 'Your external account is linked to your profile successfully.' => 'あなたの外部アカウントはプロフィールに正常にリンクしています', + 'Email' => 'Email', + 'Task removed successfully.' => 'タスクを削除しました', + 'Unable to remove this task.' => 'タスクの削除に失敗しました', + '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' => '色に基いてカテゴリを変える', + 'Task creation or modification' => 'タスクの作成または変更', + 'Category' => 'カテゴリ', + 'Category:' => 'カテゴリ:', + 'Categories' => 'カテゴリ', + 'Your category has been created successfully.' => 'カテゴリを作成しました', + 'This category has been updated successfully.' => 'カテゴリを更新しました', + 'Unable to update this category.' => 'カテゴリの更新に失敗しました', + 'Remove a category' => 'カテゴリの削除', + 'Category removed successfully.' => 'カテゴリを削除しました', + 'Unable to remove this category.' => 'カテゴリを削除できませんでした', + 'Category modification for the project "%s"' => 'プロジェクト「%s」のカテゴリの編集', + 'Category Name' => 'カテゴリ名', + 'Add a new category' => 'カテゴリの追加', + 'Do you really want to remove this category: "%s"?' => 'カテゴリ「%s」を削除しますか?', + 'All categories' => 'すべてのカテゴリ', + 'No category' => 'カテゴリなし', + 'The name is required' => '名前を入力してください', + 'Remove a file' => 'ファイルの削除', + 'Unable to remove this file.' => 'ファイルの削除に失敗しました', + 'File removed successfully.' => 'ファイルを削除しました', + 'Attach a document' => 'ドキュメントを添付', + 'Do you really want to remove this file: "%s"?' => 'ファイル「%s」を削除しますか?', + 'Attachments' => '添付', + 'Edit the task' => 'タスクを編集', + 'Add a comment' => 'コメントを追加', + 'Edit a comment' => 'コメントを編集', + 'Summary' => '要約', + 'Time tracking' => '時間の追跡', + 'Estimate:' => '見積:', + 'Spent:' => '経過:', + 'Do you really want to remove this sub-task?' => 'サブタスクを削除しますか?', + 'Remaining:' => '残:', + 'hours' => '時間', + 'estimated' => '見積', + 'Sub-Tasks' => 'サブタスク', + 'Add a sub-task' => 'サブタスクを追加', + 'Original estimate' => '初期の見積', + 'Create another sub-task' => '続けて別のサブタスクを追加', + 'Time spent' => '経過時間', + 'Edit a sub-task' => 'サブタスクを編集', + 'Remove a sub-task' => 'サブタスクを削除', + 'The time must be a numeric value' => '時間は数字で入力してください', + 'Todo' => '作業予定', + 'In progress' => '作業中', + 'Sub-task removed successfully.' => 'サブタスクを削除しました', + 'Unable to remove this sub-task.' => 'サブタスクの削除に失敗しました', + 'Sub-task updated successfully.' => 'サブタスクを更新しました', + 'Unable to update your sub-task.' => 'サブタスクの更新に失敗しました', + 'Unable to create your sub-task.' => 'サブタスクの追加に失敗しました', + 'Maximum size: ' => '最大:', + 'Display another project' => '別のプロジェクトを表示', + 'Created by %s' => '%s が作成', + 'Tasks Export' => 'タスクの出力', + 'Start Date' => '開始日', + 'Execute' => '実行', + 'Task Id' => 'タスク ID', + 'Creator' => '作成者', + 'Modification date' => '更新日', + 'Completion date' => '完了日', + 'Clone' => '複製', + 'Project cloned successfully.' => 'プロジェクトを複製しました', + 'Unable to clone this project.' => 'プロジェクトの複製に失敗しました', + 'Enable email notifications' => 'メール通知を設定', + 'Task position:' => 'タスクの位置:', + 'The task #%d has been opened.' => 'タスク #%d を作成しました', + 'The task #%d has been closed.' => 'タスク #%d を完了しました', + 'Sub-task updated' => 'サブタスクの更新', + 'Title:' => 'タイトル:', + 'Status:' => 'ステータス:', + 'Assignee:' => '担当:', + 'Time tracking:' => '時間計測:', + 'New sub-task' => '新しいサブタスク', + 'New attachment added "%s"' => '添付ファイル「%s」が追加されました', + 'New comment posted by %s' => '「%s」の新しいコメントが追加されました', + 'New comment' => '新しいコメント', + 'Comment updated' => 'コメントが更新されました', + 'New subtask' => '新しいサブタスク', + 'I only want to receive notifications for these projects:' => '以下のプロジェクトにのみ通知を受け取る:', + 'view the task on Kanboard' => 'Kanboard でタスクを見る', + 'Public access' => '公開アクセス', + 'Disable public access' => '公開アクセスを無効にする', + 'Enable public access' => '公開アクセスを有効にする', + 'Public access disabled' => '公開アクセスは無効化されています', + 'Move the task to another project' => 'タスクを別プロジェクトに移動', + 'Move to project' => '別プロジェクトに移動', + 'Do you really want to duplicate this task?' => 'タスクを複製しますか?', + 'Duplicate a task' => 'タスクの複製', + 'External accounts' => '外部アカウント', + 'Account type' => 'アカウントの種類', + 'Local' => 'ローカル', + 'Remote' => 'リモート', + 'Enabled' => '有効', + 'Disabled' => '無効', + 'Login:' => 'ユーザー名:', + 'Full Name:' => '名前:', + 'Email:' => 'Email:', + 'Notifications:' => '通知:', + 'Notifications' => '通知', + 'Account type:' => 'アカウントの種類:', + 'Edit profile' => 'プロフィールの編集', + 'Change password' => 'パスワードの変更', + 'Password modification' => 'パスワードの変更', + 'External authentications' => '外部認証', + 'Never connected.' => '未接続', + 'No external authentication enabled.' => '外部認証が設定されていません', + 'Password modified successfully.' => 'パスワードを変更しました', + 'Unable to change the password.' => 'パスワードを変更できませんでした', + 'Change category' => 'カテゴリの変更', + '%s updated the task %s' => '%s がタスク %s を更新しました', + '%s opened the task %s' => '%s がタスク %s を作成しました', + '%s moved the task %s to the position #%d in the column "%s"' => '%s がタスク %s をポジション #%d カラム %s に移動しました', + '%s moved the task %s to the column "%s"' => '%s がタスク %s をカラム「%s」に移動しました', + '%s created the task %s' => '%s がタスク %s を作成しました', + '%s closed the task %s' => '%s がタスク %s を完了しました', + '%s created a subtask for the task %s' => '%s がタスク %s のサブタスクを追加しました', + '%s updated a subtask for the task %s' => '%s がタスク %s のサブタスクを更新しました', + 'Assigned to %s with an estimate of %s/%sh' => '担当者 %s は見積を %s/%s時間 に変更しました', + 'Not assigned, estimate of %sh' => '担当者無しで見積を %s時間 に変更しました', + '%s updated a comment on the task %s' => '%s がタスク %s のコメントを更新しました', + '%s commented the task %s' => '%s がタスク %s にコメントしました', + '%s\'s activity' => '%s の活動状況', + 'RSS feed' => 'RSS フィード', + '%s updated a comment 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 を更新しました', + '%s created the task #%d' => '%s がタスク #%d を追加しました', + '%s closed the task #%d' => '%s がタスク #%d を完了しました', + '%s opened the task #%d' => '%s がタスク #%d を作成しました', + 'Activity' => '活動状況', + 'Default values are "%s"' => '既定値は「%s」', + 'Default columns for new projects (Comma-separated)' => '新規プロジェクトの既定カラム (コンマで区切って入力)', + 'Task assignee change' => '担当者の変更', + '%s changed the assignee of the task #%d to %s' => '%s がタスク #%d の担当を %s に変更しました', + '%s changed the assignee of the task %s to %s' => '%s がタスク %s の担当を %s に変更しました', + 'New password for the user "%s"' => 'ユーザー「%s」の新しいパスワード', + 'Choose an event' => 'イベントの選択', + 'Create a task from an external provider' => 'タスクを外部サービスから作成', + 'Change the assignee based on an external username' => '担当者を外部サービスに基いて変更', + 'Change the category based on an external label' => 'カテゴリを外部サービスに基いて変更', + 'Reference' => '参照', + 'Label' => 'ラベル', + 'Database' => 'データベース', + 'About' => '情報', + 'Database driver:' => 'データベースドライバ:', + 'Board settings' => '基本設定', + 'Webhook settings' => 'Webhook の設定', + 'Reset token' => 'トークンのリセット', + 'API endpoint:' => 'API エンドポイント:', + 'Refresh interval for personal board' => '非公開ボードの更新頻度', + 'Refresh interval for public board' => '公開ボードの更新頻度', + 'Task highlight period' => 'タスクのハイライト期間', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'タスクが最近更新されたとみなす期間(0 はハイライト無効、既定 2 日)', + 'Frequency in second (60 seconds by default)' => '秒数 (既定 60 秒)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => '秒数 (0 は機能を無効化、既定 10 秒)', + 'Application URL' => 'アプリケーションの URL', + 'Token regenerated.' => 'トークンが再生成されました', + 'Date format' => 'データのフォーマット', + 'ISO format is always accepted, example: "%s" and "%s"' => 'ISO フォーマットが入力できます(例: %s または %s)', + 'New personal project' => '非公開プロジェクト作成', + 'This project is personal' => 'このプロジェクトは非公開です', + 'Add' => '追加', + 'Start date' => '開始時間', + 'Time estimated' => '見積時間', + 'There is nothing assigned to you.' => '何もアサインされていません', + 'My tasks' => '自分のタスク', + 'Activity stream' => '活動状況', + 'Dashboard' => 'ダッシュボード', + 'Confirmation' => '確認', + 'Webhooks' => 'Webhook', + 'API' => 'API', + 'Create a comment from an external provider' => '外部サービスからコメントを作成', + 'Project management' => 'プロジェクト管理', + 'Columns' => 'カラム', + 'Task' => 'タスク', + 'Percentage' => '割合', + 'Number of tasks' => 'タスク数', + 'Task distribution' => 'タスク分布', + 'Analytics' => '分析', + 'Subtask' => 'サブタスク', + 'User repartition' => '担当者分布', + 'Clone this project' => 'このプロジェクトを複製', + 'Column removed successfully.' => 'カラムを削除しました', + 'Not enough data to show the graph.' => 'グラフを描画するためのデータが不足しています', + 'Previous' => '戻る', + 'The id must be an integer' => 'id は数字でなければなりません', + 'The project id must be an integer' => 'project id は数字でなければなりません', + 'The status must be an integer' => 'status は数字でなければなりません', + 'The subtask id is required' => 'subtask id が必要です', + 'The subtask id must be an integer' => 'subtask id は数字でなければなりません', + 'The task id is required' => 'task id が必要です', + 'The task id must be an integer' => 'task id は数字でなければなりません', + 'The user id must be an integer' => 'user id は数字でなければなりません', + 'This value is required' => 'この値が必要です', + 'This value must be numeric' => 'この値は数字でなければなりません', + 'Unable to create this task.' => 'このタスクを作成できませんでした', + 'Cumulative flow diagram' => '累積フロー図', + 'Daily project summary' => '日時プロジェクトサマリー', + 'Daily project summary export' => '日時プロジェクトサマリーの出力', + 'Exports' => '出力', + 'This export contains the number of tasks per column grouped per day.' => 'この出力は日時のカラムごとのタスク数を集計したものです', + 'Active swimlanes' => 'アクティブなスイムレーン', + 'Add a new swimlane' => '新しいスイムレーン', + 'Default swimlane' => '既定スイムレーン', + 'Do you really want to remove this swimlane: "%s"?' => 'スイムレーン「%s」を削除しますか?', + 'Inactive swimlanes' => '無効なスイムレーン', + 'Remove a swimlane' => 'スイムレーンの削除', + 'Swimlane modification for the project "%s"' => '「%s」に対するスイムレーン変更', + 'Swimlane removed successfully.' => 'スイムレーンを削除しました', + 'Swimlanes' => 'スイムレーン', + 'Swimlane updated successfully.' => 'スイムレーンを更新しました', + 'Unable to remove this swimlane.' => 'スイムレーンを削除できませんでした', + 'Unable to update this swimlane.' => 'スイムレーンを更新できませんでした', + 'Your swimlane has been created successfully.' => 'スイムレーンが作成されました', + 'Example: "Bug, Feature Request, Improvement"' => '例: バグ, 機能, 改善', + 'Default categories for new projects (Comma-separated)' => '新しいプロジェクトの既定カテゴリー (コンマ区切り)', + 'Integrations' => '連携', + 'Integration with third-party services' => 'サードパーティサービスとの連携', + 'Subtask Id' => 'サブタスク Id', + 'Subtasks' => 'サブタスク', + 'Subtasks Export' => 'サブタスクの出力', + 'Task Title' => 'タスクタイトル', + 'Untitled' => 'タイトル無し', + 'Application default' => 'アプリケーションの既定値', + 'Language:' => '言語:', + 'Timezone:' => 'タイムゾーン:', + 'All columns' => '全てのカラム', + 'Next' => '次へ', + '#%d' => '#%d', + 'All swimlanes' => '全てのスイムレーン', + 'All colors' => '全ての色', + 'Moved to column %s' => 'カラム %s へ移動しました', + 'User dashboard' => 'ユーザーダッシュボード', + 'Allow only one subtask in progress at the same time for a user' => '1ユーザーにつき1件のタスクのみを進行中にできるよう制限', + 'Edit column "%s"' => 'カラム「%s」の編集', + '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?' => 'プロジェクトの何を複製しますか?', + 'Disallow login form' => 'ログインフォームからのログインを許可しない', + 'Start' => '開始', + 'End' => '終了', + 'Task age in days' => 'タスクの経過日数', + 'Days in this column' => 'カラムでの経過日数', + '%dd' => '%d 日', + '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' => 'リンクの変更', + 'Opposite label' => '反対のラベル', + 'Remove a link' => 'ラベルの削除', + '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' => '<1時間', + '%dh' => '%d 時間', + 'Expand tasks' => 'タスクを詳細表示', + 'Collapse tasks' => 'タスクを簡略表示', + 'Expand/collapse tasks' => 'タスクの展開/閉じる', + 'Close dialog box' => 'ダイアログボックスを閉じる', + 'Submit a form' => 'フォームを送信', + 'Board view' => 'ボードビュー', + 'Keyboard shortcuts' => 'キーボードショートカット', + 'Open board switcher' => 'ボード切り替えを開く', + 'Application' => 'アプリケーション', + 'Compact view' => 'コンパクトビュー', + 'Horizontal scrolling' => '横スクロール', + 'Compact/wide view' => 'コンパクト/ワイドビュー', + 'Currency' => '通貨', + 'Personal project' => '非公開プロジェクト', + 'AUD - Australian Dollar' => 'AUD - 豪ドル', + 'CAD - Canadian Dollar' => 'CAD - 加ドル', + 'CHF - Swiss Francs' => 'CHF - スイスフラン', + 'Custom Stylesheet' => 'カスタムスタイルシート', + 'EUR - Euro' => 'EUR - ユーロ', + 'GBP - British Pound' => 'GBP - 独ポンド', + 'INR - Indian Rupee' => 'INR - 伊ルピー', + 'JPY - Japanese Yen' => 'JPY - 日本円', + 'NZD - New Zealand Dollar' => 'NZD - NZ ドル', + 'PEN - Peruvian Sol' => 'PEN - ペルー・ソル', + 'RSD - Serbian dinar' => 'RSD - セルビアデナール', + 'CNY - Chinese Yuan' => 'CNY - 中国元', + 'USD - US Dollar' => 'USD - 米ドル', + 'VES - Venezuelan Bolívar' => 'VES - ベネズエラ・ボリバル', + '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' => '移動元のカラム', + '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' => '現在の基軸通貨', + 'Reference currency' => '基軸通貨', + 'The currency rate has been added successfully.' => '通貨レートが正常に追加されました', + 'Unable to add this currency rate.' => 'この通貨レートを追加できません', + 'Webhook URL' => 'Webhook URL', + '%s removed the assignee of the task %s' => '%s がタスク「%s」の担当を解除しました', + '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' => '二要素認証', + 'This QR code contains the key URI: ' => 'この QR コードが URI キーを含んでいます:', + 'Check my code' => '自分のコードをチェック', + 'Secret key: ' => '秘密鍵:', + 'Test your device' => 'あなたのデバイスをテスト', + 'Assign a color when the task is moved to a specific column' => 'タスクが特定のカラムに移動した時の色を設定', + '%s via Kanboard' => '%s by Kanboard', + 'Burndown chart' => 'バーンダウンチャート', + 'This chart show the task complexity over the time (Work Remaining).' => 'グラフは、時間の経過とともにタスクの複雑さを示しています(残っている作業)', + 'Screenshot taken %s' => 'スクリーンショット %s', + 'Add a screenshot' => 'スクリーンショットを追加', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'スクリーンショットを撮りCTRL + Vまたは⌘+ Vを押してここに貼り付けてください', + 'Screenshot uploaded successfully.' => 'スクリーンショットが正常にアップロードされました', + 'SEK - Swedish Krona' => 'SEK - スウェーデン クローナ', + 'Identifier' => '識別子', + 'Disable two factor authentication' => '二要素認証を無効にする', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => '本当にユーザー「%s」の二要素認証を無効にしますか?', + 'Edit link' => 'リンクを編集', + 'Start to type task title...' => 'タスクタイトルを入力...', + 'A task cannot be linked to itself' => 'タスク自身へのリンクはできません', + 'The exact same link already exists' => '同じリンクが既に存在しています', + 'Recurrent task is scheduled to be generated' => '反復タスクが生成されるようにスケジュールされています', + 'Score' => 'スコア', + 'The identifier must be unique' => '識別子はユニークでなければなりません', + 'This linked task id doesn\'t exists' => 'リンクされたタスクIDは存在しません', + 'This value must be alphanumeric' => '英数字で入力ください', + 'Edit recurrence' => '反復タスクの編集', + 'Generate recurrent task' => '反復タスクを生成', + 'Trigger to generate recurrent task' => '反復タスクを生成するためのトリガ', + 'Factor to calculate new due date' => '新しい期限を計算する要素', + 'Timeframe to calculate new due date' => '新しい期限を計算するための時間枠', + 'Base date to calculate new due date' => '新しい期限を計算するための基準日', + 'Action date' => '実行日', + 'Base date to calculate new due date: ' => '新しい期限を計算するための基準日:', + 'This task has created this child task: ' => 'このタスクはこの子タスクを作成しました:', + 'Day(s)' => '日', + 'Existing due date' => '既存の予定日', + 'Factor to calculate new due date: ' => '新しい期限を計算する要素:', + 'Month(s)' => 'ヶ月', + 'This task has been created by: ' => 'タスク作成者', + 'Recurrent task has been generated:' => '反復タスクが生成されました:', + 'Timeframe to calculate new due date: ' => '新しい期限を計算する時間枠:', + 'Trigger to generate recurrent task: ' => '反復タスクを生成するためのトリガー:', + 'When task is closed' => 'タスクが完了したとき', + 'When task is moved from first column' => 'タスクが最初のカラムから移動されたとき', + 'When task is moved to last column' => 'タスクが最後のカラムに移動したとき', + 'Year(s)' => '年', + 'Project settings' => 'プロジェクト設定', + 'Automatically update the start date' => '開始日を自動的に更新', + 'iCal feed' => 'iCal 配信', + 'Preferences' => 'プリファレンス', + 'Security' => 'セキュリティ', + 'Two factor authentication disabled' => '二要素認証は無効です', + 'Two factor authentication enabled' => '二要素認証は有効です', + 'Unable to update this user.' => 'このユーザーを更新できません', + 'There is no user management for personal projects.' => '非公開プロジェクトのためのユーザー管理はありません', + 'User that will receive the email' => 'メールを受信するユーザー', + 'Email subject' => 'メールの件名', + 'Date' => '日付', + 'Add a comment log when moving the task between columns' => 'カラム間でタスクを移動するときにコメントログを追加', + 'Move the task to another column when the category is changed' => 'カテゴリが変更されたときにタスクを別のカラムに移動', + 'Send a task by email to someone' => 'タスクをメールで送信', + 'Reopen a task' => 'タスクを再開', + 'Notification' => '通知', + '%s moved the task #%d to the first swimlane' => '%sはタスク#%dを最初のスイムレーンに移動しました', + 'Swimlane' => 'スイムレーン', + '%s moved the task %s to the first swimlane' => '%sはタスク%sを最初のスイムレーンに移動しました', + '%s moved the task %s to the swimlane "%s"' => '%sはタスク%sをスイムレーン "%s"に移動しました', + 'This report contains all subtasks information for the given date range.' => 'このレポートには、指定した期間のすべてのサブタスク情報が含まれています', + 'This report contains all tasks information for the given date range.' => 'このレポートには、指定した期間のすべてのタスク情報が含まれます', + 'Project activities for %s' => '%sのプロジェクトの活動状況', + 'view the board on Kanboard' => 'Kanboard上のボードを見る', + 'The task has been moved to the first swimlane' => 'タスクは最初のスイムレーンに移動されました', + 'The task has been moved to another swimlane:' => 'タスクは次のスイムレーンに移動しました:', + 'New title: %s' => '新しいタイトル:%s', + 'The task is not assigned anymore' => 'タスクはもう割当てられません', + 'New assignee: %s' => '新しい担当者:%s', + 'There is no category now' => 'カテゴリはありません', + 'New category: %s' => '新しいカテゴリ:%s', + 'New color: %s' => '新しい色:%s', + 'New complexity: %d' => '新しい複雑さ:%d', + 'The due date has been removed' => '期限が削除されました', + 'There is no description anymore' => '説明はありません', + 'Recurrence settings has been modified' => '反復設定が変更されました', + 'Time spent changed: %sh' => '経過時間が変更されました:%sh', + 'Time estimated changed: %sh' => '見積時間が変更されました:%sh', + 'The field "%s" has been updated' => 'フィールド "%s"は更新されました', + 'The description has been modified:' => '説明が変更されました:', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'タスク"%s"と、すべてのサブタスクを完了しますか?', + 'I want to receive notifications for:' => '次の通知を受信します:', + 'All tasks' => 'すべてのタスク', + 'Only for tasks assigned to me' => '自分のタスクのみ', + 'Only for tasks created by me' => '自分が作成したタスクのみ', + 'Only for tasks created by me and tasks assigned to me' => '自分が作成した自分のタスクのみ', + '%%Y-%%m-%%d' => '%%Y-%%m-%%d', + 'Total for all columns' => 'すべてのカラムの合計', + 'You need at least 2 days of data to show the chart.' => 'グラフを表示するには最低2日間のデータが必要です', + '<15m' => '15分未満', + '<30m' => '30分未満', + 'Stop timer' => 'タイマー停止', + 'Start timer' => 'タイマー開始', + 'My activity stream' => '自分の活動状況', + 'Search tasks' => 'タスクを検索', + 'Reset filters' => 'フィルターをリセット', + 'My tasks due tomorrow' => '明日の自分のタスク', + 'Tasks due today' => '今日のタスク', + 'Tasks due tomorrow' => '明日のタスク', + 'Tasks due yesterday' => '昨日のタスク', + 'Closed tasks' => '完了タスク', + 'Open tasks' => '未完了タスク', + 'Not assigned' => '未割当', + 'View advanced search syntax' => '詳細検索の構文を見る', + 'Overview' => '概要', + 'Board/Calendar/List view' => 'ボード/カレンダー/リストビュー', + 'Switch to the board view' => 'ボードビューに切替', + 'Switch to the list view' => 'リストビューに切替', + 'Go to the search/filter box' => '検索/フィルタボックスに移動', + 'There is no activity yet.' => 'まだ活動状況はありません', + 'No tasks found.' => 'タスクが見つかりません', + 'Keyboard shortcut: "%s"' => 'キーボード・ショートカット: "%s"', + 'List' => 'リスト', + 'Filter' => 'フィルター', + 'Advanced search' => '詳細検索', + 'Example of query: ' => 'クエリの例:', + 'Search by project: ' => 'プロジェクトで検索:', + 'Search by column: ' => 'カラムで検索:', + 'Search by assignee: ' => '担当者で検索:', + 'Search by color: ' => '色で検索:', + 'Search by category: ' => 'カテゴリで検索:', + 'Search by description: ' => '説明で検索:', + 'Search by due date: ' => '期限で検索:', + 'Average time spent in each column' => '各カラムで経過した時間の平均', + 'Average time spent' => '平均経過時間', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'このグラフは最後の%d個のタスクに対して各カラムで経過した平均時間を示しています', + 'Average Lead and Cycle time' => '平均リードタイム・平均サイクルタイム', + 'Average lead time: ' => '平均リードタイム:', + 'Average cycle time: ' => '平均サイクルタイム:', + 'Cycle Time' => 'サイクルタイム', + 'Lead Time' => 'リードタイム', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'このグラフは時間の経過に伴う最後の%d個のタスクの平均リードタイムとサイクルタイムを示します', + 'Average time into each column' => '各カラムの平均時間', + 'Lead and cycle time' => 'リード/サイクルタイム', + 'Lead time: ' => 'リードタイム:', + 'Cycle time: ' => 'サイクルタイム:', + 'Time spent in each column' => '各カラムの経過時間', + 'The lead time is the duration between the task creation and the completion.' => 'リードタイムはタスクの作成〜完了まで時間です', + 'The cycle time is the duration between the start date and the completion.' => 'サイクルタイムは着手〜完了までの期間です', + 'If the task is not closed the current time is used instead of the completion date.' => 'タスクが終了していない場合は完了日の代わりに現在時刻を使用します', + 'Set the start date automatically' => '開始日を自動的に設定', + 'Edit Authentication' => '認証の編集', + 'Remote user' => 'リモートユーザー', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'リモートユーザーはパスワードをKanboardデータベースに保存しません(例:LDAP、Google、Githubのアカウント)', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => '「ログインフォームを許可しない」チェックボックスをオンにすると、ログインフォームに入力された認証情報は無視されます', + 'Default task color' => '規定のタスクカラー', + 'This feature does not work with all browsers.' => 'この機能は一部のブラウザで動作しません', + 'There is no destination project available.' => '利用可能なプロジェクトはありません', + 'Trigger automatically subtask time tracking' => '自動的にサブタスク時間トラッキングを作動させる', + 'Include closed tasks in the cumulative flow diagram' => '累積フロー図に完了したタスクを含める', + 'Current swimlane: %s' => '現在のスイムレーン:%s', + 'Current column: %s' => '現在のカラム:%s', + 'Current category: %s' => '現在のカテゴリ:%s', + 'no category' => 'カテゴリなし', + 'Current assignee: %s' => '現在の担当者:%s', + 'not assigned' => '未割当', + 'Author:' => '著者:', + 'contributors' => '協力者', + 'License:' => 'ライセンス:', + 'License' => 'ライセンス', + 'Enter the text below' => 'テキストを入力', + 'Start date:' => '開始日:', + 'Due date:' => '期限:', + 'People who are project managers' => 'プロジェクト管理者', + 'People who are project members' => 'プロジェクトメンバー', + 'NOK - Norwegian Krone' => 'NOK - ノルウェークローネ', + 'Show this column' => 'このカラムを表示', + 'Hide this column' => 'このカラムを隠す', + 'End date' => '終了日', + 'Users overview' => 'ユーザー概要', + 'Members' => 'メンバー', + 'Shared project' => '共有プロジェクト', + 'Project managers' => 'プロジェクト管理者', + 'Projects list' => 'プロジェクトリスト', + 'End date:' => '終了日:', + 'Change task color when using a specific task link' => '特定のタスクリンクを使用するとタスクの色を変更', + 'Task link creation or modification' => 'タスクへのリンクの作成または変更', + 'Milestone' => 'マイルストーン', + 'Reset the search/filter box' => '検索/フィルタをリセット', + 'Documentation' => 'ドキュメント', + 'Author' => '著者', + 'Version' => 'バージョン', + 'Plugins' => 'プラグイン', + 'There is no plugin loaded.' => 'プラグインがロードされていません', + 'My notifications' => '自分の通知', + 'Custom filters' => 'カスタムフィルタ', + 'Your custom filter has been created successfully.' => 'カスタムフィルタを作成しました', + 'Unable to create your custom filter.' => 'カスタムフィルタを作成できません', + 'Custom filter removed successfully.' => 'カスタムフィルタを削除しました', + 'Unable to remove this custom filter.' => 'このカスタムフィルタは削除できません', + 'Edit custom filter' => 'カスタムフィルタを編集', + 'Your custom filter has been updated successfully.' => 'カスタムフィルタを更新しました', + 'Unable to update custom filter.' => 'カスタムフィルタを更新できません', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'タスク#%dの新しい添付ファイル:%s', + 'New comment on task #%d' => 'タスク#%dの新しいコメント', + 'Comment updated on task #%d' => 'タスク#%dで更新されたコメント', + 'New subtask on task #%d' => 'タスク#%dの新しいサブタスク', + 'Subtask updated on task #%d' => 'タスク#%dで更新されたサブタスク', + 'New task #%d: %s' => '新しいタスク#%d:%s', + 'Task updated #%d' => 'タスクが更新されました#%d', + 'Task #%d closed' => 'タスク#%dは完了しました', + 'Task #%d opened' => 'タスク#%dを作成しました', + 'Column changed for task #%d' => 'タスク#%dのカラムが変更されました', + 'New position for task #%d' => 'タスク#%dの新しい位置', + 'Swimlane changed for task #%d' => 'タスク#%dのスイムレーンが変更されました', + 'Assignee changed on task #%d' => 'タスク#%dの担当者が変更されました', + '%d overdue tasks' => '%d 件の期限切れタスク', + 'No notification.' => '通知なし', + 'Mark all as read' => 'すべてを既読にする', + 'Mark as read' => '既読にする', + 'Total number of tasks in this column across all swimlanes' => 'すべてのスイムレーンでこのカラムのタスクの合計', + 'Collapse swimlane' => 'スイムレーンをたたむ', + 'Expand swimlane' => 'スイムレーンを展開', + 'Add a new filter' => 'フィルターを追加', + 'Share with all project members' => 'プロジェクトメンバーと共有', + 'Shared' => '共有済み', + 'Owner' => '所有者', + 'Unread notifications' => '未読通知', + 'Notification methods:' => '通知方法:', + 'Unable to read your file' => 'あなたのファイルを読むことができません', + '%d task(s) have been imported successfully.' => '%d件のタスクが正常にインポートされました', + 'Nothing has been imported!' => '何もインポートされていません!', + 'Import users from CSV file' => 'CSVファイルからユーザーをインポート', + '%d user(s) have been imported successfully.' => '%d人のユーザーが正常にインポートされました', + 'Comma' => 'カンマ', + 'Semi-colon' => 'セミコロン', + 'Tab' => 'タブ', + 'Vertical bar' => '縦棒', + 'Double Quote' => '二重引用符', + 'Single Quote' => '一重引用符', + '%s attached a file to the task #%d' => '%sさんがタスクにファイルを添付しました#%d', + 'There is no column or swimlane activated in your project!' => 'あなたのプロジェクトではカラムやスイムレーンが有効になっていません!', + 'Append filter (instead of replacement)' => '置換の代わりにフィルターを追加', + 'Append/Replace' => '追加/置換', + 'Append' => '追加', + 'Replace' => '置換', + 'Import' => 'インポート', + 'Change sorting' => 'ソートの変更', + 'Tasks Importation' => 'タスクのインポート', + 'Delimiter' => '区切り', + 'Enclosure' => 'エンクロージャ', + 'CSV File' => 'CSVファイル', + 'Instructions' => '指示', + 'Your file must use the predefined CSV format' => 'ファイルは定義済みのCSV形式である必要があります', + 'Your file must be encoded in UTF-8' => 'ファイルはUTF-8でエンコードする必要があります', + 'The first row must be the header' => '最初の行は見出しでなければなりません', + 'Duplicates are not verified for you' => '重複は検証されません', + 'The due date must use the ISO format: YYYY-MM-DD' => '期限はISO形式である必要があります:YYYY-MM-DD', + 'Download CSV template' => 'CSVテンプレートをダウンロード', + 'No external integration registered.' => '外部統合が登録されていません', + 'Duplicates are not imported' => '重複はインポートされません', + 'Usernames must be lowercase and unique' => 'ユーザー名は小文字でユニークでなければなりません', + 'Passwords will be encrypted if present' => 'パスワードがあれば暗号化されます', + '%s attached a new file to the task %s' => '%sが、タスク %s に新しいファイルを添付しました', + 'Link type' => 'リンクタイプ', + 'Assign automatically a category based on a link' => 'リンクに基づいてカテゴリを自動的に割当てる', + 'BAM - Konvertible Mark' => 'BAM - 兌換マルク', + 'Assignee Username' => '担当者のユーザー名', + 'Assignee Name' => '担当者名', + 'Groups' => 'グループ', + 'Members of %s' => '%sのメンバー', + 'New group' => '新しいグループ', + 'Group created successfully.' => 'グループは正常に作成されました', + 'Unable to create your group.' => 'あなたのグループを作成できません', + 'Edit group' => 'グループを編集', + 'Group updated successfully.' => 'グループは正常に更新されました', + 'Unable to update your group.' => 'あなたのグループを更新できません', + 'Add group member to "%s"' => '「%s」にグループメンバーを追加', + 'Group member added successfully.' => 'グループメンバーが正常に追加されました', + 'Unable to add group member.' => 'グループメンバーを追加できません', + 'Remove user from group "%s"' => 'グループ「%s」からユーザーを削除', + 'User removed successfully from this group.' => 'このグループからユーザーが正常に削除されました', + 'Unable to remove this user from the group.' => 'このユーザーをグループから削除できません', + 'Remove group' => 'グループを削除', + 'Group removed successfully.' => 'グループは正常に削除されました', + 'Unable to remove this group.' => 'このグループを削除できません', + 'Project Permissions' => 'プロジェクトの権限', + 'Manager' => '組織の管理者', + 'Project Manager' => 'プロジェクト管理者', + 'Project Member' => 'プロジェクトメンバー', + 'Project Viewer' => 'プロジェクトビューアー', + 'Your account is locked for %d minutes' => 'あなたのアカウントは%d分間ロックされています', + 'Invalid captcha' => '無効なcaptcha', + 'The name must be unique' => '名前はユニークである必要があります', + 'View all groups' => 'すべてのグループを表示', + 'There is no user available.' => '利用可能なユーザーはありません', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'ユーザー「%s」をグループ「%s」から削除しますか?', + 'There is no group.' => 'グループはありません', + 'Add group member' => 'グループメンバーを追加', + 'Do you really want to remove this group: "%s"?' => 'グループ「%s」を削除しますか?', + 'There is no user in this group.' => 'このグループにはユーザーがいません', + 'Permissions' => '権限', + 'Allowed Users' => '許可されたユーザー', + 'No specific user has been allowed.' => '許可されたユーザーはありません', + 'Role' => '役割', + 'Enter user name...' => 'ユーザー名を入力...', + 'Allowed Groups' => '許可されたグループ', + 'No group has been allowed.' => '許可されたグループはありません', + 'Group' => 'グループ', + 'Group Name' => 'グループ名', + 'Enter group name...' => 'グループ名を入力...', + 'Role:' => '役割:', + 'Project members' => 'プロジェクトメンバー', + '%s mentioned you in the task #%d' => '%sはタスク#%dであなたのことに言及しました', + '%s mentioned you in a comment on the task #%d' => '%sはタスク#%dのコメントであなたのことに言及しました', + 'You were mentioned in the task #%d' => 'タスク#%dであなたのことが言及されました', + 'You were mentioned in a comment on the task #%d' => 'タスク#%dのコメントであなたのことが言及されました', + 'Estimated hours: ' => '見積時間:', + 'Actual hours: ' => '実際の時間:', + 'Hours Spent' => '時間(経過)', + 'Hours Estimated' => '時間(見積)', + 'Estimated Time' => '見積時間', + 'Actual Time' => '実際の時間', + 'Estimated vs actual time' => '見積時間と実際の時間', + 'RUB - Russian Ruble' => '露ルーブル', + 'Assign the task to the person who does the action when the column is changed' => 'カラムが変更されたときにアクションを実行する人にタスクを割当てる', + 'Close a task in a specific column' => '特定のカラムのタスクを完了させる', + 'Time-based One-time Password Algorithm' => '時間基準のワンタイム・パスワード・アルゴリズム', + 'Two-Factor Provider: ' => '二要素認証の提供元:', + 'Disable two-factor authentication' => '二要素認証を無効にする', + 'Enable two-factor authentication' => '二要素認証を有効にする', + 'There is no integration registered at the moment.' => '現在登録されている統合はありません', + 'Password Reset for Kanboard' => 'Kanboardのパスワードリセット', + 'Forgot password?' => 'パスワードをお忘れですか?', + 'Enable "Forget Password"' => '「パスワードをお忘れですか?」を有効にする', + 'Password Reset' => 'パスワードリセット', + 'New password' => '新しいパスワード', + 'Change Password' => 'パスワードを変更', + 'To reset your password click on this link:' => 'このリンクをクリックするとパスワードをリセットします:', + 'Last Password Reset' => '最後のパスワードリセット', + 'The password has never been reinitialized.' => 'パスワードが再設定されていません', + 'Creation' => '作成', + 'Expiration' => '失効', + 'Password reset history' => 'パスワードリセット履歴', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'カラム「%s」 かつ、スイムレーン「%s」の全てのタスクは正常に終了しました', + 'Do you really want to close all tasks of this column?' => 'このカラムのすべてのタスクを終了しますか?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d件の、カラム「%s」 かつ、スイムレーン「%s」 にあるタスクを終了します', + 'Close all tasks in this column and this swimlane' => 'このカラムのすべてのタスクを終了', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'プラグインはプロジェクト通知メソッドを登録していません。ユーザープロフィールで個別に通知を設定することができます', + 'My dashboard' => 'ダッシュボード', + 'My profile' => 'プロフィール', + 'Project owner: ' => 'プロジェクト責任者:', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'プロジェクト識別子はオプションです。設定する場合は英数字にしてください。 例:MYPROJECT', + 'Project owner' => 'プロジェクト責任者', + 'Personal projects do not have users and groups management.' => '非公開プロジェクトにはユーザーとグループの管理はありません', + 'There is no project member.' => 'プロジェクトメンバーがいません', + 'Priority' => '優先度', + 'Task priority' => 'タスクの優先度', + 'General' => '一般', + 'Dates' => '日付', + 'Default priority' => '既定の優先度', + 'Lowest priority' => '最低優先度', + 'Highest priority' => '最高優先度', + 'Close a task when there is no activity' => '活動がないときはタスクを終了', + 'Duration in days' => '期間(日)', + 'Send email when there is no activity on a task' => 'タスクに活動がないときにメールを送信', + 'Unable to fetch link information.' => 'リンク情報を取得できません', + 'Daily background job for tasks' => 'タスクのためのバックグラウンドジョブ(毎日)', + 'Auto' => '自動', + 'Related' => '関連', + 'Attachment' => '添付', + 'Web Link' => 'Webリンク', + 'External links' => '外部リンク', + 'Add external link' => '外部リンクを追加', + 'Type' => 'タイプ', + 'Dependency' => '依存', + 'Add internal link' => '内部リンクを追加', + 'Add a new external link' => '新しい外部リンクを追加', + 'Edit external link' => '外部リンクを編集', + 'External link' => '外部リンク', + 'Copy and paste your link here...' => 'リンクをここにコピー&ペースト...', + 'URL' => 'URL', + 'Internal links' => '内部リンク', + 'Assign to me' => '自分が担当する', + 'Me' => '自分', + 'Do not duplicate anything' => '何も複製しない', + 'Projects management' => 'プロジェクト管理', + 'Users management' => 'ユーザー管理', + 'Groups management' => 'グループ管理', + 'Create from another project' => '別のプロジェクトから作成', + 'open' => '開始', + 'closed' => '終了', + 'Priority:' => '優先度:', + 'Reference:' => '参照:', + 'Complexity:' => '複雑さ:', + 'Swimlane:' => 'スイムレーン:', + 'Column:' => 'カラム:', + 'Position:' => '位置:', + 'Creator:' => '作成者:', + 'Time estimated:' => '見積時間:', + '%s hours' => '%s時間', + 'Time spent:' => '経過時間:', + 'Created:' => '作成:', + 'Modified:' => '更新:', + 'Completed:' => '完了:', + 'Started:' => '開始:', + 'Moved:' => '移動:', + 'Task #%d' => 'タスク #%d', + 'Time format' => '時刻形式', + 'Start date: ' => '開始日:', + 'End date: ' => '完了日:', + 'New due date: ' => '新しい期限:', + 'Start date changed: ' => '開始日が変更されました:', + 'Disable personal projects' => '非公開プロジェクトを無効にする', + 'Do you really want to remove this custom filter: "%s"?' => 'このカスタムフィルタ「%s」を削除しますか?', + 'Remove a custom filter' => 'カスタムフィルタを削除', + 'User activated successfully.' => 'ユーザーは有効になりました', + 'Unable to enable this user.' => 'このユーザーを有効にできません', + 'User disabled successfully.' => 'ユーザーが無効になりました', + 'Unable to disable this user.' => 'このユーザーを無効にできません', + 'All files have been uploaded successfully.' => 'すべてのファイルが正常にアップロードされました', + 'The maximum allowed file size is %sB.' => '最大許容ファイルサイズ:%sB', + 'Drag and drop your files here' => 'ファイルをドラッグアンドドロップ', + 'choose files' => 'ファイルを選択', + 'View profile' => 'プロフィールを見る', + 'Two Factor' => '二要素認証', + 'Disable user' => 'ユーザーを無効にする', + 'Do you really want to disable this user: "%s"?' => 'ユーザー「%s」を無効にしますか?', + 'Enable user' => 'ユーザーを有効にする', + 'Do you really want to enable this user: "%s"?' => 'ユーザー「%s」を有効にしますか?', + 'Download' => 'ダウンロード', + 'Uploaded: %s' => 'アップロード完了:%s', + 'Size: %s' => 'サイズ:%s', + 'Uploaded by %s' => '%sによってアップロードされました', + 'Filename' => 'ファイル名', + 'Size' => 'サイズ', + 'Column created successfully.' => 'カラムが正常に作成されました', + 'Another column with the same name exists in the project' => '同じ名前の別のカラムがプロジェクトにあります', + 'Default filters' => '既定フィルタ', + 'Your board doesn\'t have any columns!' => 'あなたのボードにはカラムがありません!', + 'Change column position' => 'カラムの位置を変更', + 'Switch to the project overview' => 'プロジェクト概要に切替', + 'User filters' => 'ユーザーフィルタ', + 'Category filters' => 'カテゴリフィルタ', + 'Upload a file' => 'ファイルをアップロード', + 'View file' => 'ファイルを見る', + 'Last activity' => '最新の活動状況', + 'Change subtask position' => 'サブタスクの位置を変更', + 'This value must be greater than %d' => '%dより大きくなければなりません', + 'Another swimlane with the same name exists in the project' => '同じ名前の別のスイムレーンがプロジェクトにあります', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => '例:https://example.kanboard.org/(絶対URLの生成に使用)', + 'Actions duplicated successfully.' => 'アクションを複製しました', + 'Unable to duplicate actions.' => 'アクションを複製できません', + 'Add a new action' => '新しいアクションを追加', + 'Import from another project' => '別のプロジェクトからインポート', + 'There is no action at the moment.' => 'アクションはありません', + 'Import actions from another project' => '別のプロジェクトからアクションをインポート', + 'There is no available project.' => '利用可能なプロジェクトはありません', + 'Local File' => 'ローカルファイル', + 'Configuration' => '構成', + 'PHP version:' => 'PHPバージョン:', + 'PHP SAPI:' => 'PHP SAPI', + 'OS version:' => 'OSバージョン:', + 'Database version:' => 'データベース・バージョン:', + 'Browser:' => 'ブラウザー:', + 'Task view' => 'タスクビュー', + 'Edit task' => 'タスクを編集', + 'Edit description' => '説明を編集', + 'New internal link' => '新しい内部リンク', + 'Display list of keyboard shortcuts' => 'キーボードショートカットのリストを表示', + 'Avatar' => 'アバター', + 'Upload my avatar image' => '自分のアバター画像をアップロード', + 'Remove my image' => '画像を削除', + 'The OAuth2 state parameter is invalid' => 'OAuth2ステートパラメータは無効です', + 'User not found.' => 'ユーザーが見つかりません', + 'Search in activity stream' => '活動状況で検索', + 'My activities' => '自分の活動状況', + 'Activity until yesterday' => '昨日までの活動状況', + 'Activity until today' => '今までの活動状況', + 'Search by creator: ' => '作成者で検索:', + 'Search by creation date: ' => '作成日で検索:', + 'Search by task status: ' => 'タスクステータスで検索:', + 'Search by task title: ' => 'タスク名で検索:', + 'Activity stream search' => '活動状況を検索', + 'Projects where "%s" is manager' => '「%s」が管理者のプロジェクト', + 'Projects where "%s" is member' => '「%s」がメンバーのプロジェクト', + 'Open tasks assigned to "%s"' => '「%s」が担当のタスクを開く', + 'Closed tasks assigned to "%s"' => '「%s」が担当した終了したタスク', + 'Assign automatically a color based on a priority' => '優先度に基づいて自動的に色を割当て', + 'Overdue tasks for the project(s) "%s"' => '期限を超過したタスク「%s」', + 'Upload files' => 'ファイルをアップロード', + 'Installed Plugins' => '使用中のプラグイン', + 'Plugin Directory' => 'プラグインディレクトリ', + 'Plugin installed successfully.' => 'プラグインがインストールされました', + 'Plugin updated successfully.' => 'プラグインが更新されました', + 'Plugin removed successfully.' => 'プラグインを削除しました', + 'Subtask converted to task successfully.' => 'サブタスクをタスクに変換しました', + 'Unable to convert the subtask.' => 'サブタスクを変換できません', + 'Unable to extract plugin archive.' => 'プラグインアーカイブを解凍できません', + 'Plugin not found.' => 'プラグインが見つかりません', + 'You don\'t have the permission to remove this plugin.' => 'このプラグインを削除する権限がありません', + 'Unable to download plugin archive.' => 'プラグインアーカイブをダウンロードできません', + 'Unable to write temporary file for plugin.' => 'プラグインの一時ファイルを書き込めません', + 'Unable to open plugin archive.' => 'プラグインアーカイブを開くことができません', + 'There is no file in the plugin archive.' => 'プラグインアーカイブにファイルがありません', + 'Create tasks in bulk' => 'タスクを一括作成', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'あなたのKanboardインスタンスは、ユーザーインターフェースからプラグインをインストールするように設定されていません', + 'There is no plugin available.' => '利用可能なプラグインはありません', + 'Install' => 'インストール', + 'Update' => '更新', + 'Up to date' => '最新の日付', + 'Not available' => '利用できません', + 'Remove plugin' => 'プラグインを削除', + 'Do you really want to remove this plugin: "%s"?' => 'このプラグインを削除しますか?: %s', + 'Uninstall' => 'アンインストール', + 'Listing' => 'リスト', + 'Metadata' => 'メタデータ', + 'Manage projects' => 'プロジェクトを管理', + 'Convert to task' => 'タスクに変換', + 'Convert sub-task to task' => 'サブタスクをタスクに変換', + 'Do you really want to convert this sub-task to a task?' => 'このサブタスクをタスクに変換しますか?', + 'My task title' => '自分のタスク名', + 'Enter one task by line.' => '1行に1件のタスクを入力', + 'Number of failed login:' => 'ログイン失敗回数:', + 'Account locked until:' => 'ロックされたアカウント:', + 'Email settings' => 'メール設定', + 'Email sender address' => '送信者のメールアドレス', + 'Email transport' => 'メールの送信', + 'Webhook token' => 'Webhookトークン', + 'Project tags management' => 'プロジェクトタグ管理', + 'Tag created successfully.' => 'タグを作成しました', + 'Unable to create this tag.' => 'タグを作成できません', + 'Tag updated successfully.' => 'タグを更新しました', + 'Unable to update this tag.' => 'タグを更新できません', + 'Tag removed successfully.' => 'タグを削除しました', + 'Unable to remove this tag.' => 'タグを削除できません', + 'Global tags management' => 'グローバルタグ管理', + 'Tags' => 'タグ', + 'Tags management' => 'タグ管理', + 'Add new tag' => '新しいタグを追加', + 'Edit a tag' => 'タグを編集', + 'Project tags' => 'プロジェクトタグ', + 'There is no specific tag for this project at the moment.' => 'このプロジェクトにはタグはありません', + 'Tag' => 'タグ', + 'Remove a tag' => 'タグを削除', + 'Do you really want to remove this tag: "%s"?' => 'タグ「%s」を削除しますか?', + 'Global tags' => 'グローバルタグ', + 'There is no global tag at the moment.' => 'グローバルタグはありません', + 'This field cannot be empty' => 'このフィールドは必須です', + 'Close a task when there is no activity in a specific column' => '特定のカラムに活動がない場合にタスクを終了', + '%s removed a subtask for the task #%d' => 'タスク#%dからサブタスク%s件を削除しました', + '%s removed a comment on the task #%d' => 'タスク#%dからコメント%s件を削除しました', + 'Comment removed on task #%d' => 'タスク#%dからコメントを削除しました', + 'Subtask removed on task #%d' => 'タスク#%dからサブタスクを削除しました', + 'Hide tasks in this column in the dashboard' => 'ダッシュボードでこのカラムのタスクは表示しない', + '%s removed a comment on the task %s' => '%sはタスク「%s」からコメントを削除しました', + '%s removed a subtask for the task %s' => '%sはタスク「%s」からサブタスクを削除しました', + 'Comment removed' => 'コメントを削除しました', + 'Subtask removed' => 'サブタスクを削除しました', + '%s set a new internal link for the task #%d' => '%sはタスク#%dの新しい内部リンクを設定しました', + '%s removed an internal link for the task #%d' => '%sはタスク#%dの内部リンクを削除しました', + 'A new internal link for the task #%d has been defined' => 'タスク#%dの新しい内部リンクが定義されています', + 'Internal link removed for the task #%d' => 'タスク#%dの内部リンクが削除されました', + '%s set a new internal link for the task %s' => '%sはタスク「%s」の新しい内部リンクを設定しました', + '%s removed an internal link for the task %s' => '%sはタスク「%s」の内部リンクを削除しました', + 'Automatically set the due date on task creation' => 'タスク作成時に自動的に期限を設定', + 'Move the task to another column when closed' => '閉じたときにタスクを別のカラムに移動', + 'Move the task to another column when not moved during a given period' => '指定された期間中に移動しなかった場合、タスクを別のカラムに移動', + 'Dashboard for %s' => '%sのダッシュボード', + 'Tasks overview for %s' => '%sのタスク概要', + 'Subtasks overview for %s' => '%sのサブタスク概要', + 'Projects overview for %s' => '%sのプロジェクト概要', + 'Activity stream for %s' => '%sの活動状況', + 'Assign a color when the task is moved to a specific swimlane' => 'タスクが特定のスイムレーンに移動したときに色を割当てる', + 'Assign a priority when the task is moved to a specific swimlane' => 'タスクが特定のスイムレーンに移動したときに優先度を割当てる', + 'User unlocked successfully.' => 'ユーザーのロックを解除しました', + 'Unable to unlock the user.' => 'ユーザーのロックを解除できません', + 'Move a task to another swimlane' => '別のスイムレーンにタスクを移動', + 'Creator Name' => '作成者', + 'Time spent and estimated' => '経過/見積時間', + 'Move position' => '移動', + 'Move task to another position on the board' => 'ボード上の別の位置にタスクを移動', + 'Insert before this task' => 'このタスクの前に挿入', + 'Insert after this task' => 'このタスクの後に挿入', + 'Unlock this user' => 'このユーザーをロック解除', + 'Custom Project Roles' => 'プロジェクト独自の役割', + 'Add a new custom role' => '新しい役割を追加', + 'Restrictions for the role "%s"' => '役割「%s」の制限', + 'Add a new project restriction' => 'プロジェクト制約を追加', + 'Add a new drag and drop restriction' => 'ドラッグアンドドロップの制限を追加', + 'Add a new column restriction' => 'カラムに制限を追加', + 'Edit this role' => '役割を編集', + 'Remove this role' => '役割を削除', + 'There is no restriction for this role.' => 'この役割には制限はありません', + 'Only moving task between those columns is permitted' => 'これらのカラム間はタスクの移動のみが許可されています', + 'Close a task in a specific column when not moved during a given period' => '指定された期間中に移動しなかった場合、特定のカラムのタスクを終了', + 'Edit columns' => 'カラムを編集', + 'The column restriction has been created successfully.' => 'カラムの制限を作成しました', + 'Unable to create this column restriction.' => 'このカラムの制限を作成できません', + 'Column restriction removed successfully.' => 'カラムの制限を削除しました', + 'Unable to remove this restriction.' => 'この制限を削除できません', + 'Your custom project role has been created successfully.' => 'あなたのプロジェクトで独自の役割を作成しました', + 'Unable to create custom project role.' => 'プロジェクト独自の役割を作成できません', + 'Your custom project role has been updated successfully.' => 'プロジェクト独自の役割を更新しました', + 'Unable to update custom project role.' => 'プロジェクト独自の役割を更新できません', + 'Custom project role removed successfully.' => 'プロジェクト独自の役割を削除しました', + 'Unable to remove this project role.' => 'プロジェクトから役割を削除できません', + 'The project restriction has been created successfully.' => 'プロジェクトの制限が作成しました', + 'Unable to create this project restriction.' => 'プロジェクトの制限を作成できません', + 'Project restriction removed successfully.' => 'プロジェクトの制限を削除しました', + 'You cannot create tasks in this column.' => 'このカラムにタスクは作成できません', + 'Task creation is permitted for this column' => 'このカラムではタスクの作成が許可されています', + 'Closing or opening a task is permitted for this column' => 'このカラムではタスクを作成/完了が許可されています', + 'Task creation is blocked for this column' => 'このカラムでのタスク作成はブロックされています', + 'Closing or opening a task is blocked for this column' => 'このカラムでのタスクの作成/完了はブロックされています', + 'Task creation is not permitted' => 'タスクの作成は許可されていません', + 'Closing or opening a task is not permitted' => 'タスクの作成/完了は許可されていません', + 'New drag and drop restriction for the role "%s"' => '役割「%s」の新しいドラッグアンドドロップ制限', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'この役割に属するユーザーは、起点カラムと宛先カラムの間でのみタスクを移動できます', + 'Remove a column restriction' => 'カラムの制限を解除', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'このカラムの制限を解除しますか?: 「%s」→「%s」', + 'New column restriction for the role "%s"' => '役割「%s」のカラムでの新しい制限', + 'Rule' => '役割', + 'Do you really want to remove this column restriction?' => 'この列の制限を解除しますか?', + 'Custom roles' => '独自の役割', + 'New custom project role' => 'プロジェクトでの新しい独自の役割', + 'Edit custom project role' => 'プロジェクトでの独自の役割を編集', + 'Remove a custom role' => 'プロジェクトでの独自の役割を削除', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => '役割「%s」を削除しますか?この役割に割当てられたすべてのユーザーがプロジェクトメンバーになります', + 'There is no custom role for this project.' => 'プロジェクトでの独自の役割はありません', + 'New project restriction for the role "%s"' => '役割「%s」の新しいプロジェクト制限', + 'Restriction' => '制限', + 'Remove a project restriction' => 'プロジェクトの制限を解除', + 'Do you really want to remove this project restriction: "%s"?' => 'プロジェクトの制限「%s」を解除しますか?', + 'Duplicate to multiple projects' => '複数プロジェクトに複製', + 'This field is required' => 'このフィールドは必須です', + 'Moving a task is not permitted' => 'タスクを移動できません', + 'This value must be in the range %d to %d' => 'この値は%d〜%dの範囲になければなりません', + 'You are not allowed to move this task.' => 'このタスクを移動する権限がありません', + 'API User Access' => 'API ユーザーアクセス', + 'Preview' => 'プレビュー', + 'Write' => '書込み', + 'Write your text in Markdown' => 'Markdownで入力', + 'No personal API access token registered.' => '個人APIアクセストークンは登録されていません', + 'Your personal API access token is "%s"' => 'あなたのパーソナルAPIアクセストークン "%s"', + 'Remove your token' => 'あなたのトークンを削除', + 'Generate a new token' => '新しいトークンを生成', + 'Showing %d-%d of %d' => '%d〜%d/%dを表示', + 'Outgoing Emails' => '送信メール', + 'Add or change currency rate' => '通貨レートを追加または変更', + 'Reference currency: %s' => '参照通貨:%s', + 'Add custom filters' => 'カスタムフィルタを追加', + 'Export' => 'エクスポート', + 'Add link label' => 'リンクラベルを追加', + 'Incompatible Plugins' => '互換性のないプラグイン', + 'Compatibility' => '互換性', + 'Permissions and ownership' => 'アクセス権と所有権', + 'Priorities' => 'プロパティ', + 'Close this window' => 'ウィンドウを閉じる', + 'Unable to upload this file.' => 'ファイルをアップロードできません', + 'Import tasks' => 'タスクのインポート', + 'Choose a project' => 'プロジェクトを選択', + 'Profile' => 'プロフィール', + 'Application role' => 'アプリケーションでの役割', + '%d invitations were sent.' => '%d通の招待状を送信しました', + '%d invitation was sent.' => '%dに招待状を送信しました', + 'Unable to create this user.' => 'ユーザーを作成できません', + 'Kanboard Invitation' => 'Kanboardへの招待', + 'Visible on dashboard' => 'ダッシュボード表示', + 'Created at:' => '作成日時:', + 'Updated at:' => '更新日時:', + 'There is no custom filter.' => 'カスタムフィルタはありません', + 'New User' => '新規ユーザー', + 'Authentication' => '認証', + 'If checked, this user will use a third-party system for authentication.' => 'チェックすると、このユーザーは認証に第三者システムを使用します', + 'The password is necessary only for local users.' => 'パスワードはローカルユーザーにのみ必要です', + 'You have been invited to register on Kanboard.' => 'あなたはKanboardに登録されました', + 'Click here to join your team' => 'クリックしてあなたのチームに参加', + 'Invite people' => '招待', + 'Emails' => 'Eメール', + 'Enter one email address by line.' => '行ごとに1つのメールアドレスを入力してください', + 'Add these people to this project' => 'これらの人々をプロジェクトに追加', + 'Add this person to this project' => 'この人をプロジェクトに追加', + 'Sign-up' => 'サインアップ', + 'Credentials' => '資格情報', + 'New user' => '新規ユーザー', + 'This username is already taken' => 'このユーザー名はすでに使用されています', + 'Your profile must have a valid email address.' => 'プロフィールには有効なメールアドレスが必要です', + 'TRL - Turkish Lira' => 'トルコリラ', + 'The project email is optional and could be used by several plugins.' => 'プロジェクトのEメールはオプション/複数のプラグインで使用可能', + 'The project email must be unique across all projects' => 'プロジェクトのEメールはすべてのプロジェクトでユニークである必要があります', + 'The email configuration has been disabled by the administrator.' => '管理者がEメールの設定を無効にしました', + 'Close this project' => 'プロジェクトを終了', + 'Open this project' => 'このプロジェクトを開く', + 'Close a project' => 'プロジェクトを終了', + 'Do you really want to close this project: "%s"?' => 'このプロジェクトを本当に終了しますか?: %s', + 'Reopen a project' => 'プロジェクトを開く', + 'Do you really want to reopen this project: "%s"?' => 'このプロジェクトを開きますか?: %s', + 'This project is open' => 'このプロジェクトは進行中', + 'This project is closed' => 'このプロジェクトは終了しました', + 'Unable to upload files, check the permissions of your data folder.' => 'ファイルをアップロードできません。データフォルダのアクセス権を確認してください。', + 'Another category with the same name exists in this project' => '同じ名前の別のカテゴリがプロジェクトに存在します', + 'Comment sent by email successfully.' => 'メールでコメントを送信しました', + 'Sent by email to "%s" (%s)' => '%s をメールで送信しました "%s"', + 'Unable to read uploaded file.' => 'アップロードされたファイルを読み込めません', + 'Database uploaded successfully.' => 'データベースをアップロードしました', + 'Task sent by email successfully.' => 'タスクをメールで送信しました', + 'There is no category in this project.' => 'このプロジェクトにカテゴリはありません', + 'Send by email' => 'メールで送る', + 'Create and send a comment by email' => 'Eメールでコメントを送信', + 'Subject' => '件名', + 'Upload the database' => 'データベースをアップロード', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => '以前にダウンロードしたSqliteデータベース(Gzip形式)をアップロードできます', + 'Database file' => 'データベースファイル', + 'Upload' => 'アップロード', + 'Your project must have at least one active swimlane.' => 'プロジェクトには少なくとも1つのアクティブなスイムレーンが必要です', + 'Project: %s' => 'プロジェクト:%s', + 'Automatic action not found: "%s"' => '自動アクションが見つかりません: %s', + '%d projects' => '%d 件のプロジェクト', + '%d project' => '%d プロジェクト', + 'There is no project.' => 'プロジェクトはありません', + 'Sort' => 'ソート', + 'Project ID' => 'プロジェクトID', + 'Project name' => 'プロジェクト名', + 'Public' => '公開', + 'Personal' => '非公開', + '%d tasks' => '%d 件のタスク', + '%d task' => '%d タスク', + 'Task ID' => 'タスクID', + 'Assign automatically a color when due date is expired' => '期限が切れたら自動的に色を割当て', + 'Total score in this column across all swimlanes' => 'このカラムのすべてのスイムレーンで合計得点', + 'HRK - Kuna' => 'クロアチア クーナ', + 'ARS - Argentine Peso' => 'ARS - アルゼンチンペソ', + 'COP - Colombian Peso' => 'COP - コロンビアペソ', + '%d groups' => '%d個のグループ', + '%d group' => '%d グループ', + 'Group ID' => 'グループID', + 'External ID' => '外部ID', + '%d users' => '%d 人のユーザー', + '%d user' => '%d ユーザー', + 'Hide subtasks' => 'サブタスクを隠す', + 'Show subtasks' => 'サブタスクを表示', + 'Authentication Parameters' => '認証パラメータ', + 'API Access' => 'APIアクセス', + 'No users found.' => 'ユーザーが見つかりません', + 'User ID' => 'ユーザーID', + 'Notifications are activated' => '通知は有効', + 'Notifications are disabled' => '通知は無効', + 'User disabled' => 'ユーザーを無効にしました', + '%d notifications' => '%d 件の通知', + '%d notification' => '%d 通知', + 'There is no external integration installed.' => '外部インテグレーションはインストールされていません', + 'You are not allowed to update tasks assigned to someone else.' => '他の人に割当てられたタスクを更新する権限がありません', + 'You are not allowed to change the assignee.' => '担当者を変更する権限はありません', + 'Task suppression is not permitted' => 'タスクの抑制は許可されていません', + 'Changing assignee is not permitted' => '担当者は変更できません', + 'Update only assigned tasks is permitted' => '割当てられたタスクのみ更新できます', + 'Only for tasks assigned to the current user' => '現在のユーザーに割当てられたタスクのみ', + 'My projects' => '自分のプロジェクト', + 'You are not a member of any project.' => 'あなたが所属しているプロジェクトにはありません', + 'My subtasks' => '自分のサブタスク', + '%d subtasks' => '%d 件のサブタスク', + '%d subtask' => '%d サブタスク', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => '現在のユーザーに割当てられたタスクは、これらのカラム間の移動のみが許可されます', + '[DUPLICATE]' => '[コピー]', + 'DKK - Danish Krona' => 'DKK - デンマーククローナ', + 'Remove user from group' => 'ユーザーをグループから削除', + 'Assign the task to its creator' => '作成者にタスクを割当て', + 'This task was sent by email to "%s" with subject "%s".' => 'このタスクをメールで 「%s」 に送信しました(件名 "%s")', + 'Predefined Email Subjects' => '既定のメールの件名', + 'Write one subject by line.' => '行ごとに1つずつ件名を書く', + 'Create another link' => '別のリンクを作成', + 'BRL - Brazilian Real' => 'BRL - ブラジルレアル', + 'Add a new Kanboard task' => 'Kanboard タスクを追加', + 'Subtask not started' => 'サブタスクは開始されていません', + 'Subtask currently in progress' => 'サブタスクは進行中', + 'Subtask completed' => 'サブタスクは完了', + 'Subtask added successfully.' => 'サブタスクを追加しました', + '%d subtasks added successfully.' => '%d 件のサブタスクを追加しました', + 'Enter one subtask by line.' => '1行に1件のサブタスクを入力', + 'Predefined Contents' => '定義済みコンテンツ', + 'Predefined contents' => '定義済みコンテンツ', + 'Predefined Task Description' => '定義済みタスクの説明', + 'Do you really want to remove this template? "%s"' => 'このテンプレートを削除しますか?: %s', + 'Add predefined task description' => '定義済みのタスクの説明を追加', + 'Predefined Task Descriptions' => '定義済みタスクの説明', + 'Template created successfully.' => 'テンプレートを作成しました', + 'Unable to create this template.' => 'テンプレートを作成できません', + 'Template updated successfully.' => 'テンプレートを更新しました', + 'Unable to update this template.' => 'テンプレートを更新できません', + 'Template removed successfully.' => 'テンプレートを削除しました', + 'Unable to remove this template.' => 'テンプレートを削除できません', + 'Template for the task description' => 'タスク説明のテンプレート', + 'The start date is greater than the end date' => '開始日が終了日を超えています', + 'Tags must be separated by a comma' => 'タグはコンマで区切る必要があります', + 'Only the task title is required' => 'タスクのタイトルのみが必要です', + 'Creator Username' => '作成者名', + 'Color Name' => '色名', + 'Column Name' => 'カラム名', + 'Swimlane Name' => 'スイムレーン名', + 'Time Estimated' => '見積時間', + 'Time Spent' => '経過時間', + 'External Link' => '外部リンク', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'この機能は、iCal フィードとRSSフィード、そしてボードビューを公開します。', + 'Stop the timer of all subtasks when moving a task to another column' => 'タスクを別のカラムに移動したときに、全てのサブタスクのタイマーを停止する', + 'Subtask Title' => 'サブタスクのタイトル', + 'Add a subtask and activate the timer when moving a task to another column' => 'サブタスクを追加し、タスクを別のカラムに移動したときにタイマーを有効化する', + 'days' => '日', + 'minutes' => '分', + 'seconds' => '秒', + 'Assign automatically a color when preset start date is reached' => '予め設定した開始日になったら、自動的に色を割り当てる', + 'Move the task to another column once a predefined start date is reached' => '予め設定した開始日になったら、タスクを別のカラムに移動する', + 'This task is now linked to the task %s with the relation "%s"' => 'このタスクと、タスク %s を、"%s"として関連付けました ', + 'The link with the relation "%s" to the task %s has been removed' => 'このタスクと "%s" として関連付けていた、タスク %s とのリンクを削除しました', + 'Custom Filter:' => 'カスタムフィルタ', + 'Unable to find this group.' => 'このグループが見つかりません', + '%s moved the task #%d to the column "%s"' => '%s がタスク #%d をカラム "%s"に移動しました', + '%s moved the task #%d to the position %d in the column "%s"' => '%s がタスク #%d をポジション#%d カラム "%s"に移動しました', + '%s moved the task #%d to the swimlane "%s"' => '%s がタスク #%d をスイムレーン "%s"に移動しました', + '%sh spent' => '%sh 経過', + '%sh estimated' => '%sh の見積', + 'Select All' => '全て選択', + 'Unselect All' => '全て選択解除', + 'Apply action' => 'アクションを実行', + 'Move selected tasks to another column or swimlane' => '選択したタスクを別のカラムに移動', + 'Edit tasks in bulk' => 'タスクを一括編集', + 'Choose the properties that you would like to change for the selected tasks.' => '選択したタスクの、変更したいプロパティを選択してください。', + 'Configure this project' => 'このプロジェクトの設定', + 'Start now' => '今すぐ開始', + '%s removed a file from the task #%d' => '%s がタスク #%d からファイルを削除しました', + 'Attachment removed from task #%d: %s' => 'タスク #%d への添付ファイル %s は削除されました', + 'No color' => '色無し', + 'Attachment removed "%s"' => '添付 "%s" は削除されました', + '%s removed a file from the task %s' => '%s がタスク %s からファイルを削除しました', + 'Move the task to another swimlane when assigned to a user' => '担当者を割り当てたとき、タスクを別のスイムレーンに移動する', + 'Destination swimlane' => '宛先のスイムレーン', + 'Assign a category when the task is moved to a specific swimlane' => 'タスクが特定のスイムレーンに移動したときにカテゴリを割り当てる', + 'Move the task to another swimlane when the category is changed' => 'カテゴリが変更されたとき、タスクを別のスイムレーンに移動する', + 'Reorder this column by priority (ASC)' => '優先度で並び替える(昇順)', + 'Reorder this column by priority (DESC)' => '優先度で並び替える(降順)', + 'Reorder this column by assignee and priority (ASC)' => '担当者と優先度で並び替える(昇順)', + 'Reorder this column by assignee and priority (DESC)' => '担当者と優先度で並び替える(降順)', + 'Reorder this column by assignee (A-Z)' => '担当者で並び替える(A-Z)', + 'Reorder this column by assignee (Z-A)' => '担当者で並び替える(Z-A)', + 'Reorder this column by due date (ASC)' => '期限で並び替える(昇順)', + 'Reorder this column by due date (DESC)' => '期限で並び替える(降順)', + 'Reorder this column by id (ASC)' => 'この列をIDで並べ替え (昇順)', + 'Reorder this column by id (DESC)' => 'この列をIDで並べ替え (降順)', + '%s moved the task #%d "%s" to the project "%s"' => '%s がタスク #%d "%s" をプロジェクト "%s" に移動しました', + 'Task #%d "%s" has been moved to the project "%s"' => 'タスク#%d "%s" はプロジェクト "%s" へ移動されました ', + 'Move the task to another column when the due date is less than a certain number of days' => '期限までの日数が規定値以下になったら、タスクを別のカラムに移動する', + 'Automatically update the start date when the task is moved away from a specific column' => 'タスクが特定のカラムから移動された時、自動的に開始日を更新する', + 'HTTP Client:' => 'HTTPクライアント', + 'Assigned' => '割当済み', + 'Task limits apply to each swimlane individually' => 'スイムレーン毎にタスク数制限を適用する。', + 'Column task limits apply to each swimlane individually' => 'スイムレーン毎にカラムのタスク数制限が適用されます。', + 'Column task limits are applied to each swimlane individually' => 'カラムのタスク数制限はスイムレーン毎に適用されています。', + 'Column task limits are applied across swimlanes' => 'カラムのタスク数制限はスイムレーンを跨いで適用されています。', + 'Task limit: ' => 'タスク数制限', + 'Change to global tag' => 'グローバルタグに変換', + 'Do you really want to make the tag "%s" global?' => '本当にタグ "%s" をグローバルタグに変換しますか?', + 'Enable global tags for this project' => 'このプロジェクトでグローバルタグを有効にする', + 'Group membership(s):' => '所属グループ', + '%s is a member of the following group(s): %s' => 'メンバー %s は以下のグループに所属しています: %s', + '%d/%d group(s) shown' => 'グループ %d/%d を表示', + 'Subtask creation or modification' => 'サブタスクが作成or更新されました', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => '特定のスイムレーンにタスクを移動した時に、特定のユーザーにタスクを割当てる', + 'Comment' => 'コメント', + 'Collapse vertically' => '縦に折りたたむ', + 'Expand vertically' => '縦の折りたたみを解除', + 'MXN - Mexican Peso' => 'メキシコ・ペソ', + 'Estimated vs actual time per column' => '列ごとの見積もり時間 vs 実際にかかった時間', + 'HUF - Hungarian Forint' => 'HUF - ハンガリー・フォリント', + 'XBT - Bitcoin' => 'XBT - Bitcoin', + 'You must select a file to upload as your avatar!' => 'アバターとしてアップロードするファイルを選択してください!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'アップロードされたファイルは有効な画像ではありません! (許可されているのは *.gif, *.jpg, *.jpeg, *.png のみです)', + 'Automatically set the due date when the task is moved away from a specific column' => '特定の列からタスクが移動されたときに期限を自動的に設定する', + 'No other projects found.' => '他のプロジェクトは見つかりませんでした。', + 'Tasks copied successfully.' => 'タスクが正常にコピーされました。', + 'Unable to copy tasks.' => 'タスクをコピーできません。', + 'Theme' => 'テーマ', + 'Theme:' => 'テーマ:', + 'Light theme' => 'ライトテーマ', + 'Dark theme' => 'ダークテーマ', + 'Automatic theme - Sync with system' => '自動テーマ - システムと同期', + 'Application managers or more' => 'アプリケーションマネージャー以上', + 'Administrators' => '管理者', + 'Visibility:' => '可視性:', + 'Standard users' => '標準ユーザー', + 'Visibility is required' => '可視性は必須です', + 'The visibility should be an app role' => '可視性はアプリの役割である必要があります', + 'Reply' => '返信', + '%s wrote: ' => '%s が書きました:', + 'Number of visible tasks in this column and swimlane' => 'この列とスイムレーンで表示されているタスクの数', + 'Number of tasks in this swimlane' => 'このスイムレーンにあるタスクの数', + 'Unable to find another subtask in progress, you can close this window.' => '他の進行中のサブタスクが見つかりません。このウィンドウを閉じることができます。', + 'This theme is invalid' => 'このテーマは無効です', + 'This role is invalid' => 'この役割は無効です', + 'This timezone is invalid' => 'このタイムゾーンは無効です', + 'This language is invalid' => 'この言語は無効です', + 'This URL is invalid' => 'このURLは無効です', + 'Date format invalid' => '日付形式が無効です', + 'Time format invalid' => '時刻形式が無効です', + 'Invalid Mail transport' => '無効なメール転送', + 'Color invalid' => '色が無効です', + 'This value must be greater or equal to %d' => 'この値は %d 以上である必要があります', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'ファイルの先頭にBOMを追加する(Microsoft Excelに必要)', + 'Just add these tag(s)' => 'これらのタグを追加するだけ', + 'Remove internal link(s)' => '内部リンクを削除', + 'Import tasks from another project' => '他のプロジェクトからタスクをインポートする', + 'Select the project to copy tasks from' => 'タスクをコピーするプロジェクトを選択してください', + 'The total maximum allowed attachments size is %sB.' => '添付ファイルの合計最大許容サイズは %sB です。', + 'Add attachments' => '添付ファイルを追加', + 'Task #%d "%s" is overdue' => 'タスク #%d "%s" は期限切れです', + 'Enable notifications by default for all new users' => '新規ユーザーに対して、デフォルトで通知を有効にする', + 'Assign the task to its creator for specific columns if no assignee is set manually' => '担当者が手動で設定されていない場合、指定したカラムではタスクを作成者に割り当てる', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => '担当者がいない場合、カラム変更で指定のカラムに移動したときにタスクをログインユーザーに割り当てる', +]; diff --git a/app/Locale/ko_KR/translations.php b/app/Locale/ko_KR/translations.php new file mode 100644 index 0000000..b776f59 --- /dev/null +++ b/app/Locale/ko_KR/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => '.', + 'number.thousands_separator' => ',', + 'None' => '없음', + 'Edit' => '수정', + 'Remove' => '삭제', + 'Yes' => '예', + 'No' => '아니오', + 'cancel' => '취소', + 'or' => '또는', + 'Yellow' => '노랑', + 'Blue' => '파랑', + 'Green' => '초록', + 'Purple' => '보라', + 'Red' => '빨강', + 'Orange' => '주황', + 'Grey' => '회색', + 'Brown' => '브라운', + 'Deep Orange' => '진홍', + 'Dark Grey' => '암회', + 'Pink' => '핑크', + 'Teal' => '암녹', + 'Cyan' => '청녹', + 'Lime' => '라임', + 'Light Green' => '연회', + 'Amber' => '호박', + 'Save' => '저장', + 'Login' => '로그인', + 'Official website:' => '공식 웹사이트:', + 'Unassigned' => '담당자 없음', + 'View this task' => '이 할일 보기', + 'Remove user' => '사용자 삭제', + 'Do you really want to remove this user: "%s"?' => '사용자 "%s"를 정말로 삭제하시겠습니까?', + 'All users' => '모든 사용자', + 'Username' => '사용자 이름', + 'Password' => '패스워드', + 'Administrator' => '관리자', + 'Sign in' => '로그인', + 'Users' => '사용자', + 'Forbidden' => '접근 거부', + 'Access Forbidden' => '접속이 거부되었습니다', + 'Edit user' => '사용자를 변경하는 ', + 'Logout' => '로그아웃', + 'Bad username or password' => '사용자 이름 또는 패스워드가 다릅니다.', + 'Edit project' => '프로젝트 수정', + 'Name' => '이름', + 'Projects' => '프로젝트', + 'No project' => '프로젝트가 없습니다', + 'Project' => '프로젝트', + 'Status' => '상태', + 'Tasks' => '할일', + 'Board' => '보드', + 'Actions' => 'Actions', + 'Inactive' => '무효', + 'Active' => '유효', + 'Unable to update this board.' => '보드를 갱신할 수 없었습니다', + 'Disable' => '비활성화', + 'Enable' => '유효하게 한다', + 'New project' => '새 프로젝트', + 'Do you really want to remove this project: "%s"?' => '프로젝트를 삭제하시겠습니까: "%s"?', + 'Remove project' => '프로젝트의 삭제', + 'Edit the board for "%s"' => '"%s"를 위한 보드 수정', + 'Add a new column' => '컬럼의 추가', + 'Title' => '제목', + 'Assigned to %s' => '담당자 %s', + 'Remove a column' => '컬럼 삭제', + 'Unable to remove this column.' => '(※)컬럼을 삭제할 수 없었습니다.', + 'Do you really want to remove this column: "%s"?' => '컬럼을 삭제하시겠습니까: "%s"?', + 'Settings' => '설정', + 'Application settings' => '애플리케이션의 설정', + 'Language' => '언어', + 'Webhook token:' => 'Webhook토큰:', + 'API token:' => 'API토큰:', + 'Database size:' => '데이터베이스의 사이즈:', + 'Download the database' => '데이터베이스의 다운로드', + 'Optimize the database' => '데이터베이스 최적화', + '(VACUUM command)' => '(VACUUM명령)', + '(Gzip compressed Sqlite file)' => '(GZip명령으로 압축된 Sqlite파일)', + 'Close a task' => '할일 마치기', + 'Column' => '컬럼', + 'Color' => '색', + 'Assignee' => '담당자', + 'Create another task' => '다른 할일 추가', + 'New task' => '새로운 할일', + 'Open a task' => '할일 열기', + 'Do you really want to open this task: "%s"?' => '할일은 시작 하시겠습니까: "%s"?', + 'Back to the board' => '보드로 돌아가기', + 'There is nobody assigned' => '담당자가 없습니다', + 'Column on the board:' => '컬럼:', + 'Close this task' => '할일 마치기', + 'Open this task' => '할일을 열다', + 'There is no description.' => '설명이 없다', + 'Add a new task' => '할일을 추가하는 ', + 'The username is required' => '사용자 이름이 필요합니다', + 'The maximum length is %d characters' => '최대 길이는 "%d" 글자 입니다', + 'The minimum length is %d characters' => '최소 길이는 "%d" 글자 입니다', + 'The password is required' => '패스워드가 필요합니다', + 'This value must be an integer' => '정수로 입력하세요', + 'The username must be unique' => '사용자 이름이 이미 사용되고 있습니다', + 'The user id is required' => '사용자 ID가 필요합니다', + 'Passwords don\'t match' => '패스워드가 일치하지 않습니다', + 'The confirmation is required' => '확인용 패스워드를 입력하세요', + 'The project is required' => '프로젝트가 필요합니다', + 'The id is required' => 'ID가 필요합니다', + 'The project id is required' => '프로젝트 ID가 필요합니다', + 'The project name is required' => '프로젝트 이름이 필요합니다', + 'The title is required' => '제목이 필요합니다', + 'Settings saved successfully.' => '설정을 저장하였습니다', + 'Unable to save your settings.' => '설정의 보존에 실패했습니다.', + 'Database optimization done.' => '데이터베이스 최적화가 끝났습니다.', + 'Your project has been created successfully.' => '프로젝트를 작성했습니다.', + 'Unable to create your project.' => '프로젝트의 작성에 실패했습니다.', + 'Project updated successfully.' => '프로젝트를 갱신했습니다.', + 'Unable to update this project.' => '프로젝트의 갱신에 실패했습니다.', + 'Unable to remove this project.' => '프로젝트의 삭제에 실패했습니다.', + 'Project removed successfully.' => '프로젝트를 삭제했습니다.', + 'Project activated successfully.' => '프로젝트를 유효로 했습니다.', + 'Unable to activate 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.' => '할일의 갱신에 실패했습니다.', + 'Task updated successfully.' => '할일을 갱신했습니다.', + 'Unable to create your task.' => '할일의 추가에 실패했습니다.', + 'Task created successfully.' => '할일을 추가했습니다.', + 'User created successfully.' => '사용자를 추가했습니다.', + 'Unable to create your user.' => '사용자의 추가에 실패했습니다.', + 'User updated successfully.' => '사용자를 갱신했습니다.', + 'User removed successfully.' => '사용자를 삭제했습니다.', + 'Unable to remove this user.' => '사용자 삭제에 실패했습니다.', + 'Board updated successfully.' => '보드를 갱신했습니다.', + 'Ready' => '준비중', + 'Backlog' => '요구사항', + 'Work in progress' => '진행중', + 'Done' => '완료', + 'Application version:' => '애플리케이션의 버전:', + 'Id' => 'ID', + 'Public link' => '공개 접속 링크', + 'Timezone' => '시간대', + 'Sorry, I didn\'t find this information in my database!' => '데이터베이스에서 정보가 발견되지 않았습니다!', + 'Page not found' => '페이지가 발견되지 않는다', + 'Complexity' => '복잡도', + 'Task limit' => '할일 수 제한', + 'Task count' => '할일 수', + 'User' => '사용자', + 'Comments' => '댓글', + 'Comment is required' => '댓글을 입력하세요', + 'Comment added successfully.' => '의견을 추가했습니다.', + 'Unable to create your comment.' => '댓글의 추가에 실패했습니다.', + 'Due Date' => '마감일', + 'Invalid date' => '날짜가 무효입니다', + 'Automatic actions' => '자동액션 관리', + 'Your automatic action has been created successfully.' => '자동 액션을 작성했습니다.', + 'Unable to create your automatic action.' => '자동 액션의 작성에 실패했습니다.', + 'Remove an action' => '자동 액션의 삭제', + 'Unable to remove this action.' => '자동 액션의 삭제에 실패했습니다.', + 'Action removed successfully.' => '자동 액션의 삭제에 성공했어요.', + 'Automatic actions for the project "%s"' => '"%s" 프로젝트를 위한 자동 액션', + 'Add an action' => '자동 액션 추가', + 'Event name' => '이벤트 이름', + 'Action' => '액션', + 'Event' => '이벤트', + 'When the selected event occurs execute the corresponding action.' => '선택된 이벤트가 발생했을 때 대응하는 액션을 실행한다.', + 'Next step' => '다음 단계', + 'Define action parameters' => '액션의 바로미터', + 'Do you really want to remove this action: "%s"?' => '액션을 삭제하시겠습니까: "%s"?', + 'Remove an automatic action' => '자동 액션의 삭제', + 'Assign the task to a specific user' => '할일 담당자를 할당', + 'Assign the task to the person who does the action' => '액션을 일으킨 사용자를 담당자이자', + 'Duplicate the task to another project' => ' 다른 프로젝트에 할일을 복제하는 ', + 'Move a task to another column' => '할일을 다른 컬럼에 이동하는 ', + 'Task modification' => '할일 변경', + 'Task creation' => '할일을 만들', + 'Closing a task' => '할일을 닫혔다', + 'Assign a color to a specific user' => '색을 사용자에 할당', + 'Position' => '위치', + 'Duplicate to project' => '다른 프로젝트에 복사', + 'Duplicate' => '복사', + 'Link' => '링크', + 'Comment updated successfully.' => '댓글을 갱신했습니다.', + 'Unable to update your comment.' => '댓글의 갱신에 실패했습니다.', + 'Remove a comment' => '댓글 삭제', + 'Comment removed successfully.' => '댓글을 삭제했습니다.', + 'Unable to remove this comment.' => '댓글의 삭제에 실패했습니다.', + 'Do you really want to remove this comment?' => '댓글을 삭제합니까?', + 'Current password for the user "%s"' => '사용자 "%s"의 현재 패스워드', + 'The current password is required' => '현재의 패스워드를 입력하세요', + 'Wrong password' => '패스워드가 다릅니다', + 'Unknown' => '불명', + 'Last logins' => '마지막 로그인', + 'Login date' => '로그인 일시', + 'Authentication method' => '인증 방법', + 'IP address' => 'IP 주소', + 'User agent' => '사용자 에이전트', + 'Persistent connections' => '세션', + 'No session.' => '세션 없음', + 'Expiration date' => '유효기간', + 'Remember Me' => '자동 로그인', + 'Creation date' => '작성일', + 'Everybody' => '모두', + 'Open' => '열림', + 'Closed' => '닫힘', + 'Search' => '검색', + 'Nothing found.' => '결과가 없습니다', + 'Due date' => '마감일', + 'Description' => '설명', + '%d comments' => '%d개의 댓글', + '%d comment' => '%d개의 댓글', + 'Email address invalid' => '메일 주소가 올바르지 않습니다.', + 'Your external account is not linked anymore to your profile.' => '외부 계정과 프로필이 더이상 연결되지 않습니다', + 'Unable to unlink your external account.' => '외부 계정과 연결 해제에 실패하였습니다', + 'External authentication failed' => '외부 인증 실패', + 'Your external account is linked to your profile successfully.' => '외부 계정과 프로필이 성공적으로 연결되었습니다', + 'Email' => '이메일', + 'Task removed successfully.' => '할일을 삭제했습니다.', + 'Unable to remove this task.' => '할일 삭제에 실패하였습니다', + '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' => '색에 바탕을 두고 카테고리를 바꾸었다', + 'Task creation or modification' => '할일의 작성 또는 변경', + 'Category' => '카테고리', + 'Category:' => '카테고리:', + 'Categories' => '카테고리', + 'Your category has been created successfully.' => '카테고리를 작성했습니다.', + 'This category has been updated successfully.' => '카테고리를 갱신했습니다.', + 'Unable to update this category.' => '카테고리의 갱신에 실패했습니다.', + 'Remove a category' => '카테고리의 삭제', + 'Category removed successfully.' => '카테고리를 삭제했습니다.', + 'Unable to remove this category.' => '카테고리를 삭제할 수 없었습니다.', + 'Category modification for the project "%s"' => '"%s" 프로젝트 카테고리 수정', + 'Category Name' => '카테고리 이름', + 'Add a new category' => '카테고리의 추가', + 'Do you really want to remove this category: "%s"?' => '카테고리를 삭제하시겠습니까: "%s"?', + 'All categories' => '모든 카테고리', + 'No category' => '카테고리 없음', + 'The name is required' => '이름을 입력하십시오', + 'Remove a file' => '파일 삭제', + 'Unable to remove this file.' => '파일 삭제에 실패했습니다.', + 'File removed successfully.' => '파일을 삭제했습니다.', + 'Attach a document' => '문서 첨부', + 'Do you really want to remove this file: "%s"?' => '파일 "%s" 을 삭제할까요?', + 'Attachments' => '첨부', + 'Edit the task' => '할일 수정', + 'Add a comment' => '댓글 추가', + 'Edit a comment' => '댓글 수정', + 'Summary' => '개요', + 'Time tracking' => '시간 추적', + 'Estimate:' => '예측:', + 'Spent:' => '경과:', + 'Do you really want to remove this sub-task?' => '서브 할일을 삭제합니까?', + 'Remaining:' => '나머지:', + 'hours' => '시간', + 'estimated' => '예측', + 'Sub-Tasks' => '서브 할일', + 'Add a sub-task' => '서브 할일 추가', + 'Original estimate' => '최초 예측시간', + 'Create another sub-task' => '다음 서브 할일 추가', + 'Time spent' => '경과시간', + 'Edit a sub-task' => '서브 할일을 변경하는 ', + 'Remove a sub-task' => '서브 할일을 삭제하는 ', + 'The time must be a numeric value' => '시간은 숫자로 입력하세요', + 'Todo' => '할일 예정', + 'In progress' => '할일 중', + 'Sub-task removed successfully.' => '서브 할일을 삭제했습니다.', + 'Unable to remove this sub-task.' => '서브 할일의 삭제가 실패했습니다.', + 'Sub-task updated successfully.' => '서브 할일을 갱신했습니다.', + 'Unable to update your sub-task.' => '서브 할일의 경신에 실패했습니다.', + 'Unable to create your sub-task.' => '서브 할일의 추가에 실패했습니다.', + 'Maximum size: ' => '최대: ', + 'Display another project' => '프로젝트 보기', + 'Created by %s' => '작성자 %s', + 'Tasks Export' => '할일 내보내기', + 'Start Date' => '시작일', + 'Execute' => '실행', + 'Task Id' => '할일 ID', + 'Creator' => '작성자', + 'Modification date' => '변경 일', + 'Completion date' => '완료일', + 'Clone' => '복사', + 'Project cloned successfully.' => '프로젝트를 복제했습니다.', + 'Unable to clone this project.' => '프로젝트의 복제에 실패했습니다.', + 'Enable email notifications' => '이메일 알림 설정', + 'Task position:' => '할일 위치:', + 'The task #%d has been opened.' => '할일 #%d가 시작되었습니다', + 'The task #%d has been closed.' => '할일 #%d가 종료되었습니다', + 'Sub-task updated' => '서브 할일 갱신', + 'Title:' => '제목:', + 'Status:' => '상태:', + 'Assignee:' => '담당:', + 'Time tracking:' => '시간 계측:', + 'New sub-task' => '새로운 서브 할일', + 'New attachment added "%s"' => '"%s"의 새로운 첨부 파일', + 'New comment posted by %s' => '"%s"님이 댓글을 추가하였습니다', + 'New comment' => ' 새로운 댓글', + 'Comment updated' => '댓글가 갱신되었습니다', + 'New subtask' => ' 새로운 서브 할일', + 'I only want to receive notifications for these projects:' => '다음 프로젝트의 알림만 받겠습니다:', + 'view the task on Kanboard' => 'Kanboard에서 할일을 본다', + 'Public access' => '공개 접속 설정', + 'Disable public access' => '공개 접속 비활성화', + 'Enable public access' => '공개 접속 활성화', + 'Public access disabled' => '공개 접속 불가', + 'Move the task to another project' => '할일별 프로젝트에 옮기', + 'Move to project' => '다른 프로젝트로 이동', + 'Do you really want to duplicate this task?' => '할일을 복제합니까?', + 'Duplicate a task' => '할일 복사', + 'External accounts' => '외부 계정', + 'Account type' => '계정종류', + 'Local' => '로컬', + 'Remote' => '원격', + 'Enabled' => '활성화', + 'Disabled' => '비활성화', + 'Login:' => '사용자명', + 'Full Name:' => '이름:', + 'Email:' => '이메일:', + 'Notifications:' => '알림:', + 'Notifications' => '알림', + 'Account type:' => '계정종류:', + 'Edit profile' => '프로필 변경', + 'Change password' => '패스워드 변경', + 'Password modification' => '패스워드 변경', + 'External authentications' => '외부 인증', + 'Never connected.' => '접속기록없음', + 'No external authentication enabled.' => '외부 인증이 설정되어 있지 않습니다.', + 'Password modified successfully.' => '패스워드를 변경했습니다.', + 'Unable to change the password.' => '비밀 번호가 변경할 수 없었습니다.', + 'Change category' => '카테고리 수정', + '%s updated the task %s' => '%s이 할일 %s을 갱신 하였습니다', + '%s opened the task %s' => '%s이 할일 %s을 시작시켰습니다', + '%s moved the task %s to the position #%d in the column "%s"' => '%s이 할일%s을 위치#%d컬럼%s로 옮겼습니다', + '%s moved the task %s to the column "%s"' => '%s이 할일 %s을 컬럼 "%s" 로 옮겼습니다', + '%s created the task %s' => '%s이 할일%s을 추가했습니다', + '%s closed the task %s' => '%s이 할일%s을 마쳤습니다', + '%s created a subtask for the task %s' => '%s이 할일%s의 서브 할일을 추가했습니다', + '%s updated a subtask for the task %s' => '%s이 할일%s의 서브 할일을 갱신했습니다', + 'Assigned to %s with an estimate of %s/%sh' => '담당자 %s에게 예상 %s/%sh을 할당했습니다', + 'Not assigned, estimate of %sh' => '예상 %sh가 할당되지 않았습니다', + '%s updated a comment on the task %s' => '%s이 할일%s의 댓글을 수정했습니다', + '%s commented the task %s' => '%s이 할일%s에 댓글을 남겼습니다', + '%s\'s activity' => '%s의 활동', + 'RSS feed' => 'RSS피드', + '%s updated a comment 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을 갱신했습니다', + '%s created the task #%d' => '%s이 할일#%d을 추가했습니다', + '%s closed the task #%d' => '%s이 할일#%d을 닫혔습니다', + '%s opened the task #%d' => '%s이 할일#%d를 오픈했습니다', + 'Activity' => '활동', + 'Default values are "%s"' => '기본 값은 "%s" 입니다', + 'Default columns for new projects (Comma-separated)' => '새로운 프로젝트의 기본 컬럼 (콤마(,)로 분리됨)', + 'Task assignee change' => '담당자의 변경', + '%s changed the assignee of the task #%d to %s' => '%s이 할일 #%d의 담당을 %s로 변경합니다', + '%s changed the assignee of the task %s to %s' => '%s이 할일 %s의 담당을 %s로 변경했습니다', + 'New password for the user "%s"' => '사용자 "%s"의 새로운 패스워드', + 'Choose an event' => '행사의 선택', + 'Create a task from an external provider' => '할일을 외부 서비스로부터 작성하는 ', + 'Change the assignee based on an external username' => '담당자를 외부 서비스에 바탕을 두고 변경하는 ', + 'Change the category based on an external label' => '카테고리를 외부 서비스에 바탕을 두고 변경하는 ', + 'Reference' => '참조', + 'Label' => '라벨', + 'Database' => '데이터베이스', + 'About' => '정보', + 'Database driver:' => '데이터베이스 드라이버:', + 'Board settings' => '기본 설정', + 'Webhook settings' => 'Webhook의 설정', + 'Reset token' => '토큰 리셋', + 'API endpoint:' => 'API엔드 포인트:', + 'Refresh interval for personal board' => '비공개 보드의 갱신 빈도', + 'Refresh interval for public board' => '공개 보드의 갱신 빈도', + 'Task highlight period' => '할일의 하이라이트 기간', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => '최근 수정된 작업 고려 기간(초), (비활성화:0, 기본값:2일)', + 'Frequency in second (60 seconds by default)' => '초당 횟수(기본값:60초)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => '초당 횟수(비활성화:0, 기본값:10초)', + 'Application URL' => '애플리케이션의 URL', + 'Token regenerated.' => '토큰이 다시 생성되었습니다.', + 'Date format' => '데이터 포멧', + 'ISO format is always accepted, example: "%s" and "%s"' => 'ISO 포멧은 항상 가능합니다. 예를 들어: "%s" 와 "%s"', + 'New personal project' => '새 비공개 프로젝트', + 'This project is personal' => '이 프로젝트는 비공개입니다', + 'Add' => '추가', + 'Start date' => '시작시간', + 'Time estimated' => '예상시간', + 'There is nothing assigned to you.' => '할일이 없습니다. 옆사람의 일을 도와주면 어떨까요?', + 'My tasks' => '내 할일', + 'Activity stream' => '활동기록', + 'Dashboard' => '대시보드', + 'Confirmation' => '확인', + 'Webhooks' => 'Webhook', + 'API' => 'API', + 'Create a comment from an external provider' => '외부 서비스로부터 의견을 작성한다', + 'Project management' => '프로젝트 관리', + 'Columns' => '컬럼', + 'Task' => '할일', + 'Percentage' => '비중', + 'Number of tasks' => '할일 수', + 'Task distribution' => '할일 분포', + 'Analytics' => '분석', + 'Subtask' => '서브 할일', + 'User repartition' => '담당자 분포', + 'Clone this project' => '이 프로젝트를 복제하는 ', + 'Column removed successfully.' => '(※)컬럼을 삭제했습니다', + 'Not enough data to show the graph.' => '그래프를 선묘화하려면 나왔지만 부족합니다', + 'Previous' => ' 돌아가', + 'The id must be an integer' => 'id은 숫자가 아니면 안 됩니다', + 'The project id must be an integer' => 'project id은 숫자가 아니면 안 됩니다', + 'The status must be an integer' => 'status는 숫자지 않으면 안 됩니다', + 'The subtask id is required' => 'subtask id가 필요합니다', + 'The subtask id must be an integer' => 'subtask id은 숫자가 아니면 안 됩니다', + 'The task id is required' => 'task id가 필요합니다', + 'The task id must be an integer' => 'task id은 숫자가 아니면 안 됩니다', + 'The user id must be an integer' => 'user id은 숫자가 아니면 안 됩니다', + 'This value is required' => '이 값이 필요합니다', + 'This value must be numeric' => '이 값은 숫자가 아니면 안 됩니다', + 'Unable to create this task.' => '이 할일을 작성할 수 없었습니다', + 'Cumulative flow diagram' => '축적 흐름', + 'Daily project summary' => '하루 프로젝트 개요', + 'Daily project summary export' => '하루 프로젝트 개요의 출력', + 'Exports' => '출력', + 'This export contains the number of tasks per column grouped per day.' => '이 출력은 날짜의 칼람별 할일 수를 집계한 것입니다', + 'Active swimlanes' => '액티브한 스윔레인', + 'Add a new swimlane' => ' 새로운 스윔레인', + 'Default swimlane' => '기본 스윔레인', + 'Do you really want to remove this swimlane: "%s"?' => '스웜레인을 삭제하시겠습니까: "%s"?', + 'Inactive swimlanes' => '인터랙티브한 스윔레인', + 'Remove a swimlane' => '스윔레인의 삭제', + 'Swimlane modification for the project "%s"' => '"%s" 프로젝트의 스웜레인 수정', + 'Swimlane removed successfully.' => '스윔레인을 삭제했습니다.', + 'Swimlanes' => '스윔레인', + 'Swimlane updated successfully.' => '스윔레인을 갱신했습니다.', + 'Unable to remove this swimlane.' => '스윔레인을 삭제할 수 없었습니다.', + 'Unable to update this swimlane.' => '스윔레인을 갱신할 수 없었습니다.', + 'Your swimlane has been created successfully.' => '스윔레인이 작성되었습니다.', + 'Example: "Bug, Feature Request, Improvement"' => '예: "버그, 특성 요청, 향상"', + 'Default categories for new projects (Comma-separated)' => '새로운 프로젝트의 기본 카테고리 (콤마(,)로 구분)', + 'Integrations' => '연계', + 'Integration with third-party services' => '외부 서비스 연계', + 'Subtask Id' => '서브 할일 Id', + 'Subtasks' => '서브 할일', + 'Subtasks Export' => '서브 할일 출력', + 'Task Title' => '할일 제목', + 'Untitled' => '제목 없음', + 'Application default' => '애플리케이션 기본', + 'Language:' => '언어:', + 'Timezone:' => '시간대:', + 'All columns' => '모든 컬럼', + 'Next' => '다음에 ', + '#%d' => '#%d', + 'All swimlanes' => '모든 스윔레인', + 'All colors' => '모든 색', + 'Moved to column %s' => '"%s" 컬럼으로 이동', + 'User dashboard' => '대시보드', + 'Allow only one subtask in progress at the same time for a user' => '한 사용자에 대한 하나의 할일만 진행 중에 가능합니다', + 'Edit column "%s"' => '"%s" 컬럼 수정', + '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?' => '프로젝트의 무엇을 복제합니까?', + 'Disallow login form' => '허가되지 않은 로그인 양식', + 'Start' => '시작', + 'End' => '종료', + 'Task age in days' => '할일이 생긴 시간', + 'Days in this column' => '이 컬럼에 있는 시간', + '%dd' => '%d일', + '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' => '링크의 변경', + 'Opposite label' => '반대의 라벨', + 'Remove a link' => '라벨의 삭제', + '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' => '<1시간', + '%dh' => '%d시간', + 'Expand tasks' => '할일 크게', + 'Collapse tasks' => '할일 작게', + 'Expand/collapse tasks' => '할일 크게/작게', + 'Close dialog box' => '다이얼로그를 닫습니다', + 'Submit a form' => '제출', + 'Board view' => '보드 뷰', + 'Keyboard shortcuts' => '키보드 숏 컷', + 'Open board switcher' => '보드 전환을 열', + 'Application' => '애플리케이션', + 'Compact view' => '컴팩트 뷰', + 'Horizontal scrolling' => '세로 스크롤', + 'Compact/wide view' => '컴팩트/와이드 뷰', + 'Currency' => '통화', + 'Personal project' => '개인 프로젝트', + 'AUD - Australian Dollar' => 'AUD - 호주 달러', + 'CAD - Canadian Dollar' => 'CAD -캐나다 달러', + 'CHF - Swiss Francs' => 'CHF - 스위스 프랑', + 'Custom Stylesheet' => '커스텀 스타일 시트', + 'EUR - Euro' => 'EUR - 유로', + 'GBP - British Pound' => 'GBP - 영국 파운드', + 'INR - Indian Rupee' => 'INR - 인도 루피', + 'JPY - Japanese Yen' => 'JPY - 일본 엔', + 'NZD - New Zealand Dollar' => 'NZD - 뉴질랜드 달러', + 'PEN - Peruvian Sol' => 'PEN - 페루 솔', + 'RSD - Serbian dinar' => 'RSD - 세르비아 디나르', + 'CNY - Chinese Yuan' => 'CNY - 중국 위안', + 'USD - US Dollar' => 'USD - 미국 달러', + 'VES - Venezuelan Bolívar' => 'VES - 베네수엘라 볼리바르', + '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' => '이동 전 컬럼', + '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' => '현재의 기축 통화', + 'Reference currency' => '기축 통화', + 'The currency rate has been added successfully.' => '통화가 성공적으로 추가되었습니다', + 'Unable to add this currency rate.' => '이 통화 환율을 추가할 수 없습니다.', + 'Webhook URL' => 'Webhook URL', + '%s removed the assignee of the task %s' => '%s이 할일 %s의 담당을 삭제했습니다', + 'Information' => '정보', + 'Check two factor authentication code' => '2단 인증을 체크한다', + 'The two factor authentication code is not valid.' => '2단 인증 코드는 무효입니다.', + 'The two factor authentication code is valid.' => '2단 인증 코드는 유효합니다.', + 'Code' => '코드', + 'Two factor authentication' => '2단 인증', + 'This QR code contains the key URI: ' => 'QR 코드는 키 URI를 포함합니다: ', + 'Check my code' => '코드 체크', + 'Secret key: ' => '비밀키: ', + 'Test your device' => '디바이스 테스트', + 'Assign a color when the task is moved to a specific column' => '상세 컬럼으로 이동할 할일의 색깔을 지정하세요', + '%s via Kanboard' => '%s via E-board', + 'Burndown chart' => '번다운 차트', + 'This chart show the task complexity over the time (Work Remaining).' => '이 차트는 시간에 따른 할일의 복잡성을 보여줍니다. (남아있는 일)', + 'Screenshot taken %s' => '스크린샷_%s', + 'Add a screenshot' => '스크린샷 추가', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => '스크린샷을 CTRL+V 혹은 ⌘+V를 눌러 붙여넣기', + 'Screenshot uploaded successfully.' => '스크린샷을 업로드하였습니다', + 'SEK - Swedish Krona' => 'SEK - 스위스 크로나', + 'Identifier' => '식별자', + 'Disable two factor authentication' => '이중 인증 비활성화', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => '"%s" 담당자의 이중 인증을 비활성화 하시겠습니까?', + 'Edit link' => '링크 수정', + 'Start to type task title...' => '할일 제목을 입력하세요', + 'A task cannot be linked to itself' => '할일을 자기자신에게 연결할 수 없습니다', + 'The exact same link already exists' => '동일한 링크가 이미 존재합니다', + 'Recurrent task is scheduled to be generated' => '반복 할일이 생성된 예정입니다.', + 'Score' => '점수', + 'The identifier must be unique' => '식별자는 유일해야 합니다', + 'This linked task id doesn\'t exists' => '연결된 할일 ID가 존재하지 않습니다', + 'This value must be alphanumeric' => '글자와 숫자만 가능합니다', + 'Edit recurrence' => '반복 수정', + 'Generate recurrent task' => '반복 할일 생성', + 'Trigger to generate recurrent task' => '반복 할일 생성 트리거', + 'Factor to calculate new due date' => '새로운 종료날짜 계산', + 'Timeframe to calculate new due date' => '종료날짜 계산 단위', + 'Base date to calculate new due date' => '새로운 기본 종료날짜 계산', + 'Action date' => '시작날짜', + 'Base date to calculate new due date: ' => '새로운 기본 종료날짜 계산: ', + 'This task has created this child task: ' => '이 할일은 하위 할일을 만들었습니다.', + 'Day(s)' => '일', + 'Existing due date' => '기존 종료날짜', + 'Factor to calculate new due date: ' => '새로운 종료날짜 계산: ', + 'Month(s)' => '월', + 'This task has been created by: ' => '할일을 만들었습니다: ', + 'Recurrent task has been generated:' => '반복 할일이 생성되었습니다', + 'Timeframe to calculate new due date: ' => '종료날짜 계산 단위', + 'Trigger to generate recurrent task: ' => '반복 할일 생성 트리거', + 'When task is closed' => '할일을 마쳤을때', + 'When task is moved from first column' => '할일이 첫번째 컬럼으로 옮겨졌을때', + 'When task is moved to last column' => '할일이 마지막 컬럼으로 옮겨졌을때', + 'Year(s)' => '년', + 'Project settings' => '프로젝트 설정', + 'Automatically update the start date' => '시작일에 자동 갱신', + 'iCal feed' => 'iCal 피드', + 'Preferences' => '우선권', + 'Security' => '보안', + 'Two factor authentication disabled' => '이중 인증이 비활성화 되었습니다', + 'Two factor authentication enabled' => '이중 인증이 활성화 되었습니다', + 'Unable to update this user.' => '담당자 갱신이 가능합니다', + 'There is no user management for personal projects.' => '비밀 프로젝트의 관리 담당자가 없습니다', + 'User that will receive the email' => '그 담당자가 이메일을 수신할 것입니다', + 'Email subject' => '이메일 제목', + 'Date' => '날짜', + 'Add a comment log when moving the task between columns' => '컬럼 중간의 할일이 이동할 때 의견 달기', + 'Move the task to another column when the category is changed' => '카테고리 변경시 할일을 다른 컬럼으로 이동', + 'Send a task by email to someone' => '할일을 이메일로 보내기', + 'Reopen a task' => '할일 다시 시작', + 'Notification' => '알림', + '%s moved the task #%d to the first swimlane' => '%s가 할일 #%d를 첫번째 스웜레인으로 이동시켰습니다', + 'Swimlane' => '스윔레인', + '%s moved the task %s to the first swimlane' => '%s가 할일 %s를 첫번째 스웜레인으로 이동시켰습니다', + '%s moved the task %s to the swimlane "%s"' => '%s가 할일 %s를 %s 스웜레인으로 이동시켰습니다', + 'This report contains all subtasks information for the given date range.' => '해당 기간의 모든 서브 할일 정보가 보고서에 포함됩니다', + 'This report contains all tasks information for the given date range.' => '해당 기간의 모든 할일 정보가 보고서에 포함됩니다', + 'Project activities for %s' => '%s의 프로젝트 활성화', + 'view the board on Kanboard' => 'kanboard로 보드 보기', + 'The task has been moved to the first swimlane' => '할일이 첫번째 스웜라인으로 이동되어 있습니다', + 'The task has been moved to another swimlane:' => '할일이 다른 스웜라인으로 이동되어 있습니다', + 'New title: %s' => '제목 변경: %s', + 'The task is not assigned anymore' => '담당자 없음', + 'New assignee: %s' => '담당자 변경: %s', + 'There is no category now' => '카테고리 없음', + 'New category: %s' => '카테고리 변경: %s', + 'New color: %s' => '색깔 변경: %s', + 'New complexity: %d' => '복잡도 변경: %d', + 'The due date has been removed' => '마감날짜 삭제', + 'There is no description anymore' => '설명 없음', + 'Recurrence settings has been modified' => '반복할일 설정 수정', + 'Time spent changed: %sh' => '경과시간 변경: %s시간', + 'Time estimated changed: %sh' => '%s시간으로 예상시간 변경', + 'The field "%s" has been updated' => '%s 필드가 갱신되어 있습니다', + 'The description has been modified:' => '설명이 수정되어 있습니다: ', + 'Do you really want to close the task "%s" as well as all subtasks?' => '할일 "%s"과 서브 할일을 모두 마치시겠습니까?', + 'I want to receive notifications for:' => '다음의 알림을 받기를 원합니다:', + 'All tasks' => '모든 할일', + 'Only for tasks assigned to me' => '내가 담당자인 일', + 'Only for tasks created by me' => '내가 만든 일', + 'Only for tasks created by me and tasks assigned to me' => '내가 만들었거나 내가 담당자인 일', + '%%Y-%%m-%%d' => '%%Y-%%m-%%d', + 'Total for all columns' => '모든 컬럼', + 'You need at least 2 days of data to show the chart.' => '차트를 보기 위하여 최소 2일의 데이터가 필요합니다', + '<15m' => '<15분', + '<30m' => '<30분', + 'Stop timer' => '타이머 정지', + 'Start timer' => '타이머 시작', + 'My activity stream' => '내 활동기록', + 'Search tasks' => '할일 찾기', + 'Reset filters' => '필터 리셋', + 'My tasks due tomorrow' => '내일까지 내 할일', + 'Tasks due today' => '오늘까지 할일', + 'Tasks due tomorrow' => '내일까지 할일', + 'Tasks due yesterday' => '어제까지 할일', + 'Closed tasks' => '닫힌 할일', + 'Open tasks' => '열린 할일', + 'Not assigned' => '담당자가 없는 일', + 'View advanced search syntax' => '추가 검색 문법보기', + 'Overview' => '개요', + 'Board/Calendar/List view' => '보드/달력/리스트 보기', + 'Switch to the board view' => '보드 보기로 전환', + 'Switch to the list view' => '리스트 보기로 전환', + 'Go to the search/filter box' => '검색/필터 박스로 이동', + 'There is no activity yet.' => '활동이 없습니다', + 'No tasks found.' => '할일을 찾을 수 없습니다', + 'Keyboard shortcut: "%s"' => '쉬운 키보드: "%s"', + 'List' => '목록', + 'Filter' => '필터', + 'Advanced search' => '검색 문법', + 'Example of query: ' => '문법 예제 ', + 'Search by project: ' => '프로젝트로 찾기 ', + 'Search by column: ' => '컬럼으로 찾기 ', + 'Search by assignee: ' => '담당자로 찾기 ', + 'Search by color: ' => '색깔로 찾기 ', + 'Search by category: ' => '카테고리로 찾기 ', + 'Search by description: ' => '설명으로 찾기 ', + 'Search by due date: ' => '마감날짜로 찾기 ', + 'Average time spent in each column' => '각 칼럼의 평균 소요시간', + 'Average time spent' => '평균 소요시간', + 'This chart shows the average time spent in each column for the last %d tasks.' => '마지막 %d 할일의 컬럼 평균 소요시간을 차트에 표시합니다', + 'Average Lead and Cycle time' => '평균 Lead and Cycle 시간', + 'Average lead time: ' => '평균 lead 시간', + 'Average cycle time: ' => '평균 cycle 시간', + 'Cycle Time' => '사이클 시간', + 'Lead Time' => '리드 시간', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => '마지막 %d 할일의 평균 리드와 사이클 시간을 차트에 표시합니다', + 'Average time into each column' => '각 컬럼의 평균 시간', + 'Lead and cycle time' => '리드와 사이클 시간', + 'Lead time: ' => '리드 시간: ', + 'Cycle time: ' => '사이클 시간: ', + 'Time spent in each column' => '각 컬럼에서 걸린 시간', + 'The lead time is the duration between the task creation and the completion.' => '리드 시간은 할일의 생성부터 완료까지의 기간입니다', + 'The cycle time is the duration between the start date and the completion.' => '사이클 시간은 할일의 시작일부터 완료까지의 기간입니다', + 'If the task is not closed the current time is used instead of the completion date.' => '할일이 종료되지 않았다면, 완료 시간 대신 현재 시간이 사용됩니다', + 'Set the start date automatically' => '자동으로 시작 날짜를 설정합니다', + 'Edit Authentication' => '계정 수정', + 'Remote user' => '원격 담당자', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => '예를 들어 LDAP, Google, Github 계정같은 원격 담당자의 비밀번호는 칸반보드 데이터베이스에 저장하지 않습니다', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => '만약 "로그인 폼 거절"에 체크한다면, 로그인 폼에 접근할 자격이 무시됩니다', + 'Default task color' => '기본 할일 색', + 'This feature does not work with all browsers.' => '이 기능은 일부 브라우저에서 작동하지 않습니다', + 'There is no destination project available.' => '가능한 목적 프로젝트가 없습니다', + 'Trigger automatically subtask time tracking' => '자동 서브 할일 시간 트래킹 트리거', + 'Include closed tasks in the cumulative flow diagram' => '누적 플로우 다이어그램에 종료된 할일을 포함합니다', + 'Current swimlane: %s' => '현재 스웜라인: %s', + 'Current column: %s' => '현재 컬럼: %s', + 'Current category: %s' => '현재 카테고리: %s', + 'no category' => '카테고리 아님', + 'Current assignee: %s' => '현재 할당자: %s', + 'not assigned' => '할당되지 않음', + 'Author:' => '글쓴이:', + 'contributors' => '기여자', + 'License:' => '라이센스:', + 'License' => '라이센스', + 'Enter the text below' => '아랫글로 들어가기', + 'Start date:' => '시작일:', + 'Due date:' => '만기일:', + 'People who are project managers' => '프로젝트 매니저', + 'People who are project members' => '프로젝트 멤버', + 'NOK - Norwegian Krone' => 'NOK - 노르웨이 크로네', + 'Show this column' => '컬럼 보이기', + 'Hide this column' => '컬럼 숨기기', + 'End date' => '종료 날짜', + 'Users overview' => '유저 전체보기', + 'Members' => '멤버', + 'Shared project' => '프로젝트 공유', + 'Project managers' => '프로젝트 매니저', + 'Projects list' => '프로젝트 리스트', + 'End date:' => '날짜 수정', + 'Change task color when using a specific task link' => '특정 할일 링크를 사용할때 할일의 색깔 변경', + 'Task link creation or modification' => '할일 링크 생성 혹은 수정', + 'Milestone' => '마일스톤', + 'Reset the search/filter box' => '찾기/필터 박스 초기화', + 'Documentation' => '문서', + 'Author' => '글쓴이', + 'Version' => '버전', + 'Plugins' => '플러그인', + 'There is no plugin loaded.' => '플러그인이 로드되지 않았습니다', + 'My notifications' => '내 알림', + 'Custom filters' => '사용자 정의 필터', + 'Your custom filter has been created successfully.' => '사용자 정의 필터가 성공적으로 생성되었습니다', + 'Unable to create your custom filter.' => '사용자 정의 필터 생성 비활성화', + 'Custom filter removed successfully.' => '사용자 정의 필터가 성공적으로 삭제되었습니다', + 'Unable to remove this custom filter.' => '정의 필터 삭제 비활성화', + 'Edit custom filter' => '정의 필터 수정', + 'Your custom filter has been updated successfully.' => '사용자 정의 필터가 성공적으로 수정되었습니다', + 'Unable to update custom filter.' => '정의 필터 수정 비활성화', + 'Web' => '웹', + 'New attachment on task #%d: %s' => '할일 #%d의 새로운 첨부파일: %s', + 'New comment on task #%d' => '할일 #%d에 새로운 댓글이 달렸습니다', + 'Comment updated on task #%d' => '할일 #%d에 댓글이 갱신 되었습니다', + 'New subtask on task #%d' => '서브 할일 #%d이 갱신 되었습니다', + 'Subtask updated on task #%d' => '서브 할일 #%d가 갱신 되었습니다', + 'New task #%d: %s' => '할일 #%d: %s이 추가되었습니다', + 'Task updated #%d' => '할일 #%d이 갱신 되었습니다', + 'Task #%d closed' => '할일 #%d를 마쳤습니다', + 'Task #%d opened' => '할일 #%d가 시작되었습니다', + 'Column changed for task #%d' => '할일 #%d의 컬럼이 변경되었습니다', + 'New position for task #%d' => '할일 #%d이 새로운 위치에 등록되었습니다', + 'Swimlane changed for task #%d' => '#%d 할일의 스웜라인이 변경됩니다', + 'Assignee changed on task #%d' => '#%d 할일의 담당자가 변경됩니다', + '%d overdue tasks' => '할일의 기한이 %d일 지났습니다', + 'No notification.' => '알림이 없습니다', + 'Mark all as read' => '모두 읽음', + 'Mark as read' => '읽음', + 'Total number of tasks in this column across all swimlanes' => '모든 스웜라인 칼럼의 할일 수', + 'Collapse swimlane' => '스웜라인 붕괴', + 'Expand swimlane' => '스웜라인 확장', + 'Add a new filter' => '새로운 필터 추가', + 'Share with all project members' => '모든 프로젝트 맴버 공유', + 'Shared' => '공유', + 'Owner' => '소유자', + 'Unread notifications' => '읽지않은 알림', + 'Notification methods:' => '알림 방법', + 'Unable to read your file' => '파일을 읽을 수 없습니다', + '%d task(s) have been imported successfully.' => '%d 할일이 성공적으로 추가되었습니다', + 'Nothing has been imported!' => '추가가 되지 않았습니다!', + 'Import users from CSV file' => 'CSV 파일에서 사용자 가져오기', + '%d user(s) have been imported successfully.' => '%d 사용자가 성공적으로 추가되었습니다', + 'Comma' => '콤마', + 'Semi-colon' => '세미콜론', + 'Tab' => '탭', + 'Vertical bar' => '세로줄', + 'Double Quote' => '이중 따옴표', + 'Single Quote' => '따옴표', + '%s attached a file to the task #%d' => '%s가 할일 #%d에 파일을 추가하였습니다', + 'There is no column or swimlane activated in your project!' => '프로젝트에 활성화된 컬럼이나 스웜라인이 없습니다', + 'Append filter (instead of replacement)' => '필터 (변경 대신)추가', + 'Append/Replace' => '추가/변경', + 'Append' => '추가', + 'Replace' => '변경', + 'Import' => '가져오기', + 'Change sorting' => '정렬 변경', + 'Tasks Importation' => '할일 가져오기', + 'Delimiter' => '구분자', + 'Enclosure' => '동봉', + 'CSV File' => 'CSV 파일', + 'Instructions' => '명령', + 'Your file must use the predefined CSV format' => '파일은 미리 정의된 CVS 형식이어야 합니다', + 'Your file must be encoded in UTF-8' => '파일은 UTF-8로 인코딩되어야 합니다', + 'The first row must be the header' => '첫 줄은 헤더이어야 합니다', + 'Duplicates are not verified for you' => '사본이 허락되지 않습니다', + 'The due date must use the ISO format: YYYY-MM-DD' => '만기일은 ISO 형식이어야 합니다: YYYY-MM-DD', + 'Download CSV template' => 'CVS 탬플릿 내려받기', + 'No external integration registered.' => '설정이 되어있지 않습니다', + 'Duplicates are not imported' => '사본을 가져올 수 없습니다', + 'Usernames must be lowercase and unique' => '사용자 이름은 소문자이며 유일해야 합니다', + 'Passwords will be encrypted if present' => '비밀번호는 암호화됩니다', + '%s attached a new file to the task %s' => '%s이 새로운 파일을 할일 %s에 추가했습니다', + 'Link type' => '링크 형식', + 'Assign automatically a category based on a link' => '링크 기반 자동 카테고리 할당', + 'BAM - Konvertible Mark' => 'BAM - 보스니아 마르카', + 'Assignee Username' => '담당자의 사용자이름', + 'Assignee Name' => '당장자 이름', + 'Groups' => '그룹', + 'Members of %s' => '%s의 맴버', + 'New group' => '새로운 그룹', + 'Group created successfully.' => '그룹이 성공적으로 생성되었습니다', + 'Unable to create your group.' => '그룹 생성 비활성화', + 'Edit group' => '그룹 편집', + 'Group updated successfully.' => '그룹이 성공적으로 수정되었습니다', + 'Unable to update your group.' => '그룹 수정 비활성화', + 'Add group member to "%s"' => '%s 그룹 맴버 추가', + 'Group member added successfully.' => '그룹 맴버가 성공적으로 추가되었습니다', + 'Unable to add group member.' => '그룹 맴버 추가 비활성화', + 'Remove user from group "%s"' => '%s 그룹 사용자 삭제', + 'User removed successfully from this group.' => '그룹 사용자가 성공적으로 삭제되었습니다', + 'Unable to remove this user from the group.' => '그룹 사용자 삭제 비활성화', + 'Remove group' => '그룹 삭제', + 'Group removed successfully.' => '그룹이 성공적으로 삭제되었습니다', + 'Unable to remove this group.' => '그룹 삭제 비활성화', + 'Project Permissions' => '프로젝트 권한', + 'Manager' => '매니저', + 'Project Manager' => '프로젝트 매니저', + 'Project Member' => '프로젝트 멤버', + 'Project Viewer' => '프로젝트 뷰어', + 'Your account is locked for %d minutes' => '%d분 동안 계정이 잠깁니다', + 'Invalid captcha' => '잘못된 보안 문자', + 'The name must be unique' => '이름은 유일해야 합니다', + 'View all groups' => '모든그룹보기', + 'There is no user available.' => '가능한 사용자가 없습니다', + 'Do you really want to remove the user "%s" from the group "%s"?' => '"%s" 사용자를 "%s" 에서 삭제하시겠습니까?', + 'There is no group.' => '그룹이 없습니다', + 'Add group member' => '멤버추가', + 'Do you really want to remove this group: "%s"?' => '그룹을 삭제하시겠습니까: "%s"?', + 'There is no user in this group.' => '이 그룹에는 사용자가 없습니다', + 'Permissions' => '권한', + 'Allowed Users' => '사용자 승인', + 'No specific user has been allowed.' => '구체적으로 승인된 사용자가 없습니다', + 'Role' => '역할', + 'Enter user name...' => '사용자 이름을 입력합니다...', + 'Allowed Groups' => '승인된 그룹', + 'No group has been allowed.' => '구체적으로 승인된 그룹이 없습니다', + 'Group' => '그룹', + 'Group Name' => '그룹명', + 'Enter group name...' => '그룹명을 입력합니다...', + 'Role:' => '역할: ', + 'Project members' => '프로젝트 멤버', + '%s mentioned you in the task #%d' => '#%d 할일에서 %s가 당신을 언급하였습니다', + '%s mentioned you in a comment on the task #%d' => '#%d 할일에서 %s가 당신의 댓글을 언급하였습니다', + 'You were mentioned in the task #%d' => '#%d 할일에서 당신이 언급되었습니다', + 'You were mentioned in a comment on the task #%d' => '할일 #%d의 댓글에서 언급되었습니다', + 'Estimated hours: ' => '예상 시간: ', + 'Actual hours: ' => '실제 시간: ', + 'Hours Spent' => '소요 시간', + 'Hours Estimated' => '예상 시간', + 'Estimated Time' => '예상 시간', + 'Actual Time' => '실제 시간', + 'Estimated vs actual time' => '예상 vs 실제 시간', + 'RUB - Russian Ruble' => 'RUB - 러시아 루블', + 'Assign the task to the person who does the action when the column is changed' => '컬럼이 변경되면 액션하지 않는 사람에게 할일을 할당합니다', + 'Close a task in a specific column' => '상세 컬럼의 할일을 종료합니다', + 'Time-based One-time Password Algorithm' => '시간에 기반한 1회용 패스워드 알고리즘', + 'Two-Factor Provider: ' => '이중 인증: ', + 'Disable two-factor authentication' => '이중 인증 비활성화', + 'Enable two-factor authentication' => '이중 인증 활성화', + 'There is no integration registered at the moment.' => '현재 등록된 통합이 없습니다.', + 'Password Reset for Kanboard' => 'Kanboard의 비밀번호 초기화', + 'Forgot password?' => '비밀번호 찾기', + 'Enable "Forget Password"' => '"비밀번호 분실" 활성화', + 'Password Reset' => '비밀번호 초기화', + 'New password' => '새로운 비밀번호', + 'Change Password' => '비밀번호 변경', + 'To reset your password click on this link:' => '비밀번호를 초기화 하시려면 링크를 눌러주세요:', + 'Last Password Reset' => '마지막 비밀번호 초기화', + 'The password has never been reinitialized.' => '비밀번호가 초기화되지 않았습니다', + 'Creation' => '생성', + 'Expiration' => '만료', + 'Password reset history' => '비밀번호 초기화 기록', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => '컬럼 "%s"와 스웜라인 "%s"의 모든 할일이 성공적으로 종료되었습니다', + 'Do you really want to close all tasks of this column?' => '이 컬럼의 모든 할일을 종료 하시겠습니까?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '컬럼 "%s"와 스웜라인 "%s"의 할일 %d가 종료될 것입니다', + 'Close all tasks in this column and this swimlane' => '컬럼의 모든 할일 마치기', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => '프로젝트 알림 방법으로 플러그인이 등록되지 않았습니다. 각각의 알림을 프로파일에서 설정하실 수 있습니다', + 'My dashboard' => '대시보드', + 'My profile' => '프로필', + 'Project owner: ' => '프로젝트 소유자:', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => '프로젝트 구분자는 선택사항이며, 숫자와 문자만 가능합니다. 예: MYPROJECT.', + 'Project owner' => '프로젝트 소유자', + 'Personal projects do not have users and groups management.' => '비밀 프로젝트는 사용자나 관리 그룹이 소유하지 않습니다', + 'There is no project member.' => '프로젝트 맴버가 없습니다', + 'Priority' => '우선순위', + 'Task priority' => '할일의 우선순위', + 'General' => '일반적인', + 'Dates' => '날짜', + 'Default priority' => '기본 우선순위', + 'Lowest priority' => '낮은 우선순위', + 'Highest priority' => '높은 우선순위', + 'Close a task when there is no activity' => '활동이 없는 할일을 종료합니다', + 'Duration in days' => '기간', + 'Send email when there is no activity on a task' => '활동이 없는 할일을 이메일로 보냅니다', + 'Unable to fetch link information.' => '링크 정보 가져오기 비활성화', + 'Daily background job for tasks' => '할일의 일일 배경 작업 ', + 'Auto' => '자동', + 'Related' => '연관된', + 'Attachment' => '첨부', + 'Web Link' => '웹 링크', + 'External links' => '외부 링크', + 'Add external link' => '외부 링크 추가', + 'Type' => '타입', + 'Dependency' => '의존', + 'Add internal link' => '내부 링크 추가', + 'Add a new external link' => '새로운 외부 링크 추가', + 'Edit external link' => '외부 링크 수정', + 'External link' => '외부 링크', + 'Copy and paste your link here...' => '여기에 링크를 복사/붙여넣기', + 'URL' => '인터넷 주소', + 'Internal links' => '내부 링크', + 'Assign to me' => '나에게 할당', + 'Me' => '나', + 'Do not duplicate anything' => '복사할까요?', + 'Projects management' => '프로젝트 관리', + 'Users management' => '사용자 관리', + 'Groups management' => '그룹 관리', + 'Create from another project' => '다른 프로젝트 생성', + 'open' => '시작', + 'closed' => '종료', + 'Priority:' => '우선순위:', + 'Reference:' => '참고:', + 'Complexity:' => '복합:', + 'Swimlane:' => '스웜라인:', + 'Column:' => '컬럼:', + 'Position:' => '위치:', + 'Creator:' => '생성자:', + 'Time estimated:' => '예상 시간:', + '%s hours' => '%s 시간', + 'Time spent:' => '소요 시간:', + 'Created:' => '생성:', + 'Modified:' => '수정;', + 'Completed:' => '완료:', + 'Started:' => '시작:', + 'Moved:' => '이동:', + 'Task #%d' => '할일 #%d', + 'Time format' => '시간 형식', + 'Start date: ' => '시작일: ', + 'End date: ' => '종료일: ', + 'New due date: ' => '새로운 만기일: ', + 'Start date changed: ' => '변경된 시작일: ', + 'Disable personal projects' => '비밀 프로젝트 비활성화', + 'Do you really want to remove this custom filter: "%s"?' => '정의 필터를 삭제하시겠습니까: "%s"?', + 'Remove a custom filter' => '정의 필터 삭제', + 'User activated successfully.' => '사용자가 성공적으로 활성화되었습니다', + 'Unable to enable this user.' => '이 사용자를 활성화할 수 없습니다.', + 'User disabled successfully.' => '사용자가 성공적으로 비활성화되었습니다', + 'Unable to disable this user.' => '이 사용자를 비활성화할 수 없습니다.', + 'All files have been uploaded successfully.' => '모든 파일이 성공적으로 업로드되었습니다', + 'The maximum allowed file size is %sB.' => '업로드 파일의 최대 크기는 %sB 입니다', + 'Drag and drop your files here' => '파일을 이곳으로 끌고오기', + 'choose files' => '파일 선택', + 'View profile' => '프로파일 보기', + 'Two Factor' => '이중', + 'Disable user' => '사용자 비활성화', + 'Do you really want to disable this user: "%s"?' => '사용자를 비활성화 시키겠습니까: "%s"?', + 'Enable user' => '사용자 활성화', + 'Do you really want to enable this user: "%s"?' => '사용자를 활성화 시키겠습니까: "%s"?', + 'Download' => '내려받기', + 'Uploaded: %s' => '올리기: %s', + 'Size: %s' => '크기: %s', + 'Uploaded by %s' => '%s로 올리기', + 'Filename' => '파일 이름', + 'Size' => '크기', + 'Column created successfully.' => '컬럼이 성공적으로 생성되었습니다', + 'Another column with the same name exists in the project' => '프로젝트에 동일한 이름의 컬럼이 있습니다', + 'Default filters' => '기본 필터', + 'Your board doesn\'t have any columns!' => '보드에 컬럼이 존재하지 않습니다', + 'Change column position' => '컬럼 위치 변경', + 'Switch to the project overview' => '프로젝트 개요로 변경', + 'User filters' => '사용자 필터', + 'Category filters' => '카테고리 필터', + 'Upload a file' => '파일 업로드', + 'View file' => '파일 보기', + 'Last activity' => '마지막 행동', + 'Change subtask position' => '서브 할일 위치 변경', + 'This value must be greater than %d' => '이 값은 %d보다 커야 합니다', + 'Another swimlane with the same name exists in the project' => '프로젝트에 같은 이름의 스웜라인이 존재합니다', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => '예: https://example.kanboard.org/ (절대적 URLs을 생성하는데 사용됨)', + 'Actions duplicated successfully.' => '행동이 성공적으로 복제되었습니다', + 'Unable to duplicate actions.' => '행동 복제 비활성화', + 'Add a new action' => '새로운 행동 추가', + 'Import from another project' => '다른 프로젝트에서 가져오기', + 'There is no action at the moment.' => '현재 행동이 없습니다', + 'Import actions from another project' => '다른 프로젝트에서 행동 가져오기', + 'There is no available project.' => '사용 가능한 프로젝트가 없습니다', + 'Local File' => '로컬 파일', + 'Configuration' => '구성', + 'PHP version:' => 'PHP 버전:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => '운영체제 버전:', + 'Database version:' => '데이터베이스 버전:', + 'Browser:' => '브라우저:', + 'Task view' => '할일 보기', + 'Edit task' => '할일 수정', + 'Edit description' => '설명 수정', + 'New internal link' => '새로운 내부 링크', + 'Display list of keyboard shortcuts' => '키보드 숏컷 리스트 보여주기', + 'Avatar' => '아바타', + 'Upload my avatar image' => '아바타 이미지 올리기', + 'Remove my image' => '이미지 삭제', + 'The OAuth2 state parameter is invalid' => 'OAuth2 상태값이 올바르지 않습니다', + 'User not found.' => '사용자를 찾을 수 없습니다', + 'Search in activity stream' => '활성 스트림 찾기', + 'My activities' => '나의 활동', + 'Activity until yesterday' => '어제까지의 활동', + 'Activity until today' => '오늘까지의 활동', + 'Search by creator: ' => '생성자로 검색: ', + 'Search by creation date: ' => '생성 날짜로 검색: ', + 'Search by task status: ' => '할일 상태로 검색: ', + 'Search by task title: ' => '할일 제목으로 검색: ', + 'Activity stream search' => '활동 스트림 검색', + 'Projects where "%s" is manager' => '"%s" 관리자의 프로젝트', + 'Projects where "%s" is member' => '"%s" 맴버의 프로젝트', + 'Open tasks assigned to "%s"' => '"%s"에게 할당된 할일 시작하기', + 'Closed tasks assigned to "%s"' => '"%s"에게 할당된 할일 종료하기', + 'Assign automatically a color based on a priority' => '우선순위로 색깔 자동 지정', + 'Overdue tasks for the project(s) "%s"' => '"%s" 프로젝트의 기한이 지난 할일', + 'Upload files' => '파일 올리기', + 'Installed Plugins' => '설치된 플러그인', + 'Plugin Directory' => '플러그인 폴더', + 'Plugin installed successfully.' => '플러그인이 성공적으로 설치 되었습니다.', + 'Plugin updated successfully.' => '플러그인이 성공적으로 갱신 되었습니다.', + 'Plugin removed successfully.' => '플러그인이 성공적으로 삭제 되었습니다.', + 'Subtask converted to task successfully.' => '서브 할일이 할일로 성공적으로 변경 되었습니다.', + 'Unable to convert the subtask.' => '서브 할일로 변경할 수 없습니다.', + 'Unable to extract plugin archive.' => '플러그인 아카이브를 추출할 수 없습니다.', + 'Plugin not found.' => '플러그인을 찾을 수 없습니다.', + 'You don\'t have the permission to remove this plugin.' => '이 플러그인의 삭제 권한이 없습니다.', + 'Unable to download plugin archive.' => '플러그인 아카이브를 다운로드할 수 없습니다.', + 'Unable to write temporary file for plugin.' => '플러그인의 임시 파일을 기록할 수 없습니다.', + 'Unable to open plugin archive.' => '플러그인 아카이브를 열수 없습니다.', + 'There is no file in the plugin archive.' => '플러그인 아카이브에 파일이 존재하지 않습니다.', + 'Create tasks in bulk' => '대량의 할일 만들기', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => '칸보드 인스턴스가 사용자 인터페이스에서 플러그인을 설치하도록 구성되지 않았습니다', + 'There is no plugin available.' => '사용 가능한 플러그인이 없습니다.', + 'Install' => '설치', + 'Update' => '갱신', + 'Up to date' => '날짜 갱신', + 'Not available' => '사용 불가능', + 'Remove plugin' => '플러그인 삭제', + 'Do you really want to remove this plugin: "%s"?' => '정말로 플러그인을 삭제하시겠습니까: "%s"?', + 'Uninstall' => '삭제', + 'Listing' => '목록', + 'Metadata' => '메타데이터', + 'Manage projects' => '프로젝트 관리', + 'Convert to task' => '할일로 변경하기', + 'Convert sub-task to task' => '서브 할일을 할일로 변경하기', + 'Do you really want to convert this sub-task to a task?' => '정말로 서브 할일을 할일로 변경하시겠습니까?', + 'My task title' => '나의 할일 제목', + 'Enter one task by line.' => '정확히 하나의 할일로 진입', + 'Number of failed login:' => '로그인 실패 횟수:', + 'Account locked until:' => '다음동안 계정이 잠겼습니다:', + 'Email settings' => '이메일 설정', + 'Email sender address' => '이메일 보낸이 주소', + 'Email transport' => '이메일 전송', + 'Webhook token' => 'Webhook토큰', + 'Project tags management' => '프로젝트 태그 관리', + 'Tag created successfully.' => '태그가 성공적으로 생성되었습니다.', + 'Unable to create this tag.' => '태그를 생성할 수 없습니다.', + 'Tag updated successfully.' => '태그가 성공적으로 수정되었습니다.', + 'Unable to update this tag.' => '태그를 수정할 수 없습니다.', + 'Tag removed successfully.' => '태그가 성공적으로 삭제되었습니다.', + 'Unable to remove this tag.' => '태그를 삭제할 수 없습니다.', + 'Global tags management' => '전역 태그 관리', + 'Tags' => '태그', + 'Tags management' => '태그 관리', + 'Add new tag' => '태그 추가', + 'Edit a tag' => '태그 수정', + 'Project tags' => '프로젝트 태그', + 'There is no specific tag for this project at the moment.' => '현재 이 프로젝트에는 태그가 없습니다.', + 'Tag' => '태그', + 'Remove a tag' => '태그 삭제', + 'Do you really want to remove this tag: "%s"?' => '태그를 삭제하시겠습니까: "%s"?', + 'Global tags' => '전역 태그', + 'There is no global tag at the moment.' => '현재 전역 태그가 없습니다.', + 'This field cannot be empty' => '이 필드는 비워둘 수 없습니다', + 'Close a task when there is no activity in a specific column' => '활동이 없는 컬럼의 할일 마치기', + '%s removed a subtask for the task #%d' => '%s가 할일 #%d의 서브 할일을 삭제하였습니다', + '%s removed a comment on the task #%d' => '%s가 할일 #%d의 댓글을 삭제하였습니다', + 'Comment removed on task #%d' => '할일 #%d의 댓글이 삭제되었습니다', + 'Subtask removed on task #%d' => '할일 #%d의 서브 할일이 삭제되었습니다', + 'Hide tasks in this column in the dashboard' => '대시보드 컬럼의 할일 숨기기', + '%s removed a comment on the task %s' => '%s가 할일 %s의 댓글을 삭제하였습니다', + '%s removed a subtask for the task %s' => '%s가 할일 %s의 서브 할일을 삭제하였습니다', + 'Comment removed' => '댓글이 삭제되었습니다', + 'Subtask removed' => '서브 할일이 삭제되었습니다', + '%s set a new internal link for the task #%d' => '%s가 할일 #%d의 새로운 내부 링크를 설정하였습니다', + '%s removed an internal link for the task #%d' => '%s가 할일 #%d의 새로운 내부 링크를 삭제하였습니다', + 'A new internal link for the task #%d has been defined' => '할일 #%d의 새로운 내부 링크가 정의되었습니다', + 'Internal link removed for the task #%d' => '할일 #%d의 새로운 내부 링크가 삭제되었습니다', + '%s set a new internal link for the task %s' => '%s가 할일 %s의 새로운 내부 링크를 설정하였습니다', + '%s removed an internal link for the task %s' => '%s가 할일 %s의 새로운 내부 링크를 삭제하였습니다', + 'Automatically set the due date on task creation' => '할일 생성시 마감일이 자동으로 설정되었습니다', + 'Move the task to another column when closed' => '할일을 마치면 다른 컬럼으로 이동시키기', + 'Move the task to another column when not moved during a given period' => '주어진 기간동안 이동하지 않으면 할일을 다른 컬럼으로 이동시키기', + 'Dashboard for %s' => '%s의 대시보드', + 'Tasks overview for %s' => '%s의 할일 개요', + 'Subtasks overview for %s' => '%s의 서브 할일 개요', + 'Projects overview for %s' => '%s의 프로젝트 개요', + 'Activity stream for %s' => '%s의 활동기록', + 'Assign a color when the task is moved to a specific swimlane' => '할일이 특정 스웜라인으로 옮겨질 때 색상 지정', + 'Assign a priority when the task is moved to a specific swimlane' => '할일이 특정 스웜라인으로 옮겨질 때 우선순위 지정', + 'User unlocked successfully.' => '사용자 잠금 성공', + 'Unable to unlock the user.' => '사용자 해제 성공', + 'Move a task to another swimlane' => '다른 스웜라인으로 할일 이동', + 'Creator Name' => '생성자 이름', + 'Time spent and estimated' => '소요시간과 예상시간', + 'Move position' => '이동 위치', + 'Move task to another position on the board' => '할일을 보드의 다른 위치로 이동', + 'Insert before this task' => '이 할일 이전에 삽입', + 'Insert after this task' => '이 할일 이후에 삽입', + 'Unlock this user' => '사용자 잠금', + 'Custom Project Roles' => '정의 프로젝트 규칙', + 'Add a new custom role' => '새로운 정의 규칙 추가', + 'Restrictions for the role "%s"' => '"%s" 역할의 제약', + 'Add a new project restriction' => '새로운 프로젝트 제약 추가', + 'Add a new drag and drop restriction' => '새로운 드래그 앤 드롭 제약 추가', + 'Add a new column restriction' => '새로운 칼럼 제약 추가', + 'Edit this role' => '역할 수정', + 'Remove this role' => '역할 삭제', + 'There is no restriction for this role.' => '역할에 대한 제약이 없습니다.', + 'Only moving task between those columns is permitted' => '칼럼간 이동만 허용됩니다.', + 'Close a task in a specific column when not moved during a given period' => '특정 기간동안 이동하지 않은 특정 칼럼의 할일 마치기', + 'Edit columns' => '칼럼 수정', + 'The column restriction has been created successfully.' => '칼럼 제약이 생성되었습니다.', + 'Unable to create this column restriction.' => '칼럼 제약을 생성할 수 없습니다.', + 'Column restriction removed successfully.' => '칼럼 제약이 삭제되었습니다.', + 'Unable to remove this restriction.' => '칼럼 제약을 삭제할 수 없습니다.', + 'Your custom project role has been created successfully.' => '정의 프로젝트 역할이 생성되었습니다.', + 'Unable to create custom project role.' => '정의 프로젝트 역할을 생성할 수 없습니다.', + 'Your custom project role has been updated successfully.' => '정의 프로젝트 역할이 수정되었습니다.', + 'Unable to update custom project role.' => '정의 프로젝트 역할을 수정할 수 없습니다.', + 'Custom project role removed successfully.' => '정의 프로젝트 역할이 삭제되었습니다.', + 'Unable to remove this project role.' => '정의 프로젝트 역할을 삭제할 수 없습니다.', + 'The project restriction has been created successfully.' => '프로젝트 제약이 생성되었습니다.', + 'Unable to create this project restriction.' => '프로젝트 제약을 생성할 수 없습니다.', + 'Project restriction removed successfully.' => '프로젝트 제약을 삭제하였습니다.', + 'You cannot create tasks in this column.' => '이 칼럼의 할일을 생성할 수 없습니다.', + 'Task creation is permitted for this column' => '이 칼럼의 할일 생성이 허가되었습니다.', + 'Closing or opening a task is permitted for this column' => '이 칼럼의 할일 열기 혹은 닫기가 허가되었습니다.', + 'Task creation is blocked for this column' => '이 칼럼의 할일 생성이 거부되었습니다.', + 'Closing or opening a task is blocked for this column' => '이 칼럼의 할일 열기 혹은 닫기가 거부되었습니다.', + 'Task creation is not permitted' => '할일 생성이 거부되었습니다.', + 'Closing or opening a task is not permitted' => '할일 열기 혹은 닫기가 거부되었습니다.', + 'New drag and drop restriction for the role "%s"' => '"%s" 역할의 새로운 드래그 앤 드롭 제약', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => '이 역할에 속한 사용자는 원본 및 대상 칼럼 사이에서만 할일을 이동할 수 있습니다', + 'Remove a column restriction' => '칼럼 제약 삭제', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => '칼럼 제약을 "%s" 에서 "%s"로 이동하시겠습니까?', + 'New column restriction for the role "%s"' => '"%s" 역할의 새로운 칼럼 제약', + 'Rule' => '역할', + 'Do you really want to remove this column restriction?' => '칼럼 제약을 삭제하시겠습니까?', + 'Custom roles' => '정의 역할', + 'New custom project role' => '새로운 정의 프로젝트 역할', + 'Edit custom project role' => '정의 프로젝트 역할 수정', + 'Remove a custom role' => '정의 역할 삭제', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => '"%s" 정의 역할을 삭제하시겠습니까? 이 역할에 할당 된 모든 사람들이 프로젝트 멤버가됩니다.', + 'There is no custom role for this project.' => '이 프로젝트에는 정의 역할이 없습니다.', + 'New project restriction for the role "%s"' => '"%s" 역할의 새로운 프로젝트 제약', + 'Restriction' => '제약', + 'Remove a project restriction' => '프로젝트 제약 삭제', + 'Do you really want to remove this project restriction: "%s"?' => '"%s" 프로젝트 제약을 삭제하시겠습니까?', + 'Duplicate to multiple projects' => '다수의 프로젝트 복제', + 'This field is required' => '이 필드는 필수 항목입니다.', + 'Moving a task is not permitted' => '할일 이동이 거부되었습니다.', + 'This value must be in the range %d to %d' => '값의 범위는 %d 부터 %d 까지 입니다.', + 'You are not allowed to move this task.' => '당신은 할일 이동이 거부되었습니다.', + 'API User Access' => 'API 사용자 접근', + 'Preview' => '미리 보기', + 'Write' => '쓰기', + 'Write your text in Markdown' => '마크다운으로 텍스트 작성', + 'No personal API access token registered.' => '개인 API 접근 토큰이 등록되지 않았습니다.', + 'Your personal API access token is "%s"' => '개인 API 접근 토큰은 "%s"입니다.', + 'Remove your token' => '토큰 제거', + 'Generate a new token' => '새 토큰 생성', + 'Showing %d-%d of %d' => '%d-%d/%d 표시', + 'Outgoing Emails' => '발신 이메일', + 'Add or change currency rate' => '환율 추가 또는 변경', + 'Reference currency: %s' => '기준 통화: %s', + 'Add custom filters' => '사용자 정의 필터 추가', + 'Export' => '내보내기', + 'Add link label' => '링크 레이블 추가', + 'Incompatible Plugins' => '호환되지 않는 플러그인', + 'Compatibility' => '호환성', + 'Permissions and ownership' => '권한 및 소유권', + 'Priorities' => '우선 순위', + 'Close this window' => '이 창 닫기', + 'Unable to upload this file.' => '이 파일을 업로드할 수 없습니다.', + 'Import tasks' => '작업 가져오기', + 'Choose a project' => '프로젝트 선택', + 'Profile' => '프로필', + 'Application role' => '애플리케이션 역할', + '%d invitations were sent.' => '초대 %d건이 발송되었습니다.', + '%d invitation was sent.' => '초대 %d건이 발송되었습니다.', + 'Unable to create this user.' => '이 사용자를 생성할 수 없습니다.', + 'Kanboard Invitation' => 'Kanboard 초대', + 'Visible on dashboard' => '대시보드에 표시', + 'Created at:' => '생성 시간:', + 'Updated at:' => '업데이트 시간:', + 'There is no custom filter.' => '사용자 정의 필터가 없습니다.', + 'New User' => '새 사용자', + 'Authentication' => '인증', + 'If checked, this user will use a third-party system for authentication.' => '선택하면 이 사용자는 타사 시스템을 사용하여 인증됩니다.', + 'The password is necessary only for local users.' => '비밀번호는 로컬 사용자에게만 필요합니다.', + 'You have been invited to register on Kanboard.' => 'Kanboard에 등록하도록 초대되었습니다.', + 'Click here to join your team' => '팀에 참가하려면 여기를 클릭하세요', + 'Invite people' => '사람 초대', + 'Emails' => '이메일', + 'Enter one email address by line.' => '한 줄에 하나의 이메일 주소를 입력하세요.', + 'Add these people to this project' => '이 사람들을 이 프로젝트에 추가', + 'Add this person to this project' => '이 사람을 이 프로젝트에 추가', + 'Sign-up' => '가입', + 'Credentials' => '자격 증명', + 'New user' => '새 사용자', + 'This username is already taken' => '이 사용자 이름은 이미 사용 중입니다.', + 'Your profile must have a valid email address.' => '프로필에 유효한 이메일 주소가 있어야 합니다.', + 'TRL - Turkish Lira' => 'TRL - 터키 리라', + 'The project email is optional and could be used by several plugins.' => '프로젝트 이메일은 선택 사항이며 여러 플러그인에서 사용할 수 있습니다.', + 'The project email must be unique across all projects' => '프로젝트 이메일은 모든 프로젝트에서 고유해야 합니다.', + 'The email configuration has been disabled by the administrator.' => '이메일 설정은 관리자에 의해 비활성화되었습니다.', + 'Close this project' => '이 프로젝트 닫기', + 'Open this project' => '이 프로젝트 열기', + 'Close a project' => '프로젝트 닫기', + 'Do you really want to close this project: "%s"?' => '이 프로젝트를 정말로 닫으시겠습니까: "%s"?', + 'Reopen a project' => '프로젝트 다시 열기', + 'Do you really want to reopen this project: "%s"?' => '이 프로젝트를 정말로 다시 여시겠습니까: "%s"?', + 'This project is open' => '이 프로젝트는 열려 있습니다.', + 'This project is closed' => '이 프로젝트는 닫혀 있습니다.', + 'Unable to upload files, check the permissions of your data folder.' => '파일을 업로드할 수 없습니다. 데이터 폴더의 권한을 확인하십시오.', + 'Another category with the same name exists in this project' => '이 프로젝트에 같은 이름의 다른 카테고리가 있습니다.', + 'Comment sent by email successfully.' => '이메일로 댓글을 성공적으로 보냈습니다.', + 'Sent by email to "%s" (%s)' => '"%s" (%s)에게 이메일로 보냄', + 'Unable to read uploaded file.' => '업로드된 파일을 읽을 수 없습니다.', + 'Database uploaded successfully.' => '데이터베이스가 성공적으로 업로드되었습니다.', + 'Task sent by email successfully.' => '작업이 이메일로 성공적으로 발송되었습니다.', + 'There is no category in this project.' => '이 프로젝트에는 카테고리가 없습니다.', + 'Send by email' => '이메일로 보내기', + 'Create and send a comment by email' => '이메일로 댓글 작성 및 보내기', + 'Subject' => '제목', + 'Upload the database' => '데이터베이스 업로드', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => '이전에 다운로드한 Sqlite 데이터베이스(Gzip 형식)를 업로드할 수 있습니다.', + 'Database file' => '데이터베이스 파일', + 'Upload' => '업로드', + 'Your project must have at least one active swimlane.' => '프로젝트에는 하나 이상의 활성 스윔레인이 있어야 합니다.', + 'Project: %s' => '프로젝트: %s', + 'Automatic action not found: "%s"' => '자동 액션을 찾을 수 없습니다: "%s"', + '%d projects' => '프로젝트 %d개', + '%d project' => '프로젝트 %d개', + 'There is no project.' => '프로젝트가 없습니다.', + 'Sort' => '정렬', + 'Project ID' => '프로젝트 ID', + 'Project name' => '프로젝트 이름', + 'Public' => '공개', + 'Personal' => '개인', + '%d tasks' => '작업 %d개', + '%d task' => '작업 %d개', + 'Task ID' => '작업 ID', + 'Assign automatically a color when due date is expired' => '마감일이 지났을 때 자동으로 색상 할당', + 'Total score in this column across all swimlanes' => '모든 스윔레인에서 이 열의 총 점수', + 'HRK - Kuna' => 'HRK - 쿠나', + 'ARS - Argentine Peso' => 'ARS - 아르헨티나 페소', + 'COP - Colombian Peso' => 'COP - 콜롬비아 페소', + '%d groups' => '그룹 %d개', + '%d group' => '그룹 %d개', + 'Group ID' => '그룹 ID', + 'External ID' => '외부 ID', + '%d users' => '사용자 %d명', + '%d user' => '사용자 %d명', + 'Hide subtasks' => '하위 작업 숨기기', + 'Show subtasks' => '하위 작업 표시', + 'Authentication Parameters' => '인증 매개변수', + 'API Access' => 'API 접근', + 'No users found.' => '사용자를 찾을 수 없습니다.', + 'User ID' => '사용자 ID', + 'Notifications are activated' => '알림이 활성화되었습니다.', + 'Notifications are disabled' => '알림이 비활성화되었습니다.', + 'User disabled' => '사용자 비활성화됨', + '%d notifications' => '알림 %d개', + '%d notification' => '알림 %d개', + 'There is no external integration installed.' => '설치된 외부 통합이 없습니다.', + 'You are not allowed to update tasks assigned to someone else.' => '다른 사람에게 할당된 작업을 업데이트할 수 없습니다.', + 'You are not allowed to change the assignee.' => '담당자를 변경할 수 없습니다.', + 'Task suppression is not permitted' => '작업 삭제는 허용되지 않습니다.', + 'Changing assignee is not permitted' => '담당자 변경은 허용되지 않습니다.', + 'Update only assigned tasks is permitted' => '할당된 작업만 업데이트할 수 있습니다.', + 'Only for tasks assigned to the current user' => '현재 사용자에게 할당된 작업에만 해당', + 'My projects' => '내 프로젝트', + 'You are not a member of any project.' => '어떤 프로젝트의 멤버도 아닙니다.', + 'My subtasks' => '내 하위 작업', + '%d subtasks' => '하위 작업 %d개', + '%d subtask' => '하위 작업 %d개', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => '현재 사용자에게 할당된 작업의 경우 해당 열 간에만 작업 이동이 허용됩니다.', + '[DUPLICATE]' => '[복제]', + 'DKK - Danish Krona' => 'DKK - 덴마크 크로네', + 'Remove user from group' => '그룹에서 사용자 제거', + 'Assign the task to its creator' => '작업을 생성자에게 할당', + 'This task was sent by email to "%s" with subject "%s".' => '이 작업은 "%s"에게 제목 "%s"으로 이메일이 발송되었습니다.', + 'Predefined Email Subjects' => '사전 정의된 이메일 제목', + 'Write one subject by line.' => '한 줄에 하나의 제목을 작성하세요.', + 'Create another link' => '다른 링크 생성', + 'BRL - Brazilian Real' => 'BRL - 브라질 헤알', + 'Add a new Kanboard task' => '새 Kanboard 작업 추가', + 'Subtask not started' => '하위 작업 시작 안 함', + 'Subtask currently in progress' => '하위 작업 현재 진행 중', + 'Subtask completed' => '하위 작업 완료됨', + 'Subtask added successfully.' => '하위 작업이 성공적으로 추가되었습니다.', + '%d subtasks added successfully.' => '하위 작업 %d개가 성공적으로 추가되었습니다.', + 'Enter one subtask by line.' => '한 줄에 하나의 하위 작업을 입력하세요.', + 'Predefined Contents' => '사전 정의된 내용', + 'Predefined contents' => '사전 정의된 내용', + 'Predefined Task Description' => '사전 정의된 작업 설명', + 'Do you really want to remove this template? "%s"' => '정말로 이 템플릿을 제거하시겠습니까? "%s"', + 'Add predefined task description' => '사전 정의된 작업 설명 추가', + 'Predefined Task Descriptions' => '사전 정의된 작업 설명', + 'Template created successfully.' => '템플릿이 성공적으로 생성되었습니다.', + 'Unable to create this template.' => '이 템플릿을 생성할 수 없습니다.', + 'Template updated successfully.' => '템플릿이 성공적으로 업데이트되었습니다.', + 'Unable to update this template.' => '이 템플릿을 업데이트할 수 없습니다.', + 'Template removed successfully.' => '템플릿이 성공적으로 제거되었습니다.', + 'Unable to remove this template.' => '이 템플릿을 제거할 수 없습니다.', + 'Template for the task description' => '작업 설명 템플릿', + 'The start date is greater than the end date' => '시작 날짜가 종료 날짜보다 큽니다.', + 'Tags must be separated by a comma' => '태그는 쉼표로 구분해야 합니다.', + 'Only the task title is required' => '작업 제목만 필수입니다.', + 'Creator Username' => '생성자 사용자 이름', + 'Color Name' => '색상 이름', + 'Column Name' => '열 이름', + 'Swimlane Name' => '스윔레인 이름', + 'Time Estimated' => '예상 시간', + 'Time Spent' => '소요 시간', + 'External Link' => '외부 링크', + 'This feature enables the iCal feed, RSS feed and the public board view.' => '이 기능은 iCal 피드, RSS 피드 및 공개 보드 보기를 활성화합니다.', + 'Stop the timer of all subtasks when moving a task to another column' => '작업을 다른 열로 이동할 때 모든 하위 작업의 타이머 중지', + 'Subtask Title' => '하위 작업 제목', + 'Add a subtask and activate the timer when moving a task to another column' => '하위 작업을 추가하고 작업이 다른 열로 이동할 때 타이머 활성화', + 'days' => '일', + 'minutes' => '분', + 'seconds' => '초', + 'Assign automatically a color when preset start date is reached' => '사전 설정된 시작 날짜에 도달하면 자동으로 색상 할당', + 'Move the task to another column once a predefined start date is reached' => '사전 정의된 시작 날짜에 도달하면 작업을 다른 열로 이동', + 'This task is now linked to the task %s with the relation "%s"' => '이 작업은 이제 "%s" 관계를 사용하여 작업 %s에 연결되었습니다.', + 'The link with the relation "%s" to the task %s has been removed' => '작업 %s에 대한 "%s" 관계의 링크가 제거되었습니다.', + 'Custom Filter:' => '사용자 정의 필터:', + 'Unable to find this group.' => '이 그룹을 찾을 수 없습니다.', + '%s moved the task #%d to the column "%s"' => '%s이(가) 작업 #%d을(를) "%s" 열(로)로 이동했습니다.', + '%s moved the task #%d to the position %d in the column "%s"' => '%s이(가) 작업 #%d을(를) "%s" 열의 %d 위치(으)로 이동했습니다.', + '%s moved the task #%d to the swimlane "%s"' => '%s이(가) 작업 #%d을(를) "%s" 스윔레인(으)로 이동했습니다.', + '%sh spent' => '%sh 소요', + '%sh estimated' => '%sh 예상', + 'Select All' => '모두 선택', + 'Unselect All' => '모두 선택 해제', + 'Apply action' => '작업 적용', + 'Move selected tasks to another column or swimlane' => '선택한 작업을 다른 열 또는 스윔레인으로 이동', + 'Edit tasks in bulk' => '작업 일괄 편집', + 'Choose the properties that you would like to change for the selected tasks.' => '선택한 작업에 대해 변경하려는 속성을 선택하십시오.', + 'Configure this project' => '이 프로젝트 구성', + 'Start now' => '지금 시작', + '%s removed a file from the task #%d' => '%s이(가) 작업 #%d에서 파일을 제거했습니다.', + 'Attachment removed from task #%d: %s' => '작업 #%d에서 첨부 파일 제거됨: %s', + 'No color' => '색상 없음', + 'Attachment removed "%s"' => '첨부 파일 "%s" 제거됨', + '%s removed a file from the task %s' => '%s이(가) 작업 %s에서 파일을 제거했습니다.', + 'Move the task to another swimlane when assigned to a user' => '사용자에게 할당될 때 작업을 다른 스윔레인으로 이동', + 'Destination swimlane' => '대상 스윔레인', + 'Assign a category when the task is moved to a specific swimlane' => '작업이 특정 스윔레인으로 이동될 때 카테고리 할당', + 'Move the task to another swimlane when the category is changed' => '카테고리가 변경될 때 작업을 다른 스윔레인으로 이동', + 'Reorder this column by priority (ASC)' => '이 열을 우선순위로 재정렬 (오름차순)', + 'Reorder this column by priority (DESC)' => '이 열을 우선순위로 재정렬 (내림차순)', + 'Reorder this column by assignee and priority (ASC)' => '이 열을 담당자 및 우선순위로 재정렬 (오름차순)', + 'Reorder this column by assignee and priority (DESC)' => '이 열을 담당자 및 우선순위로 재정렬 (내림차순)', + 'Reorder this column by assignee (A-Z)' => '이 열을 담당자 (A-Z)로 재정렬', + 'Reorder this column by assignee (Z-A)' => '이 열을 담당자 (Z-A)로 재정렬', + 'Reorder this column by due date (ASC)' => '이 열을 마감일로 재정렬 (오름차순)', + 'Reorder this column by due date (DESC)' => '이 열을 마감일로 재정렬 (내림차순)', + 'Reorder this column by id (ASC)' => '이 열을 ID로 재정렬 (오름차순)', + 'Reorder this column by id (DESC)' => '이 열을 ID로 재정렬 (내림차순)', + '%s moved the task #%d "%s" to the project "%s"' => '%s이(가) 작업 #%d "%s"을(를) 프로젝트 "%s"(으)로 이동했습니다.', + 'Task #%d "%s" has been moved to the project "%s"' => '작업 #%d "%s"이(가) 프로젝트 "%s"(으)로 이동되었습니다.', + 'Move the task to another column when the due date is less than a certain number of days' => '마감일이 특정 일수 미만일 때 작업을 다른 열로 이동', + 'Automatically update the start date when the task is moved away from a specific column' => '작업이 특정 열에서 이동될 때 시작 날짜 자동 업데이트', + 'HTTP Client:' => 'HTTP 클라이언트:', + 'Assigned' => '할당됨', + 'Task limits apply to each swimlane individually' => '작업 제한은 각 스윔레인에 개별적으로 적용됩니다.', + 'Column task limits apply to each swimlane individually' => '열 작업 제한은 각 스윔레인에 개별적으로 적용됩니다.', + 'Column task limits are applied to each swimlane individually' => '열 작업 제한은 각 스윔레인에 개별적으로 적용됩니다.', + 'Column task limits are applied across swimlanes' => '열 작업 제한은 스윔레인 전반에 적용됩니다.', + 'Task limit: ' => '작업 제한:', + 'Change to global tag' => '글로벌 태그로 변경', + 'Do you really want to make the tag "%s" global?' => '정말로 태그 "%s"를 글로벌로 만드시겠습니까?', + 'Enable global tags for this project' => '이 프로젝트에 대해 글로벌 태그 활성화', + 'Group membership(s):' => '그룹 구성원:', + '%s is a member of the following group(s): %s' => '%s은(는) 다음 그룹의 구성원입니다: %s', + '%d/%d group(s) shown' => '그룹 %d/%d 표시됨', + 'Subtask creation or modification' => '하위 작업 생성 또는 수정', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => '작업이 특정 스윔레인으로 이동될 때 특정 사용자에게 작업 할당', + 'Comment' => '댓글', + 'Collapse vertically' => '세로로 접기', + 'Expand vertically' => '세로로 확장', + 'MXN - Mexican Peso' => 'MXN - 멕시코 페소', + 'Estimated vs actual time per column' => '열당 예상 시간 대 실제 시간', + 'HUF - Hungarian Forint' => 'HUF - 헝가리 포린트', + 'XBT - Bitcoin' => 'XBT - 비트코인', + 'You must select a file to upload as your avatar!' => '아바타로 업로드할 파일을 선택해야 합니다!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => '업로드한 파일은 유효한 이미지가 아닙니다! (*.gif, *.jpg, *.jpeg 및 *.png만 허용됩니다!)', + 'Automatically set the due date when the task is moved away from a specific column' => '작업이 특정 열에서 이동될 때 마감일 자동 설정', + 'No other projects found.' => '다른 프로젝트를 찾을 수 없습니다.', + 'Tasks copied successfully.' => '작업이 성공적으로 복사되었습니다.', + 'Unable to copy tasks.' => '작업을 복사할 수 없습니다.', + 'Theme' => '테마', + 'Theme:' => '테마:', + 'Light theme' => '밝은 테마', + 'Dark theme' => '어두운 테마', + 'Automatic theme - Sync with system' => '자동 테마 - 시스템과 동기화', + 'Application managers or more' => '애플리케이션 관리자 이상', + 'Administrators' => '관리자', + 'Visibility:' => '가시성:', + 'Standard users' => '표준 사용자', + 'Visibility is required' => '가시성은 필수입니다.', + 'The visibility should be an app role' => '가시성은 앱 역할이어야 합니다.', + 'Reply' => '답글', + '%s wrote: ' => '%s 작성: ', + 'Number of visible tasks in this column and swimlane' => '이 열과 스윔레인에 표시되는 작업 수', + 'Number of tasks in this swimlane' => '이 스윔레인의 작업 수', + 'Unable to find another subtask in progress, you can close this window.' => '다른 진행 중인 하위 작업을 찾을 수 없습니다. 이 창을 닫을 수 있습니다.', + 'This theme is invalid' => '이 테마는 유효하지 않습니다.', + 'This role is invalid' => '이 역할은 유효하지 않습니다.', + 'This timezone is invalid' => '이 시간대는 유효하지 않습니다.', + 'This language is invalid' => '이 언어는 유효하지 않습니다.', + 'This URL is invalid' => '이 URL은 유효하지 않습니다.', + 'Date format invalid' => '잘못된 날짜 형식', + 'Time format invalid' => '잘못된 시간 형식', + 'Invalid Mail transport' => '잘못된 메일 전송', + 'Color invalid' => '잘못된 색상', + 'This value must be greater or equal to %d' => '이 값은 %d보다 크거나 같아야 합니다.', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => '파일 시작 부분에 BOM 추가 (Microsoft Excel에 필요)', + 'Just add these tag(s)' => '이 태그(들)만 추가', + 'Remove internal link(s)' => '내부 링크 제거', + 'Import tasks from another project' => '다른 프로젝트에서 작업 가져오기', + 'Select the project to copy tasks from' => '작업을 복사할 프로젝트 선택', + 'The total maximum allowed attachments size is %sB.' => '허용되는 총 첨부 파일 최대 크기는 %sB입니다.', + 'Add attachments' => '첨부 파일 추가', + 'Task #%d "%s" is overdue' => '할일 #%d "%s"의 기한이 지났습니다', + 'Enable notifications by default for all new users' => '모든 신규 사용자에 대해 기본적으로 알림 활성화', + 'Assign the task to its creator for specific columns if no assignee is set manually' => '담당자가 수동으로 지정되지 않은 경우, 지정한 컬럼에서는 작업을 생성자에게 할당', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => '사용자가 할당되지 않은 경우, 컬럼 변경으로 지정된 컬럼으로 이동할 때 작업을 로그인한 사용자에게 할당', +]; diff --git a/app/Locale/mk_MK/translations.php b/app/Locale/mk_MK/translations.php new file mode 100644 index 0000000..72e2b51 --- /dev/null +++ b/app/Locale/mk_MK/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => '.', + 'None' => 'Никој', + 'Edit' => 'Уредување', + 'Remove' => 'Отстрани', + 'Yes' => 'Да', + 'No' => 'Не', + 'cancel' => 'Откажи', + 'or' => 'или', + 'Yellow' => 'Жолто', + 'Blue' => 'Сина', + 'Green' => 'Зелена', + 'Purple' => 'Виолетова', + 'Red' => 'Црвено', + 'Orange' => 'Портокалова', + 'Grey' => 'Сиво', + 'Brown' => 'Кафеав', + 'Deep Orange' => 'Темно портокалова', + 'Dark Grey' => 'Темно сива', + 'Pink' => 'Розова', + 'Teal' => 'Тиркизна боја', + 'Cyan' => 'Цијан', + 'Lime' => 'Вар', + 'Light Green' => 'Светло зелено', + 'Amber' => 'Килибарна', + 'Save' => 'Зачувај', + 'Login' => 'Чекирање', + 'Official website:' => 'Официјална страница:', + 'Unassigned' => 'Не е доделен', + 'View this task' => 'Прегледајте ја задачата', + 'Remove user' => 'Отстранете го корисникот', + 'Do you really want to remove this user: "%s"?' => 'Дали сте сигурни дека сакате да го отстраните корисникот: "%s"?', + 'All users' => 'Сите корисници', + 'Username' => 'Корисник', + 'Password' => 'Лозинка', + 'Administrator' => 'Администратор', + 'Sign in' => 'Чекирање', + 'Users' => 'Корисници', + 'Forbidden' => 'Забрането', + 'Access Forbidden' => 'Одбиен пристап', + 'Edit user' => 'Уредете го корисникот', + 'Logout' => 'Одјави се', + 'Bad username or password' => 'Лошо корисничко име или лозинка', + 'Edit project' => 'Уредете го проектот', + 'Name' => 'Име', + 'Projects' => 'Проекти', + 'No project' => 'Нема проект', + 'Project' => 'Проект', + 'Status' => 'Статус', + 'Tasks' => 'Доделување', + 'Board' => 'Табла', + 'Actions' => 'Дејства', + 'Inactive' => 'Неактивен', + 'Active' => 'Активни', + 'Unable to update this board.' => 'Ова ажурирање на таблата не успеа', + 'Disable' => 'Оневозможи', + 'Enable' => 'Овозможи', + 'New project' => 'Нов проект', + 'Do you really want to remove this project: "%s"?' => 'Дали сте сигурни дека сакате да го отстраните проектот: "%s"?', + 'Remove project' => 'Отстрани проект', + 'Edit the board for "%s"' => 'Уредете ја таблата за "%s"', + 'Add a new column' => 'Додадете нова колона', + 'Title' => 'Наслов', + 'Assigned to %s' => 'Доделено на %s', + 'Remove a column' => 'Отстранете ја колоната', + 'Unable to remove this column.' => 'Не може да се отстрани оваа колона.', + 'Do you really want to remove this column: "%s"?' => 'Дали сте сигурни дека сакате да ја отстраните оваа колона: "%s"?', + 'Settings' => 'Поставки', + 'Application settings' => 'Поставки за апликација', + 'Language' => 'Јазик', + 'Webhook token:' => 'Webhook токен:', + 'API token:' => 'Token API', + 'Database size:' => 'Големина на основата:', + 'Download the database' => 'Преземете ја базата на податоци', + 'Optimize the database' => 'Оптимизирајте ја базата на податоци', + '(VACUUM command)' => '(Команда ВАКУУМ)', + '(Gzip compressed Sqlite file)' => '(Sqlite база на податоци спакувана со Gzip)', + 'Close a task' => 'Затворете ја задачата', + 'Column' => 'Колона', + 'Color' => 'Бојата', + 'Assignee' => 'Извршител', + 'Create another task' => 'Додадете задача', + 'New task' => 'Нова задача', + 'Open a task' => 'Отворете ја задачата', + 'Do you really want to open this task: "%s"?' => 'Дали навистина сакате да ја отворите оваа задача: "%s"?', + 'Back to the board' => 'Назад на таблата', + 'There is nobody assigned' => 'Не е доделен на никого', + 'Column on the board:' => 'Колона на табла:', + 'Close this task' => 'Затворете ја оваа задача', + 'Open this task' => 'Отворете ја оваа задача', + 'There is no description.' => 'Без опис.', + 'Add a new task' => 'Додадете задача', + 'The username is required' => 'Потребно е корисничко име', + 'The maximum length is %d characters' => 'Максималната должина е %d карактери', + 'The minimum length is %d characters' => 'Минималната должина е %d карактери', + 'The password is required' => 'Потребна е лозинка', + 'This value must be an integer' => 'Мора да биде цел број', + 'The username must be unique' => 'Корисничкото име мора да биде единствено', + 'The user id is required' => 'Потребна е корисничка лична карта', + 'Passwords don\'t match' => 'Лозинките не се совпаѓаат', + 'The confirmation is required' => 'Потребна е потврда', + 'The project is required' => 'Проектот е задолжителен', + 'The id is required' => 'Потребна е лична карта', + 'The project id is required' => 'Потребен е проект за проект', + 'The project name is required' => 'Задолжително е името на проектот', + 'The title is required' => 'Потребен е наслов', + 'Settings saved successfully.' => 'Поставките беа успешно зачувани.', + 'Unable to save your settings.' => 'Не може да се зачуваат поставките.', + 'Database optimization done.' => 'Оптимизацијата на базата на податоци е завршена.', + 'Your project has been created successfully.' => 'Проектот беше успешно завршен.', + 'Unable to create your project.' => 'Не може да се создаде проект.', + 'Project updated successfully.' => 'Проектот е успешно ажуриран.', + 'Unable to update this project.' => 'Не може да се ажурира овој проект.', + 'Unable to remove this project.' => 'Не може да се отстрани овој проект.', + 'Project removed successfully.' => 'Проектот е успешно отстранет.', + 'Project activated successfully.' => 'Проектот е успешно активиран.', + 'Unable to activate 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.' => 'Не може да се ажурира задачата.', + 'Task updated successfully.' => 'Задачата е успешно ажурирана.', + 'Unable to create your task.' => 'Не може да се создаде задача.', + 'Task created successfully.' => 'Задачата е успешно креирана.', + 'User created successfully.' => 'Корисникот е успешно создаден', + 'Unable to create your user.' => 'Не успеа да се создаде корисник.', + 'User updated successfully.' => 'Корисникот е успешно ажуриран.', + 'User removed successfully.' => 'Корисникот е успешно отстранет.', + 'Unable to remove this user.' => 'Не може да се отстрани корисникот.', + 'Board updated successfully.' => 'Таблата успешно се ажурираше.', + 'Ready' => 'Спремен', + 'Backlog' => 'Дневник', + 'Work in progress' => 'Во тек', + 'Done' => 'Направено', + 'Application version:' => 'Верзија на апликација:', + 'Id' => 'ИД', + 'Public link' => 'Јавна врска', + 'Timezone' => 'Временска зона', + 'Sorry, I didn\'t find this information in my database!' => 'Извинете, не се пронајдени информации во базата на податоци', + 'Page not found' => 'Страната не е пронајдена', + 'Complexity' => 'Сложеност', + 'Task limit' => 'Ограничување на задачите', + 'Task count' => 'Број на задачи', + 'User' => 'Корисник', + 'Comments' => 'Коментари', + 'Comment is required' => 'Потребен е коментар', + 'Comment added successfully.' => 'Коментарот е успешно оставен', + 'Unable to create your comment.' => 'Не може да се создадат коментари', + 'Due Date' => 'Рок за завршување', + 'Invalid date' => 'Лош датум', + 'Automatic actions' => 'Автоматски дејства', + 'Your automatic action has been created successfully.' => 'Успешно создадено автоматско дејство', + 'Unable to create your automatic action.' => 'Не може да се создаде автоматско дејство', + 'Remove an action' => 'Акција за бришење', + 'Unable to remove this action.' => 'Не може да се избрише дејството', + 'Action removed successfully.' => 'Дејството е избришано', + 'Automatic actions for the project "%s"' => 'Акции за автоматизација на проектот "%s"', + 'Add an action' => 'додадете акција', + 'Event name' => 'Име на настанот', + 'Action' => 'Акција', + 'Event' => 'Настан', + 'When the selected event occurs execute the corresponding action.' => 'Кога ќе се случи настан, преземете соодветно дејство.', + 'Next step' => 'Следен чекор', + 'Define action parameters' => 'Дефинирајте ги параметрите на дејството', + 'Do you really want to remove this action: "%s"?' => 'Треба ли да го избришам дејството "%s"?', + 'Remove an automatic action' => 'Избришете го автоматското дејство', + 'Assign the task to a specific user' => 'Доделете задача на одреден корисник', + 'Assign the task to the person who does the action' => 'Доделете ја задачата на корисникот што го извршил дејството', + 'Duplicate the task to another project' => 'Копирајте го дејството во друг проект', + 'Move a task to another column' => 'Поместете ја задачата во друга колона', + 'Task modification' => 'Уредување задача', + 'Task creation' => 'Создавање задача', + 'Closing a task' => 'Затворање на задачата', + 'Assign a color to a specific user' => 'Доделете боја на корисникот', + 'Position' => 'Позиција', + 'Duplicate to project' => 'Копирајте во друг проект', + 'Duplicate' => 'Направете копија', + 'Link' => 'Врската', + 'Comment updated successfully.' => 'Коментарот е успешно ажуриран.', + 'Unable to update your comment.' => 'Ажурирањето на коментарите не успеа.', + 'Remove a comment' => 'Избриши коментар', + 'Comment removed successfully.' => 'Коментарот е успешно избришан.', + 'Unable to remove this comment.' => 'Не успеа да ги избрише коментарите.', + 'Do you really want to remove this comment?' => 'Треба ли да го избришам овој коментар?', + 'Current password for the user "%s"' => 'Тековна лозинка за корисникот "%s"', + 'The current password is required' => 'Тековната лозинка е потребна', + 'Wrong password' => 'Погрешна лозинка', + 'Unknown' => 'Непознато', + 'Last logins' => 'Последна најава', + 'Login date' => 'Датум на аплицирање', + 'Authentication method' => 'Метод за проверка', + 'IP address' => 'IP адреса', + 'User agent' => 'Прелистувач', + 'Persistent connections' => 'Постојана врска', + 'No session.' => 'Нема сесија', + 'Expiration date' => 'Определување', + 'Remember Me' => 'Запомни ме', + 'Creation date' => 'Датум на производство', + 'Everybody' => 'Сите', + 'Open' => 'Отворено', + 'Closed' => 'Затворено', + 'Search' => 'Пребарување', + 'Nothing found.' => 'Ништо не е пронајдено', + 'Due date' => 'Рок за завршување', + 'Description' => 'Опис', + '%d comments' => '%d коментари', + '%d comment' => '%d коментар', + 'Email address invalid' => 'Адресата за е-пошта е неважечка', + 'Your external account is not linked anymore to your profile.' => 'Вашиот надворешен профил повеќе не е поврзана со вашиот профил.', + 'Unable to unlink your external account.' => 'Не може да се одврзе врската со вашиот надворешен профил.', + 'External authentication failed' => 'Надворешната проверка не успеа', + 'Your external account is linked to your profile successfully.' => 'Вашиот надворешен профил е успешно поврзан со вашиот профил.', + 'Email' => 'Е-пошта', + 'Task removed successfully.' => 'Задачата е успешно отстранета.', + 'Unable to remove this task.' => 'Не може да се отстрани задачата.', + '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' => 'Автоматски доделува категорија по боја', + 'Task creation or modification' => 'Создадете или изменете задача', + 'Category' => 'Категорија', + 'Category:' => 'Категорија:', + 'Categories' => 'Категории', + 'Your category has been created successfully.' => 'Успешно креирана категорија.', + 'This category has been updated successfully.' => 'Категоријата е успешно променета', + 'Unable to update this category.' => 'Не може да се промени категоријата', + 'Remove a category' => 'Избриши категорија', + 'Category removed successfully.' => 'Категоријата е успешно отстранета.', + 'Unable to remove this category.' => 'Не може да се отстрани категоријата.', + 'Category modification for the project "%s"' => 'Изменете ја категоријата за проект "%s"', + 'Category Name' => 'Име на категорија', + 'Add a new category' => 'Додадете нова категорија', + 'Do you really want to remove this category: "%s"?' => 'Дали сте сигурни дека сакате да ја отстраните категоријата: "%s"?', + 'All categories' => 'Сите Категории', + 'No category' => 'Нема категорија', + 'The name is required' => 'Потребно е име', + 'Remove a file' => 'Отстранете ја датотеката', + 'Unable to remove this file.' => 'Датотеката не може да се отстрани.', + 'File removed successfully.' => 'Датотеката е успешно отстранета.', + 'Attach a document' => 'Прикачете го документот', + 'Do you really want to remove this file: "%s"?' => 'Дали сте сигурни дека сакате да ја отстраните датотеката: "%s"?', + 'Attachments' => 'Прилози', + 'Edit the task' => 'Уредување задача', + 'Add a comment' => 'Додај коментар', + 'Edit a comment' => 'Уредете го коментарот', + 'Summary' => 'Преглед', + 'Time tracking' => 'Следење на времето', + 'Estimate:' => 'Проценка:', + 'Spent:' => 'Поминато:', + 'Do you really want to remove this sub-task?' => 'Дали навистина сакате да ја отстраните подзадачата?', + 'Remaining:' => 'Остатокот:', + 'hours' => 'часови', + 'estimated' => 'проценето', + 'Sub-Tasks' => 'Подзадачи', + 'Add a sub-task' => 'Додадете подзадача', + 'Original estimate' => 'Оригинална проценка', + 'Create another sub-task' => 'Додадете нова подзадача', + 'Time spent' => 'Потрошено време', + 'Edit a sub-task' => 'Уредете подзадача', + 'Remove a sub-task' => 'Отстранете ја подзадачата', + 'The time must be a numeric value' => 'Времето мора да биде нумеричка вредност', + 'Todo' => 'Да направиш', + 'In progress' => 'Во тек', + 'Sub-task removed successfully.' => 'Потзадачата е успешно отстранета.', + 'Unable to remove this sub-task.' => 'Не може да се отстрани оваа подзадача', + 'Sub-task updated successfully.' => 'Подзадача успешно се ажурираше', + 'Unable to update your sub-task.' => 'Не успеа да се ажурира подзадачата.', + 'Unable to create your sub-task.' => 'Не може да се создаде подзадача.', + 'Maximum size: ' => 'Максимална големина:', + 'Display another project' => 'Покажете друг проект', + 'Created by %s' => 'Направено од %s', + 'Tasks Export' => 'Извозни задачи', + 'Start Date' => 'Почетен датум', + 'Execute' => 'Изврши', + 'Task Id' => 'Идентификатор на задача', + 'Creator' => 'Авторот', + 'Modification date' => 'Датум на измена', + 'Completion date' => 'Датум на завршување', + 'Clone' => 'Клон', + 'Project cloned successfully.' => 'Проектот е успешно клониран.', + 'Unable to clone this project.' => 'Не може да се клонира проект.', + 'Enable email notifications' => 'Овозможете известувања по е-пошта', + 'Task position:' => 'Позиција на задачата:', + 'The task #%d has been opened.' => 'Задачата #%d е отворена.', + 'The task #%d has been closed.' => 'Задачата #%d е затворена.', + 'Sub-task updated' => 'Подзадача се смени', + 'Title:' => 'Наслов:', + 'Status:' => 'Статус', + 'Assignee:' => 'Извршител:', + 'Time tracking:' => 'Следење на време:', + 'New sub-task' => 'Нова подзадача', + 'New attachment added "%s"' => 'Вметнат е нов прилог "%s"', + 'New comment posted by %s' => 'Нов коментар оставен од %s', + 'New comment' => 'Нов коментар', + 'Comment updated' => 'Коментарот е ажуриран', + 'New subtask' => 'Нова подзадача', + 'I only want to receive notifications for these projects:' => 'Сакам известувања само за проекти:', + 'view the task on Kanboard' => 'преглед на задачи во Канборд', + 'Public access' => 'Пристап до јавноста', + 'Disable public access' => 'Забрани јавен пристап', + 'Enable public access' => 'Дозволете пристап до јавноста', + 'Public access disabled' => 'Оневозможен пристап до јавноста', + 'Move the task to another project' => 'Поместете ја задачата во друг проект', + 'Move to project' => 'Префрлете се на друг проект', + 'Do you really want to duplicate this task?' => 'Дали навистина сакате да ја копирате оваа задача?', + 'Duplicate a task' => 'Копирајте ја задачата', + 'External accounts' => 'Надворешни профил', + 'Account type' => 'Тип на профилот', + 'Local' => 'Локално', + 'Remote' => 'Далеку', + 'Enabled' => 'Овозможено', + 'Disabled' => 'Оневозможено', + 'Login:' => 'Пријавување:', + 'Full Name:' => 'Име и презиме', + 'Email:' => 'Е-пошта:', + 'Notifications:' => 'Известувања:', + 'Notifications' => 'Известувања', + 'Account type:' => 'Тип на профилот', + 'Edit profile' => 'Уреди го профилот', + 'Change password' => 'Промени го пасвордот', + 'Password modification' => 'Промени го пасвордот', + 'External authentications' => 'Надворешни идентификации', + 'Never connected.' => 'Никогаш не се поврзани', + 'No external authentication enabled.' => 'Не е овозможена надворешна проверка.', + 'Password modified successfully.' => 'Успешна промена на лозинка.', + 'Unable to change the password.' => 'Не можам да ја сменам лозинката.', + 'Change category' => 'Уреди категорија', + '%s updated the task %s' => '%s ја ажурираше задачата %s', + '%s opened the task %s' => '%s ја отвори задачата %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s ја премести задачата %s на позиција #%d во колоната "%s"', + '%s moved the task %s to the column "%s"' => '%s ја премести задачата %s во колоната "%s"', + '%s created the task %s' => '%s ја создаде задачата %s', + '%s closed the task %s' => '%s ја затвори задачата %s', + '%s created a subtask for the task %s' => '%s ја создаде подзадача за задача %s', + '%s updated a subtask for the task %s' => '%s ја измени подзадачата за задача %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Доделено на корисник %s со временска проценка %s/%sч', + 'Not assigned, estimate of %sh' => 'Неназначено, проценето време %sч', + '%s updated a comment on the task %s' => '%s ја ажурираше коментарот за задача %s', + '%s commented the task %s' => '%s коментираше за задачата %s', + '%s\'s activity' => '%s - активности', + 'RSS feed' => 'RSS-фид', + '%s updated a comment 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', + '%s created the task #%d' => '%s креирана задача #%d', + '%s closed the task #%d' => '%s затворена задача #%d', + '%s opened the task #%d' => '%s отворена задача #%d', + 'Activity' => 'Активности', + 'Default values are "%s"' => 'Стандардните вредности се: "%s"', + 'Default columns for new projects (Comma-separated)' => 'Стандардни колони за нов проект (одделена со запирка)', + 'Task assignee change' => 'Промена на извршител на задачи', + '%s changed the assignee of the task #%d to %s' => '%s го смени извршителот на задачите #%d во %s', + '%s changed the assignee of the task %s to %s' => '%s го смени извршителот на задачата %s во %s', + 'New password for the user "%s"' => 'Нова лозинка за корисникот "%s"', + 'Choose an event' => 'Изберете настан', + 'Create a task from an external provider' => 'Направете ја задачата преку посредник', + 'Change the assignee based on an external username' => 'Променете го извршителот врз основа на надворешно корисничко име', + 'Change the category based on an external label' => 'Променете ја категоријата врз основа на надворешната ознака', + 'Reference' => 'Референца', + 'Label' => 'Етикета', + 'Database' => 'База', + 'About' => 'Информации', + 'Database driver:' => 'Возач на база на податоци:', + 'Board settings' => 'Прилагодување на панелот', + 'Webhook settings' => 'Поставки за Webhook', + 'Reset token' => 'Ресетирајте го токенот', + 'API endpoint:' => 'Крајна точка на API:', + 'Refresh interval for personal board' => 'Интервал на освежување на лична табла', + 'Refresh interval for public board' => 'Интервал на освежување на јавната табла', + 'Task highlight period' => 'Период на обележување задача', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Период (во секунди) за кој се смета дека извршил измени во задачата (0 е оневозможен, стандарден е 2 дена)', + 'Frequency in second (60 seconds by default)' => 'Фреквенција во секунди (стандардно 60)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Фреквенција во секунди (0 ја исклучува оваа функционалност, 10 е стандардно)', + 'Application URL' => 'URL на апликација', + 'Token regenerated.' => 'Токенот е регенериран', + 'Date format' => 'Формат на датум', + 'ISO format is always accepted, example: "%s" and "%s"' => 'ИСО-форматот е секогаш прифатлив, пример: "%s", "%s"', + 'New personal project' => 'Нов личен проект', + 'This project is personal' => 'Овој проект е личен', + 'Add' => 'Додади', + 'Start date' => 'Почетен датум', + 'Time estimated' => 'Проценето време', + 'There is nothing assigned to you.' => 'Ништо не ти е доделено', + 'My tasks' => 'Моите задачи', + 'Activity stream' => 'Список на активности', + 'Dashboard' => 'Контролна табла', + 'Confirmation' => 'Потврда', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Create a comment from an external provider' => 'Направете коментар преку надворешен посредник', + 'Project management' => 'Уредување на проектот', + 'Columns' => 'Колумни', + 'Task' => 'Доделување', + 'Percentage' => 'Процент', + 'Number of tasks' => 'Број на задачи', + 'Task distribution' => 'Поделба на задачи', + 'Analytics' => 'Анализа', + 'Subtask' => 'Подзадача', + 'User repartition' => 'Кориснички одговорности', + 'Clone this project' => 'Клонирајте го проектот', + 'Column removed successfully.' => 'Колоната е успешно отстранета.', + 'Not enough data to show the graph.' => 'Недоволни податоци за графиконот.', + 'Previous' => 'Претходна', + 'The id must be an integer' => 'ID мора да биде цел број', + 'The project id must be an integer' => 'Проект проект мора да биде цел број', + 'The status must be an integer' => 'Статусот мора да биде цел број', + 'The subtask id is required' => 'Потребен е ИД на подзадача', + 'The subtask id must be an integer' => 'ИД на подзадача мора да биде цел број', + 'The task id is required' => 'Потребен е ИД на задача', + 'The task id must be an integer' => 'ИД на задачата мора да биде цел број', + 'The user id must be an integer' => 'Корисничкиот ИД мора да биде цел број', + 'This value is required' => 'Оваа вредност е потребна', + 'This value must be numeric' => 'Оваа вредност мора да биде број', + 'Unable to create this task.' => 'Не може да се создаде оваа задача.', + 'Cumulative flow diagram' => 'Дијаграм на кумулативен проток', + 'Daily project summary' => 'Дневно резиме на проектот', + 'Daily project summary export' => 'Дневно резиме на проект - извоз', + 'Exports' => 'Извозот', + 'This export contains the number of tasks per column grouped per day.' => 'Овој извоз содржи број на задачи по колона групирани по денови.', + 'Active swimlanes' => 'Активна патека', + 'Add a new swimlane' => 'Додадете нова патека', + 'Default swimlane' => 'Стандардна патека', + 'Do you really want to remove this swimlane: "%s"?' => 'Дали навистина сакате да ја отстраните оваа патека: "%s"?', + 'Inactive swimlanes' => 'Неактивни патеки', + 'Remove a swimlane' => 'Отстрани патека', + 'Swimlane modification for the project "%s"' => 'Измена на патеката за проектот "%s"', + 'Swimlane removed successfully.' => 'Патеката е успешно отстранета.', + 'Swimlanes' => 'Патеки', + 'Swimlane updated successfully.' => 'Патеката е успешно ажурирана.', + 'Unable to remove this swimlane.' => 'Не може да се отстрани оваа патеката', + 'Unable to update this swimlane.' => 'Не може да се ажурира оваа патеката', + 'Your swimlane has been created successfully.' => 'Вашата патека е успешно креирана.', + 'Example: "Bug, Feature Request, Improvement"' => 'На пример: „Грешка, барање за функционалност, подобрување“', + 'Default categories for new projects (Comma-separated)' => 'Стандардни категории за нови проекти', + 'Integrations' => 'Интеграција', + 'Integration with third-party services' => 'Интеграција со надворешни услуги', + 'Subtask Id' => 'ИД на подзадача', + 'Subtasks' => 'Подзадачи', + 'Subtasks Export' => 'Извоз на подподатоци', + 'Task Title' => 'Наслов на задача', + 'Untitled' => 'Без наслов', + 'Application default' => 'Стандардна апликација', + 'Language:' => 'Јазик:', + 'Timezone:' => 'Временска зона:', + 'All columns' => 'Сите колони', + 'Next' => 'Следното', + '#%d' => '#%d', + 'All swimlanes' => 'Сите патеки', + 'All colors' => 'Сите бои', + 'Moved to column %s' => 'Преместено во колоната %s', + 'User dashboard' => 'Кориснички контролен панел', + 'Allow only one subtask in progress at the same time for a user' => 'Дозволете само една „задача во тек“ по корисник', + 'Edit column "%s"' => 'Уредете ја колоната "%s"', + '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?' => 'Кои делови од проектот сакате да ги дуплирате', + 'Disallow login form' => 'Забранете го формуларот за апликација', + 'Start' => 'Почеток', + 'End' => 'Крај', + 'Task age in days' => 'Возраст на задачи во денови', + 'Days in this column' => 'Денови во оваа колона', + '%dd' => '%dd', + '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' => 'Модификација на врската', + 'Opposite label' => 'Наспроти етикетата', + 'Remove a link' => 'Отстранете ја врската', + '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' => '<1ч', + '%dh' => '%dч', + 'Expand tasks' => 'Проширете ги задачите', + 'Collapse tasks' => 'Собери ги задачите', + 'Expand/collapse tasks' => 'Задачи проширување/соберивање', + 'Close dialog box' => 'Затвори дијалог-кутија', + 'Submit a form' => 'Поднесете формулар', + 'Board view' => 'Преглед на табла', + 'Keyboard shortcuts' => 'Кратенки за тастатура', + 'Open board switcher' => 'Отворете ги прекинувачите на панелот', + 'Application' => 'Апликација', + 'Compact view' => 'Компактен преглед', + 'Horizontal scrolling' => 'Хоризонтално лизгање', + 'Compact/wide view' => 'Собери / прошири го прегледот', + 'Currency' => 'Валута', + 'Personal project' => 'Личен проект', + 'AUD - Australian Dollar' => 'АUD- австралиски долар', + 'CAD - Canadian Dollar' => 'CAD - канадски долар', + 'CHF - Swiss Francs' => 'CHF - швајцарски франк', + 'Custom Stylesheet' => 'Прилагоден стил', + 'EUR - Euro' => 'ЕUR - Евра', + 'GBP - British Pound' => 'GBP - британска фунта', + 'INR - Indian Rupee' => 'INR - индиска дупка', + 'JPY - Japanese Yen' => 'JPY - јапонски јен', + 'NZD - New Zealand Dollar' => 'NZD - Долар од Нов Зеланд', + 'PEN - Peruvian Sol' => 'ПЕН - Перуански Сол', + 'RSD - Serbian dinar' => 'RSD - српски динар', + 'CNY - Chinese Yuan' => 'CNY - кинески јен', + 'USD - US Dollar' => 'USD - американски долар', + 'VES - Venezuelan Bolívar' => 'VES - венецуелски боливар', + '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' => 'Изворната колона', + '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' => 'Променете ја референтната валута', + 'Reference currency' => 'Референтна валута', + 'The currency rate has been added successfully.' => 'Девизната стапка е успешно додадена.', + 'Unable to add this currency rate.' => 'Не може да се додаде оваа стапка на валута.', + 'Webhook URL' => 'Webhook URL-адреса', + '%s removed the assignee of the task %s' => '%s го отстрани извршителот на задачите %s', + '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' => 'Два фактори за проверка', + 'This QR code contains the key URI: ' => 'Овој QR-код го содржи клучот URL:', + 'Check my code' => 'Проверете го мојот код', + 'Secret key: ' => 'Таен клуч:', + 'Test your device' => 'Тестирајте го вашиот уред', + 'Assign a color when the task is moved to a specific column' => 'Доделете боја кога задачата се преместува во избраната колона', + '%s via Kanboard' => '%s преку Канборд', + 'Burndown chart' => 'Преостаната работа / време', + 'This chart show the task complexity over the time (Work Remaining).' => 'Овој графикон ја покажува комплексноста на задачата со текот на времето (преостаната работа)', + 'Screenshot taken %s' => 'Снимката на екранот е направена %s', + 'Add a screenshot' => 'Додајте слика од екранот', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Направете слика од екранот и притиснете CTRL + V или ⌘ + V за да залепите тука.', + 'Screenshot uploaded successfully.' => 'Сликата на екранот е успешно додадена.', + 'SEK - Swedish Krona' => 'SEK - шведска круна', + 'Identifier' => 'Идентификатор', + 'Disable two factor authentication' => 'Оневозможете автентикација со два фактори', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Дали сте сигурни дека сакате да ја оневозможите автентикацијата со два фактори за овој корисник: "%s"?', + 'Edit link' => 'Уредете ја врската', + 'Start to type task title...' => 'Започнете да пишувате наслов на задача ...', + 'A task cannot be linked to itself' => 'Задачата не може да биде поврзана со самата себе', + 'The exact same link already exists' => 'Идентична врска веќе постои', + 'Recurrent task is scheduled to be generated' => 'Подготвена е периодична задача да се креира', + 'Score' => 'Рејтинг', + 'The identifier must be unique' => 'Идентификаторот мора да биде единствен', + 'This linked task id doesn\'t exists' => 'Овој ИД на поврзана задача не постои', + 'This value must be alphanumeric' => 'Оваа вредност мора да биде алфанумеричка', + 'Edit recurrence' => 'Променете го повторувањето', + 'Generate recurrent task' => 'Направете повторувачка задача', + 'Trigger to generate recurrent task' => 'Активирање што прави повторлива задача', + 'Factor to calculate new due date' => 'Фактор за пресметување на новиот рок за завршување', + 'Timeframe to calculate new due date' => 'Временска рамка за пресметување на новиот рок за завршување', + 'Base date to calculate new due date' => 'Датум на започнување за пресметување на новиот рок', + 'Action date' => 'Датум на дејствување', + 'Base date to calculate new due date: ' => 'Датум на започнување за пресметување на новиот рок:', + 'This task has created this child task: ' => 'Оваа задача го направи детето задача:', + 'Day(s)' => 'Ден(и)', + 'Existing due date' => 'Постоечки рок за завршување', + 'Factor to calculate new due date: ' => 'Фактор за пресметување на новиот рок за завршување:', + 'Month(s)' => 'Месец(и)', + 'This task has been created by: ' => 'Оваа задача беше направена од:', + 'Recurrent task has been generated:' => 'Генерирана е периодична задача:', + 'Timeframe to calculate new due date: ' => 'Временска рамка за пресметување на новиот рок за завршување:', + 'Trigger to generate recurrent task: ' => 'Активирање за создавање повторувачка задача', + 'When task is closed' => 'Кога задачата е затворена', + 'When task is moved from first column' => 'Кога задачата се преместува од првата колона', + 'When task is moved to last column' => 'Кога задачата е преместена во последната колона', + 'Year(s)' => 'Годинa(и)', + 'Project settings' => 'Поставки на проектот', + 'Automatically update the start date' => 'Автоматски ажурирајте го датумот на започнување', + 'iCal feed' => 'iCal канал', + 'Preferences' => 'Поставки', + 'Security' => 'Безбедност', + 'Two factor authentication disabled' => 'Автентикацијата со два фактори е оневозможена', + 'Two factor authentication enabled' => 'Овозможена е автентикација со два фактори', + 'Unable to update this user.' => 'Не може да се ажурира овој корисник', + 'There is no user management for personal projects.' => 'Не постои механизам за управување со корисниците за лични проекти.', + 'User that will receive the email' => 'Корисникот кој ќе ја добие е-поштата', + 'Email subject' => 'Предмет на е-пошта', + 'Date' => 'Датум', + 'Add a comment log when moving the task between columns' => 'Додадете коментар во дневникот кога задачата се преместува од една во друга колона', + 'Move the task to another column when the category is changed' => 'Поместете ја задачата во втората колона кога категоријата се менува', + 'Send a task by email to someone' => 'Задачата испратете ја по е-пошта некому', + 'Reopen a task' => 'Повторно отворете ја задачата', + 'Notification' => 'Известување', + '%s moved the task #%d to the first swimlane' => '%s ја премести задачата #%d на првата патека', + 'Swimlane' => 'Патека', + '%s moved the task %s to the first swimlane' => '%s ја премести задачата %s на првата патека', + '%s moved the task %s to the swimlane "%s"' => '%s ја премести задачата %s на патеката "%s"', + 'This report contains all subtasks information for the given date range.' => 'Овој извештај ги содржи сите информации за подзадачите во даден период', + 'This report contains all tasks information for the given date range.' => 'Овој извештај ги содржи сите информации за задачите во даден период', + 'Project activities for %s' => 'Проектни активности за %s', + 'view the board on Kanboard' => 'преглед на табла на Канборд', + 'The task has been moved to the first swimlane' => 'Задачата е преместена на првата патека', + 'The task has been moved to another swimlane:' => 'Задачата е преместена на друга патека:', + 'New title: %s' => 'Нов наслов: %s', + 'The task is not assigned anymore' => 'Задачата веќе нема извршител', + 'New assignee: %s' => 'Нов извршител: %s', + 'There is no category now' => 'Сега нема категорија', + 'New category: %s' => 'Нова категорија: %s', + 'New color: %s' => 'Нова боја: %s', + 'New complexity: %d' => 'Нова сложеност: %d', + 'The due date has been removed' => 'Рокот за завршување е отстранет', + 'There is no description anymore' => 'Нема повеќе описи', + 'Recurrence settings has been modified' => 'Променети поставки за периодични задачи', + 'Time spent changed: %sh' => 'Времето поминато се смени: %sч', + 'Time estimated changed: %sh' => 'Проценетото време се смени: %sч', + 'The field "%s" has been updated' => 'Полето "%s" е ажурирано', + 'The description has been modified:' => 'Описот е променет:', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Дали навистина сакате да ја затворите задачата "%s", како и сите подзадачи?', + 'I want to receive notifications for:' => 'Сакам да добивам известувања за:', + 'All tasks' => 'Сите задачи', + 'Only for tasks assigned to me' => 'Само за задачи за кои сум извршител', + 'Only for tasks created by me' => 'Само за задачи што ги креирав', + 'Only for tasks created by me and tasks assigned to me' => 'Само за задачи што ги креирав или сум извршител на задачата', + '%%Y-%%m-%%d' => '%%Y-%%m-%%d', + 'Total for all columns' => 'Вкупно за сите колони', + 'You need at least 2 days of data to show the chart.' => 'Потребни ви се најмалку 2 дена податоци за да ја прикажете табелата.', + '<15m' => '<15м', + '<30m' => '<30м', + 'Stop timer' => 'Запрете го тајмерот', + 'Start timer' => 'Започнете го тајмерот', + 'My activity stream' => 'Текот на моите активности', + 'Search tasks' => 'Задачи за пребарување', + 'Reset filters' => 'Ресетирајте ги филтрите', + 'My tasks due tomorrow' => 'Моите задачи да бидат завршени утре', + 'Tasks due today' => 'Задачите да бидат завршени денес', + 'Tasks due tomorrow' => 'Задачите ќе бидат завршени утре', + 'Tasks due yesterday' => 'Задачи што требаше да бидат завршени вчера', + 'Closed tasks' => 'Затворени задачи', + 'Open tasks' => 'Отворени задачи', + 'Not assigned' => 'Без извршител', + 'View advanced search syntax' => 'Погледнете синтакса за напредно пребарување', + 'Overview' => 'Преглед', + 'Board/Calendar/List view' => 'Преглед на табела / календар / список', + 'Switch to the board view' => 'Префрлете се на погледот на таблата', + 'Switch to the list view' => 'Префрлете се на прегледот на списокот', + 'Go to the search/filter box' => 'Одете во полето за пребарување / филтер', + 'There is no activity yet.' => 'Сè уште нема активност.', + 'No tasks found.' => 'Задачите не се пронајдени.', + 'Keyboard shortcut: "%s"' => 'Кратенка за тастатура: "%s"', + 'List' => 'Список', + 'Filter' => 'Филтер', + 'Advanced search' => 'Напредно пребарување', + 'Example of query: ' => 'Пример за пребарување:', + 'Search by project: ' => 'Пребарување по проект:', + 'Search by column: ' => 'Пребарување по колона:', + 'Search by assignee: ' => 'Пребарување по извршителот:', + 'Search by color: ' => 'Пребарување според боја:', + 'Search by category: ' => 'Пребарај по категорија:', + 'Search by description: ' => 'Пребарување по опис:', + 'Search by due date: ' => 'Пребарување според рок за завршување:', + 'Average time spent in each column' => 'Просечно време поминато во секоја колона', + 'Average time spent' => 'Просечно поминато време', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Овој графикон го покажува просечното време поминато во секоја колона за последните %d задачи.', + 'Average Lead and Cycle time' => 'Просечно време на реализација и циклус:', + 'Average lead time: ' => 'Просечно време на реализација:', + 'Average cycle time: ' => 'Просечно време на циклус:', + 'Cycle Time' => 'Време на циклус', + 'Lead Time' => 'Време на реализација', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Овој графикон ги прикажува просечното време на реализација и циклусите за последните %d задачи со текот на времето.', + 'Average time into each column' => 'Просечно време во секоја колона', + 'Lead and cycle time' => 'Време на реализација и циклус', + 'Lead time: ' => 'Време на реализација:', + 'Cycle time: ' => 'Време на циклус:', + 'Time spent in each column' => 'Време поминато во секоја колона', + 'The lead time is the duration between the task creation and the completion.' => 'Време на реализација е времето што поминало помеѓу отворање и затворање на задача.', + 'The cycle time is the duration between the start date and the completion.' => 'Време на циклус е времето што поминало помеѓу почетокот и крајот на работата на задачата.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Ако задачата не е затворена, се користи тековното време наместо датумот на крај.', + 'Set the start date automatically' => 'Автоматски поставете го времето на започнување', + 'Edit Authentication' => 'Уредете ја автентикацијата', + 'Remote user' => 'Далечен корисник', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Далечинските корисници не ја чуваат својата лозинка во базата на податоци на Kanboard, примери: LDAP, Google и Github профил.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Ако сте го провериле полето „Забрани формулар за апликација“, податоците за внесување во формуларот за апликација ќе бидат игнорирани.', + 'Default task color' => 'Стандардна боја на задачата', + 'This feature does not work with all browsers.' => 'Оваа функционалност не работи на сите прелистувачи.', + 'There is no destination project available.' => 'Нема достапен проект за дестинација.', + 'Trigger automatically subtask time tracking' => 'Активирајте автоматски следете го следењето на времето.', + 'Include closed tasks in the cumulative flow diagram' => 'Вклучете ги затворените задачи во табелата за кумулативен проток', + 'Current swimlane: %s' => 'Тековна патека: %s', + 'Current column: %s' => 'Тековна колона: %s', + 'Current category: %s' => 'Тековна категорија: %s', + 'no category' => 'без категорија', + 'Current assignee: %s' => 'Тековен извршител: %s', + 'not assigned' => 'не е доделен', + 'Author:' => 'Авторот:', + 'contributors' => 'соработници', + 'License:' => 'Лиценца:', + 'License' => 'Лиценца', + 'Enter the text below' => 'Внесете го текстот подолу', + 'Start date:' => 'Време на започнување:', + 'Due date:' => 'Рок за завршување:', + 'People who are project managers' => 'Луѓе кои се менаџери на проекти', + 'People who are project members' => 'Луѓе кои се членови на проектот', + 'NOK - Norwegian Krone' => 'NOK - норвешка круна', + 'Show this column' => 'Покажете ја оваа колона', + 'Hide this column' => 'Скријте ја оваа колона', + 'End date' => 'Датум на завршување', + 'Users overview' => 'Преглед на корисник', + 'Members' => 'Членови', + 'Shared project' => 'Заеднички проект', + 'Project managers' => 'Менаџери на проекти', + 'Projects list' => 'Список на проекти', + 'End date:' => 'Датум на завршување:', + 'Change task color when using a specific task link' => 'Променете ја бојата на задачата кога се користи специфичен линк за задачата', + 'Task link creation or modification' => 'Врската за задачата е направена или изменета', + 'Milestone' => 'Прекретница', + 'Reset the search/filter box' => 'Ресетирајте го полето за пребарување / филтер', + 'Documentation' => 'Документација', + 'Author' => 'Авторот', + 'Version' => 'Верзија', + 'Plugins' => 'Додатоци', + 'There is no plugin loaded.' => 'Не се вчитани додатоци.', + 'My notifications' => 'Моите известувања', + 'Custom filters' => 'Прилагодени филтри', + 'Your custom filter has been created successfully.' => 'Вашиот прилагоден филтер е успешно креиран.', + 'Unable to create your custom filter.' => 'Не може да се создаде сопствен филтер.', + 'Custom filter removed successfully.' => 'Прилагодениот филтер е успешно отстранет.', + 'Unable to remove this custom filter.' => 'Не може да се отстрани прилагодениот филтер.', + 'Edit custom filter' => 'Уредете сопствен филтер', + 'Your custom filter has been updated successfully.' => 'Прилагодениот филтер е успешно ажуриран.', + 'Unable to update custom filter.' => 'Не може да се ажурира прилагодениот филтер', + 'Web' => 'Веб', + 'New attachment on task #%d: %s' => 'Нов прилог на задачата #%d: %s', + 'New comment on task #%d' => 'Нов коментар за задачата #%d', + 'Comment updated on task #%d' => 'Ажуриран коментар за задачата #%d', + 'New subtask on task #%d' => 'Нова подзадача на задачата #%d', + 'Subtask updated on task #%d' => 'Подзадача ажурирана на задачата #%d', + 'New task #%d: %s' => 'Нова задача #%d: %s', + 'Task updated #%d' => 'Задачата е ажурирана #%d', + 'Task #%d closed' => 'Задачата #%d е затворена', + 'Task #%d opened' => 'Задачата #%d е отворена', + 'Column changed for task #%d' => 'Променета колона за задача #%d', + 'New position for task #%d' => 'Нова позиција за задача #%d', + 'Swimlane changed for task #%d' => 'Патеката е променета за задачата #%d', + 'Assignee changed on task #%d' => 'Извршителот беше променет на задачата #%d', + '%d overdue tasks' => '%d доцни задачи', + 'No notification.' => 'Нема нови известувања.', + 'Mark all as read' => 'Обележете сè како прочитано', + 'Mark as read' => 'Означи како прочитано', + 'Total number of tasks in this column across all swimlanes' => 'Вкупниот број на задачи во оваа колона во сите патеки', + 'Collapse swimlane' => 'Соберете ја патеката', + 'Expand swimlane' => 'Проширете ја патеката', + 'Add a new filter' => 'Додадете нов филтер', + 'Share with all project members' => 'Споделете со сите членови на проектот', + 'Shared' => 'Поделени', + 'Owner' => 'Сопственикот', + 'Unread notifications' => 'Непрочитани известувања', + 'Notification methods:' => 'Методи за известување:', + 'Unable to read your file' => 'Не може да се прочита датотеката', + '%d task(s) have been imported successfully.' => '%d задачите се успешно увезени.', + 'Nothing has been imported!' => 'Ништо увезено!', + 'Import users from CSV file' => 'Увезете корисници преку CSV-датотека', + '%d user(s) have been imported successfully.' => '%d корисниците успешно се увезени.', + 'Comma' => 'Запирка', + 'Semi-colon' => 'Точка и запирка', + 'Tab' => 'Таб', + 'Vertical bar' => 'Вертикална лента', + 'Double Quote' => 'Двојни цитати', + 'Single Quote' => 'Синглови цитати', + '%s attached a file to the task #%d' => '%s додаде нова датотека на задачата #%d', + 'There is no column or swimlane activated in your project!' => 'Нема колони или активни патеки во вашиот проект!', + 'Append filter (instead of replacement)' => 'Додадете филтер (наместо да го замените постоечкиот)', + 'Append/Replace' => 'Додај / замени', + 'Append' => 'Додади', + 'Replace' => 'Заменете', + 'Import' => 'Увоз', + 'Change sorting' => 'Променете го сортирањето', + 'Tasks Importation' => 'Задачи за увоз', + 'Delimiter' => 'Делимтер', + 'Enclosure' => 'Прилог', + 'CSV File' => 'CSV-датотека', + 'Instructions' => 'Инструкции', + 'Your file must use the predefined CSV format' => 'Вашата датотека мора да користи предефиниран CSV формат', + 'Your file must be encoded in UTF-8' => 'Вашата датотека мора мора да биде кодирана у UTF-8', + 'The first row must be the header' => 'Првата линија мора да биде заглавие', + 'Duplicates are not verified for you' => 'Дупликатите нема да бидат проверени (мора да го направите тоа сами)', + 'The due date must use the ISO format: YYYY-MM-DD' => 'Рок за завршување мора да биде во ISO формат: YYYY-MM-DD', + 'Download CSV template' => 'Преземете го шаблонот CSV', + 'No external integration registered.' => 'Нема регистрирано надворешни интеграции.', + 'Duplicates are not imported' => 'Дупликатите не се увезуваат', + 'Usernames must be lowercase and unique' => 'Корисничкото име мора да биде со мали букви и уникатно', + 'Passwords will be encrypted if present' => 'Лозинките (доколку ги има) ќе бидат шифрирани', + '%s attached a new file to the task %s' => '%s додаде нова датотека во задачата %s', + 'Link type' => 'Тип на врска', + 'Assign automatically a category based on a link' => 'Автоматско доделување категории базирани на врска', + 'BAM - Konvertible Mark' => 'BAM - Конвертибилна марка', + 'Assignee Username' => 'Корисничко име на извршителот', + 'Assignee Name' => 'Име на извршител', + 'Groups' => 'Групи', + 'Members of %s' => 'Членови на %s', + 'New group' => 'Нова група', + 'Group created successfully.' => 'Групата е успешно создадена.', + 'Unable to create your group.' => 'Не може да се создаде група.', + 'Edit group' => 'Уредете ја групата', + 'Group updated successfully.' => 'Групата е успешно ажурирана.', + 'Unable to update your group.' => 'Не може да се ажурира групата', + 'Add group member to "%s"' => 'Додадете член во групата "%s"', + 'Group member added successfully.' => 'Успешно додаден член на групата.', + 'Unable to add group member.' => 'Не може да се додаде член во групата.', + 'Remove user from group "%s"' => 'Отстранете го корисникот од групата "%s"', + 'User removed successfully from this group.' => 'Корисникот е успешно отстранет од групата.', + 'Unable to remove this user from the group.' => 'Не може да се отстрани корисникот од групата.', + 'Remove group' => 'Отстрани ја групата', + 'Group removed successfully.' => 'Групата е успешно отстранета.', + 'Unable to remove this group.' => 'Не може да се отстрани групата.', + 'Project Permissions' => 'Дозволи за проектот', + 'Manager' => 'Менаџер', + 'Project Manager' => 'Менаџер на проект', + 'Project Member' => 'Член на проектот', + 'Project Viewer' => 'Набverудувач на проектот', + 'Your account is locked for %d minutes' => 'Вашиот профил е заклучен во следните %d минути', + 'Invalid captcha' => 'Погрешно фаќање', + 'The name must be unique' => 'Името мора да биде единствено', + 'View all groups' => 'Прелистајте ги сите групи', + 'There is no user available.' => 'Нема достапни корисници.', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Дали сте сигурни дека сакате да отстраните "%s" од групата "%s"?', + 'There is no group.' => 'Нема група', + 'Add group member' => 'Додадете член на групата', + 'Do you really want to remove this group: "%s"?' => 'Дали сте сигурни дека сакате да ја отстраните оваа група: "%s"?', + 'There is no user in this group.' => 'Во моментов нема корисници во оваа група.', + 'Permissions' => 'Дозволи', + 'Allowed Users' => 'Дозволени корисници', + 'No specific user has been allowed.' => 'Нема специфичен корисник.', + 'Role' => 'Улоги', + 'Enter user name...' => 'Внесете корисничко име ...', + 'Allowed Groups' => 'Дозволени групи', + 'No group has been allowed.' => 'Не се дозволени групи.', + 'Group' => 'Група', + 'Group Name' => 'Име на групата', + 'Enter group name...' => 'Внесете име на групата ...', + 'Role:' => 'Улогата:', + 'Project members' => 'Членови на проектот', + '%s mentioned you in the task #%d' => '%s ве спомна во задачата #%d', + '%s mentioned you in a comment on the task #%d' => '%s ве спомна во у коментар за задачата #%d', + 'You were mentioned in the task #%d' => 'Вие сте споменати во задачата #%d', + 'You were mentioned in a comment on the task #%d' => 'Вие сте споменати во коментарот за задачата #%d', + 'Estimated hours: ' => 'Проценети часови:', + 'Actual hours: ' => 'Навистина часови:', + 'Hours Spent' => 'Поминати часови:', + 'Hours Estimated' => 'Проценети часови', + 'Estimated Time' => 'Проценето време', + 'Actual Time' => 'Вистинско време', + 'Estimated vs actual time' => 'Проценето / вистинско време', + 'RUB - Russian Ruble' => 'RUB - руска руба', + 'Assign the task to the person who does the action when the column is changed' => 'Доделете ја задачата на лицето кое го извршило дејството за промена на колоната', + 'Close a task in a specific column' => 'Затворете ја задачата во одредена колона', + 'Time-based One-time Password Algorithm' => 'Алгоритам за еднократна лозинка заснована на време', + 'Two-Factor Provider: ' => 'Двофакторски добавувач:', + 'Disable two-factor authentication' => 'Оневозможете автентикација со два фактори', + 'Enable two-factor authentication' => 'Овозможете автентикација со два фактори', + 'There is no integration registered at the moment.' => 'Во моментов не се регистрирани интеграции.', + 'Password Reset for Kanboard' => 'Променете ја лозинката за Канборд', + 'Forgot password?' => 'Ја заборави лозинката?', + 'Enable "Forget Password"' => 'Овозможете ја заборавената лозинка', + 'Password Reset' => 'Сменете ја вашата лозинка', + 'New password' => 'Нова лозинка', + 'Change Password' => 'Променете ја лозинката', + 'To reset your password click on this link:' => 'За промена на лозинката кликнете на овој линк:', + 'Last Password Reset' => 'Последна промена на лозинка', + 'The password has never been reinitialized.' => 'Лозинката никогаш не е променета.', + 'Creation' => 'Направени', + 'Expiration' => 'Истекување', + 'Password reset history' => 'Историја на промена на лозинка', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Сите задачи во колоната "%s" и патеката "%s" се успешно затворени.', + 'Do you really want to close all tasks of this column?' => 'Дали навистина сакате да ги затворите сите задачи во оваа колона?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d Задачa(и) во колоната "%s" и патеката "%s" ќе бидат затворени.', + 'Close all tasks in this column and this swimlane' => 'Затворете ги сите задачи во оваа колона и оваа патека', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Ниту еден додаток не го регистрираше методот на известување за проектот. Сè уште можете да конфигурирате индивидуални известувања во вашиот кориснички профил.', + 'My dashboard' => 'Мојата табла', + 'My profile' => 'Мој Профил', + 'Project owner: ' => 'Сопственик на проектот:', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'Идентификаторот на проектот е опционален, тој мора да биде алфанумерички, на пример: МОЈ ПРОЕКТ.', + 'Project owner' => 'Сопственик на проектот', + 'Personal projects do not have users and groups management.' => 'Личните проекти не управуваат со корисници и групи.', + 'There is no project member.' => 'Нема членови на проектот.', + 'Priority' => 'Приоритет', + 'Task priority' => 'Приоритет на задачата', + 'General' => 'Општи', + 'Dates' => 'Датуми', + 'Default priority' => 'Стандарден приоритет', + 'Lowest priority' => 'Најнизок приоритет', + 'Highest priority' => 'Највисок приоритет', + 'Close a task when there is no activity' => 'Затворете ја задачата кога нема активност', + 'Duration in days' => 'Времетраење во денови', + 'Send email when there is no activity on a task' => 'Испратете е-пошта кога нема активност на задачата', + 'Unable to fetch link information.' => 'Не можам да добијам информации за врска.', + 'Daily background job for tasks' => 'Дневни работни места во позадина за задачи', + 'Auto' => 'Автоматски', + 'Related' => 'Поврзан', + 'Attachment' => 'Прилог', + 'Web Link' => 'Веб линк', + 'External links' => 'Надворешни врски', + 'Add external link' => 'Додадете надворешен линк', + 'Type' => 'Видови', + 'Dependency' => 'Зависност', + 'Add internal link' => 'Додадете внатрешна врска', + 'Add a new external link' => 'Додадете нова надворешна врска', + 'Edit external link' => 'Уредете ја надворешната врска', + 'External link' => 'Надворешна врска', + 'Copy and paste your link here...' => 'Копирајте ја и залепете ја вашата врска овде ...', + 'URL' => 'URL-адреса', + 'Internal links' => 'Внатрешни врски', + 'Assign to me' => 'Назначи ми', + 'Me' => 'Јас', + 'Do not duplicate anything' => 'Не дуплирајте ништо', + 'Projects management' => 'Управување со проекти', + 'Users management' => 'Управување со корисниците', + 'Groups management' => 'Менаџмент на група', + 'Create from another project' => 'Направете од друг проект', + 'open' => 'отворено', + 'closed' => 'затворен', + 'Priority:' => 'Приоритет:', + 'Reference:' => 'Референца:', + 'Complexity:' => 'Сложеност:', + 'Swimlane:' => 'Патека:', + 'Column:' => 'Колона:', + 'Position:' => 'Позиција:', + 'Creator:' => 'Творец:', + 'Time estimated:' => 'Очекувано време:', + '%s hours' => '%s часови', + 'Time spent:' => 'Потрошено време:', + 'Created:' => 'Создадено на:', + 'Modified:' => 'Изменето:', + 'Completed:' => 'Завршено:', + 'Started:' => 'Почна:', + 'Moved:' => 'Преместен:', + 'Task #%d' => 'Задача #%d', + 'Time format' => 'Формат на време', + 'Start date: ' => 'Почетен датум:', + 'End date: ' => 'Крајна дата:', + 'New due date: ' => 'Нов рок за завршување:', + 'Start date changed: ' => 'Датумот на почеток е променет:', + 'Disable personal projects' => 'Оневозможете лични проекти', + 'Do you really want to remove this custom filter: "%s"?' => 'Дали сте сигурни дека сакате да го отстраните овој прилагоден филтер "%s"?', + 'Remove a custom filter' => 'Отстранете го прилагодениот филтер', + 'User activated successfully.' => 'Корисникот е успешно активиран.', + 'Unable to enable this user.' => 'Не може да се овозможи овој корисник.', + 'User disabled successfully.' => 'Корисникот е успешно оневозможен.', + 'Unable to disable this user.' => 'Не може да се оневозможи овој корисник.', + 'All files have been uploaded successfully.' => 'Сите датотеки беа успешно поставени.', + 'The maximum allowed file size is %sB.' => 'Максималната дозволена големина на датотека е %sB.', + 'Drag and drop your files here' => 'Повлечете ги и испуштете ги вашите датотеки овде', + 'choose files' => 'изберете датотеки', + 'View profile' => 'Види профил', + 'Two Factor' => 'Два фактори', + 'Disable user' => 'Оневозможете го корисникот', + 'Do you really want to disable this user: "%s"?' => 'Дали навистина сакате да го оневозможите овој корисник: "%s"?', + 'Enable user' => 'Овозможете корисник', + 'Do you really want to enable this user: "%s"?' => 'Дали навистина сакате да го овозможите овој корисник: "%s"?', + 'Download' => 'Преземено', + 'Uploaded: %s' => 'Испратено: %s', + 'Size: %s' => 'Големина: %s', + 'Uploaded by %s' => 'Испратено од %s', + 'Filename' => 'Име на датотека', + 'Size' => 'Големина', + 'Column created successfully.' => 'Колоната е успешно креирана.', + 'Another column with the same name exists in the project' => 'Во овој проект веќе постои колона со исто име.', + 'Default filters' => 'Стандардни филтри', + 'Your board doesn\'t have any columns!' => 'Вашата табла нема една колона!', + 'Change column position' => 'Променете ја положбата на колоната', + 'Switch to the project overview' => 'Префрлете се на преглед на проектот', + 'User filters' => 'Кориснички филтри', + 'Category filters' => 'Категорија флетери', + 'Upload a file' => 'Поставете датотека', + 'View file' => 'Погледнете ја датотеката', + 'Last activity' => 'Последна активност', + 'Change subtask position' => 'Променете ја положбата на подзадачата', + 'This value must be greater than %d' => 'Оваа вредност мора да биде поголема од %d', + 'Another swimlane with the same name exists in the project' => 'Друга патека со исто име постои во проектот', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'На пример: https://example.kanboard.org/ (се користи за генерирање на апсолутна URL-адреса)', + 'Actions duplicated successfully.' => 'Дејствата успешно се дуплираат.', + 'Unable to duplicate actions.' => 'Не може да се дуплираат дејства.', + 'Add a new action' => 'Додадете нова акција', + 'Import from another project' => 'Увоз од друг проект', + 'There is no action at the moment.' => 'Во моментов нема активности.', + 'Import actions from another project' => 'Увезете акции од друг проект', + 'There is no available project.' => 'Во моментов нема достапни проекти.', + 'Local File' => 'Локална датотека', + 'Configuration' => 'Конфигурација', + 'PHP version:' => 'Верзија за PHP:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'Верзија за ОС:', + 'Database version:' => 'Верзија на базата на податоци:', + 'Browser:' => 'Прелистувач:', + 'Task view' => 'Преглед на задача', + 'Edit task' => 'Уредете ја задачата', + 'Edit description' => 'Уредете го описот', + 'New internal link' => 'Нова внатрешна врска', + 'Display list of keyboard shortcuts' => 'Покажете список со кратенки на тастатурата', + 'Avatar' => 'Аватар', + 'Upload my avatar image' => 'Поставете слика на аватар', + 'Remove my image' => 'Отстранете ја мојата слика', + 'The OAuth2 state parameter is invalid' => 'Параметарот на состојбата OAuth2 е невалиден', + 'User not found.' => 'Корисникот не е пронајден.', + 'Search in activity stream' => 'Активности за пребарување', + 'My activities' => 'Моите активности', + 'Activity until yesterday' => 'Активности до вчера', + 'Activity until today' => 'Активности до денес', + 'Search by creator: ' => 'Пребарување според креаторот:', + 'Search by creation date: ' => 'Пребарување според датумот на создавање:', + 'Search by task status: ' => 'Пребарување по статус на задача:', + 'Search by task title: ' => 'Пребарување според наслов на задача:', + 'Activity stream search' => 'Пребарување активност', + 'Projects where "%s" is manager' => 'Проекти каде што "%s" е менаџер', + 'Projects where "%s" is member' => 'Проекти каде што "%s" е член', + 'Open tasks assigned to "%s"' => 'Отворени задачи доделени "%s"', + 'Closed tasks assigned to "%s"' => 'Затворените задачи доделени "%s"', + 'Assign automatically a color based on a priority' => 'Доделете ја бојата автоматски врз основа на приоритетот', + 'Overdue tasks for the project(s) "%s"' => 'Задачи доцна за проект(и) "%s"', + 'Upload files' => 'Поставете датотеки', + 'Installed Plugins' => 'Инсталирани додатоци', + 'Plugin Directory' => 'Директориум за додатоци', + 'Plugin installed successfully.' => 'Додатокот е успешно инсталиран', + 'Plugin updated successfully.' => 'Додатокот е успешно ажуриран.', + 'Plugin removed successfully.' => 'Додатокот е успешно отстранет.', + 'Subtask converted to task successfully.' => 'Подзадача успешно се претвори во задача.', + 'Unable to convert the subtask.' => 'Не може да се конвертира подзадача.', + 'Unable to extract plugin archive.' => 'Не може да се распакува архивата на додатокот.', + 'Plugin not found.' => 'Додатокот не е пронајден.', + 'You don\'t have the permission to remove this plugin.' => 'Немате дозвола да го отстраните овој додатокот.', + 'Unable to download plugin archive.' => 'Не може да се преземе архивата на додатокот.', + 'Unable to write temporary file for plugin.' => 'Не може да се напише привремена датотека со додатокот.', + 'Unable to open plugin archive.' => 'Не може да се отвори архивата на додатокот.', + 'There is no file in the plugin archive.' => 'Нема датотека во архивата на додатокот.', + 'Create tasks in bulk' => 'Масовно создавање задачи', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Овој Канборд не е конфигуриран да поддржува инсталирање додатоци преку корисничкото опкружување.', + 'There is no plugin available.' => 'Нема достапни додатоци.', + 'Install' => 'Инсталирајте', + 'Update' => 'Ажурирање', + 'Up to date' => 'Во тек', + 'Not available' => 'Не е достапно', + 'Remove plugin' => 'Отстранете го додатокот', + 'Do you really want to remove this plugin: "%s"?' => 'Дали сте сигурни дека сакате да го отстраните овој додатокот: "%s"?', + 'Uninstall' => 'Деинсталирај', + 'Listing' => 'Листата', + 'Metadata' => 'Метаподатоци', + 'Manage projects' => 'Управување со проекти', + 'Convert to task' => 'Претворете го во задача', + 'Convert sub-task to task' => 'Претворете ја подзадачата во задача', + 'Do you really want to convert this sub-task to a task?' => 'Дали навистина сакате подзадача да ја претворите во задача?', + 'My task title' => 'Наслов на задача', + 'Enter one task by line.' => 'Внесете една задача по линија.', + 'Number of failed login:' => 'Број на неуспешни апликации:', + 'Account locked until:' => 'Профилот е заклучен до:', + 'Email settings' => 'Поставки за е-пошта', + 'Email sender address' => 'Адреса за е-пошта на испраќачот', + 'Email transport' => 'Транспорт по е-пошта', + 'Webhook token' => 'Токен за Webhook', + 'Project tags management' => 'Управување со ознаки на проектот', + 'Tag created successfully.' => 'Ознаката е успешно создадена.', + 'Unable to create this tag.' => 'Не може да се создаде ознака.', + 'Tag updated successfully.' => 'Ознаката е успешно ажурирана.', + 'Unable to update this tag.' => 'Не може да се ажурира оваа ознака.', + 'Tag removed successfully.' => 'Ознаката е успешно отстранета.', + 'Unable to remove this tag.' => 'Не може да се отстрани оваа ознака.', + 'Global tags management' => 'Глобално управување со ознаки.', + 'Tags' => 'Ознаки', + 'Tags management' => 'Управување со ознаки', + 'Add new tag' => 'Додадете нова ознака', + 'Edit a tag' => 'Уредете ја ознаката', + 'Project tags' => 'Ознаки на проектот', + 'There is no specific tag for this project at the moment.' => 'Во моментов нема ознаки за овој проект.', + 'Tag' => 'Ознакa', + 'Remove a tag' => 'Отстранете ја oзнакa', + 'Do you really want to remove this tag: "%s"?' => 'Дали сте сигурни дека сакате да ја отстраните оваа ознака: "%s"?', + 'Global tags' => 'Глобални ознаки', + 'There is no global tag at the moment.' => 'Во моментов нема глобални ознаки.', + 'This field cannot be empty' => 'Ова поле не може да биде празно', + 'Close a task when there is no activity in a specific column' => 'Затворете ја задачата кога нема активност во избраната колона', + '%s removed a subtask for the task #%d' => '%s ја отстрани подзадачата за задача #%d', + '%s removed a comment on the task #%d' => '%s ја отстрани коментар за задачата #%d', + 'Comment removed on task #%d' => 'Коментарот е отстранет за задачата #%d', + 'Subtask removed on task #%d' => 'Подзадачата е отстранета на задачата #%d', + 'Hide tasks in this column in the dashboard' => 'Скријте ги задачите во оваа колона на контролната табла', + '%s removed a comment on the task %s' => '%s го отстрани коментарот за задача %s', + '%s removed a subtask for the task %s' => '%s го отстрани подзадачата за задача %s', + 'Comment removed' => 'Коментарот е отстранет', + 'Subtask removed' => 'Подзадачата е отстранета', + '%s set a new internal link for the task #%d' => '%s ја постави нова внатрешна врска за задачата #%d', + '%s removed an internal link for the task #%d' => '%s ја отстрани внатрешната врска за задачата #%d', + 'A new internal link for the task #%d has been defined' => 'Дефинирана е нова внатрешна врска за задачата #%d', + 'Internal link removed for the task #%d' => 'Внатрешната врска е отстранета за задачата #%d', + '%s set a new internal link for the task %s' => '%s додаде нова внатрешна врска за задача %s', + '%s removed an internal link for the task %s' => '%s ја отстрани внатрешната врска за задача %s', + 'Automatically set the due date on task creation' => 'Автоматски додајте рок за завршување на креираната задача', + 'Move the task to another column when closed' => 'Поместете ја задачата во втората колона кога е затворена', + 'Move the task to another column when not moved during a given period' => 'Поместете ја задачата во друга колона кога задачата не се поместила во дадениот период', + 'Dashboard for %s' => 'Контролен панел за %s', + 'Tasks overview for %s' => 'Преглед на задача за %s', + 'Subtasks overview for %s' => 'Погледнете ги подзадачите за %s', + 'Projects overview for %s' => 'Преглед на проектот за %s', + 'Activity stream for %s' => 'Проток на активност за %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Доделете боја кога задачата е преместена на одредена патека', + 'Assign a priority when the task is moved to a specific swimlane' => 'Доделете приоритет кога задачата е преместена на одредена патека', + 'User unlocked successfully.' => 'Корисникот е успешно отклучен.', + 'Unable to unlock the user.' => 'Не може да се отклучи корисникот.', + 'Move a task to another swimlane' => 'Поместете задача на друга патека', + 'Creator Name' => 'Име на креаторот', + 'Time spent and estimated' => 'Времето поминато и проценето', + 'Move position' => 'Место на движење', + 'Move task to another position on the board' => 'Поместете ја задачата на друго место на таблата', + 'Insert before this task' => 'Вметнете пред оваа задача', + 'Insert after this task' => 'Вметнете по оваа задача', + 'Unlock this user' => 'Отклучете го овој корисник', + 'Custom Project Roles' => 'Прилагодена улоги на проекти', + 'Add a new custom role' => 'Додадете нова прилагодена улога', + 'Restrictions for the role "%s"' => 'Ограничувања за улоги "%s"', + 'Add a new project restriction' => 'Додадете нови ограничувања на проектот', + 'Add a new drag and drop restriction' => 'Додадете нови ограничувања за „влечење и испуштање“', + 'Add a new column restriction' => 'Додадете нови ограничувања на колоните', + 'Edit this role' => 'Уредете ја оваа улога', + 'Remove this role' => 'Отстранете ја оваа улога', + 'There is no restriction for this role.' => 'Нема ограничувања за оваа улога.', + 'Only moving task between those columns is permitted' => 'Дозволено е само движење помеѓу овие колони', + 'Close a task in a specific column when not moved during a given period' => 'Затворете ја задачата во избраната колона кога нема скролување за наведениот период', + 'Edit columns' => 'Уредете ги колоните', + 'The column restriction has been created successfully.' => 'Ограничувањата во колоните се успешно создадени.', + 'Unable to create this column restriction.' => 'Не може да се создадат ограничувања на колоните.', + 'Column restriction removed successfully.' => 'Ограничувањата на колоните беа успешно отстранети.', + 'Unable to remove this restriction.' => 'Ова ограничување не може да се отстрани.', + 'Your custom project role has been created successfully.' => 'Вашата прилагодена улога на проектот е успешно креирана.', + 'Unable to create custom project role.' => 'Не е можно да се создаде прилагодена улога на проектот.', + 'Your custom project role has been updated successfully.' => 'Вашата прилагодена улога на проектот е успешно ажурирана.', + 'Unable to update custom project role.' => 'Не може да се ажурира улогата на сопствениот проект.', + 'Custom project role removed successfully.' => 'Улогата на прилагодениот проект е успешно отстранета.', + 'Unable to remove this project role.' => 'Не е можно да се отстрани оваа улога на проектот.', + 'The project restriction has been created successfully.' => 'Ограничувањето на проектот е успешно креирано.', + 'Unable to create this project restriction.' => 'Не е можно да се создаде ова ограничување на проектот.', + 'Project restriction removed successfully.' => 'Ограничувањето на проектот е успешно отстрането.', + 'You cannot create tasks in this column.' => 'Не можете да креирате задача во оваа колона.', + 'Task creation is permitted for this column' => 'Создавање задача во оваа колона е забрането', + 'Closing or opening a task is permitted for this column' => 'Затворање или отворање задача во оваа колона е забрането', + 'Task creation is blocked for this column' => 'Создавањето задачи е оневозможено за оваа колона', + 'Closing or opening a task is blocked for this column' => 'Затворањето или отворањето на задача во оваа колона е оневозможено', + 'Task creation is not permitted' => 'Креирање задача не е дозволено', + 'Closing or opening a task is not permitted' => 'Затворање или отворање задача не е дозволено', + 'New drag and drop restriction for the role "%s"' => 'Ново ограничување „влечи и пушти“ за улогата "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Луѓето кои ја имаат доделена оваа улога ќе можат да преместуваат задачи само во колоните извори и дестинации.', + 'Remove a column restriction' => 'Отстранете го ограничувањето на колоната', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Дали сте сигурни дека сакате да го отстраните ограничувањето на оваа колона: "%s" во "%s"?', + 'New column restriction for the role "%s"' => 'Ново ограничување на колоната за улогата "%s"', + 'Rule' => 'Правило', + 'Do you really want to remove this column restriction?' => 'Дали навистина сакате да го отстраните ова ограничување на колоната?', + 'Custom roles' => 'Прилагодени улоги', + 'New custom project role' => 'Нова прилагодена улога за проектот', + 'Edit custom project role' => 'Уредете прилагодена улога на проект', + 'Remove a custom role' => 'Отстранете ја прилагодената улога', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Дали сте сигурни дека сакате да ја отстраните оваа прилагодена улога: "%s"? Сите лица кои се доделени на оваа улога ќе станат членови на проектот.', + 'There is no custom role for this project.' => 'Нема прилагодени улоги за овој проект.', + 'New project restriction for the role "%s"' => 'Ново ограничување на проектот за улогата "%s"', + 'Restriction' => 'Ограничување', + 'Remove a project restriction' => 'Отстранете го ограничувањето за проектот', + 'Do you really want to remove this project restriction: "%s"?' => 'Дали навистина сакате да го отстраните ова ограничување на проектот: "%s"?', + 'Duplicate to multiple projects' => 'Дупликат во повеќе проекти', + 'This field is required' => 'Ова поле е задолжително', + 'Moving a task is not permitted' => 'Преместувањето на задачата не е дозволено', + 'This value must be in the range %d to %d' => 'Оваа вредност мора да се движи од %d до %d', + 'You are not allowed to move this task.' => 'Немате дозвола да ја преместите оваа задача.', + 'API User Access' => 'API Кориснички пристап', + 'Preview' => 'Преглед', + 'Write' => 'Напиши', + 'Write your text in Markdown' => 'Внесете текст во Маркдаун', + 'No personal API access token registered.' => 'Не е регистриран личен токен за пристап до API', + 'Your personal API access token is "%s"' => 'Вашиот личен API-токен за пристап е: "%s"', + 'Remove your token' => 'Отстранете го вашиот токен', + 'Generate a new token' => 'Генерирајте нов токен', + 'Showing %d-%d of %d' => 'Прикажани %d - %d од %d', + 'Outgoing Emails' => 'Е-пошта што излегуваат', + 'Add or change currency rate' => 'Додадете или променете го девизниот курс', + 'Reference currency: %s' => 'Референтна валута: %s', + 'Add custom filters' => 'Додадете сопствени филтри', + 'Export' => 'Извоз', + 'Add link label' => 'Додадете етикета на врската', + 'Incompatible Plugins' => 'Некомпатибилни додатоци', + 'Compatibility' => 'Компатибилност', + 'Permissions and ownership' => 'Дозволи и сопственост', + 'Priorities' => 'Приоритети', + 'Close this window' => 'Затворете го овој прозорец', + 'Unable to upload this file.' => 'Не може да се вчита оваа датотека.', + 'Import tasks' => 'Задачи за увоз', + 'Choose a project' => 'Изберете проект', + 'Profile' => 'Профил', + 'Application role' => 'Улога во апликацијата', + '%d invitations were sent.' => '%d е испратена покана.', + '%d invitation was sent.' => '%d е испратена покана.', + 'Unable to create this user.' => 'Не може да се создаде овој корисник.', + 'Kanboard Invitation' => 'Покана за Канборд', + 'Visible on dashboard' => 'Видливо на контролната табла', + 'Created at:' => 'Создадено на:', + 'Updated at:' => 'Ажурирано:', + 'There is no custom filter.' => 'Нема сопствени филтри.', + 'New User' => 'Нов корисник', + 'Authentication' => 'Автентикација', + 'If checked, this user will use a third-party system for authentication.' => 'Ако е означено, овој корисник ќе користи независен систем за проверка.', + 'The password is necessary only for local users.' => 'Лозинката е потребна само за локалните корисници.', + 'You have been invited to register on Kanboard.' => 'Вие сте поканети да се регистрирате на Канборд.', + 'Click here to join your team' => 'Кликнете тука за да се придружите на вашиот тим', + 'Invite people' => 'Покани луѓе', + 'Emails' => 'Е-пошта', + 'Enter one email address by line.' => 'Внесете една адреса за е-пошта по ред.', + 'Add these people to this project' => 'Додадете ги овие луѓе во овој проект', + 'Add this person to this project' => 'Додадете ја оваа личност на овој проект', + 'Sign-up' => 'Регистрација', + 'Credentials' => 'Параметри за пристап', + 'New user' => 'Нов корисник', + 'This username is already taken' => 'Ова корисничко име е зафатено', + 'Your profile must have a valid email address.' => 'Профилот мора да има важечка адреса за е-пошта.', + 'TRL - Turkish Lira' => 'TRL - турска лира', + 'The project email is optional and could be used by several plugins.' => 'Проектот за е-пошта е опционален и може да се користи од неколку додатоци.', + 'The project email must be unique across all projects' => 'Е-поштата на проектот мора да биде единствена за секој проект', + 'The email configuration has been disabled by the administrator.' => 'Уредувањето на е-пошта е оневозможено од администраторот.', + 'Close this project' => 'Затворете го овој проект', + 'Open this project' => 'Отворете го овој проект', + 'Close a project' => 'Затворете го проектот', + 'Do you really want to close this project: "%s"?' => 'Дали навистина сакате да го затворите проектот: "%s"?', + 'Reopen a project' => 'Повторно отворете го проектот', + 'Do you really want to reopen this project: "%s"?' => 'Дали навистина сакате повторно да го отворите проектот: "%s"?', + 'This project is open' => 'Овој проект е отворен', + 'This project is closed' => 'Овој проект е затворен', + 'Unable to upload files, check the permissions of your data folder.' => 'Не може да се постават датотеки, проверете ги дозволите за директориум на серверот.', + 'Another category with the same name exists in this project' => 'Категорија со ова име веќе постои на овој проект', + 'Comment sent by email successfully.' => 'Коментарот е успешно испратен по е-пошта.', + 'Sent by email to "%s" (%s)' => 'Послато е-поштом на "%s" (%s)', + 'Unable to read uploaded file.' => 'Не можам да ја читам поставената датотека.', + 'Database uploaded successfully.' => 'Базата на податоци е успешно поставена.', + 'Task sent by email successfully.' => 'Задачата е успешно испратена по е-пошта.', + 'There is no category in this project.' => 'Нема категории во овој проект.', + 'Send by email' => 'Испратете по е-пошта', + 'Create and send a comment by email' => 'Направете и испратете коментар по е-пошта', + 'Subject' => 'Предмет', + 'Upload the database' => 'Поставете база на податоци', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Може да испратите претходно преземена база на податоци Sqlite (формат Gzip).', + 'Database file' => 'Датотека со базата на податоци', + 'Upload' => 'Поставете', + 'Your project must have at least one active swimlane.' => 'Вашиот проект мора да има барем една активна патека.', + 'Project: %s' => 'Проект: %s', + 'Automatic action not found: "%s"' => 'Автоматското дејство не е пронајдено: "%s"', + '%d projects' => '%d проекти', + '%d project' => '%d проект', + 'There is no project.' => 'Нема проект.', + 'Sort' => 'Сортирај', + 'Project ID' => 'ИД на проект', + 'Project name' => 'Име на проектот', + 'Public' => 'Јавно', + 'Personal' => 'Лично', + '%d tasks' => '%d задачи', + '%d task' => '%d задача', + 'Task ID' => 'ИД на задача', + 'Assign automatically a color when due date is expired' => 'Автоматски ја поставува бојата кога истече рок за завршување', + 'Total score in this column across all swimlanes' => 'Вкупниот резултат во оваа колона за сите патеки', + 'HRK - Kuna' => 'HRK - хрватска куна', + 'ARS - Argentine Peso' => 'ARS- аргентински пезос', + 'COP - Colombian Peso' => 'COP - колумбиски пезос', + '%d groups' => '%d групи', + '%d group' => '%d група', + 'Group ID' => 'ID на групата', + 'External ID' => 'Надворешна ИД', + '%d users' => '%d корисници', + '%d user' => '%d корисник', + 'Hide subtasks' => 'Сокриј ги подзадачите', + 'Show subtasks' => 'Прикажи ги подзадачите', + 'Authentication Parameters' => 'Параметри за автентикација', + 'API Access' => 'АPI пристап', + 'No users found.' => 'Не се пронајдени корисници.', + 'User ID' => 'Кориснички проект', + 'Notifications are activated' => 'Известувањата се овозможени', + 'Notifications are disabled' => 'Известувањата се оневозможени', + 'User disabled' => 'Корисникот е оневозможен', + '%d notifications' => '%d известувањe(a)', + '%d notification' => '%d известување', + 'There is no external integration installed.' => 'Не е инсталирана надворешна интеграција.', + 'You are not allowed to update tasks assigned to someone else.' => 'Не ви е дозволено да ги ажурирате задачите доделени на некој друг.', + 'You are not allowed to change the assignee.' => 'Не смеете да го менувате извршителот.', + 'Task suppression is not permitted' => 'Сузбивање на задачите не е дозволено', + 'Changing assignee is not permitted' => 'Промена на извршителот не е дозволена', + 'Update only assigned tasks is permitted' => 'Само доделените задачи имаат дозвола да се ажурираат', + 'Only for tasks assigned to the current user' => 'Само за задачите доделени на тековниот корисник', + 'My projects' => 'Мои проекти', + 'You are not a member of any project.' => 'Вие не сте член на ниеден проект.', + 'My subtasks' => 'Мои подзадачи', + '%d subtasks' => '%d подзадачи', + '%d subtask' => '%d подзадача', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Дозволено е само поместување на задача помеѓу овие колони за задачите доделени на тековниот корисник', + '[DUPLICATE]' => '[ДУПЛИКАТ]', + 'DKK - Danish Krona' => 'DKK- данска круна', + 'Remove user from group' => 'Отстранете го корисникот од групата', + 'Assign the task to its creator' => 'Доделете задача на нејзиниот творец', + 'This task was sent by email to "%s" with subject "%s".' => 'Оваа задача беше испратена по е-пошта до "%s" со предмет "%s".', + 'Predefined Email Subjects' => 'Преддефинирани ставки по е-пошта', + 'Write one subject by line.' => 'Внесете една ставка по линија', + 'Create another link' => 'Создадете друга врска', + 'BRL - Brazilian Real' => 'BRL - бразилски реал', + 'Add a new Kanboard task' => 'Додадете нова задача на Канборд', + 'Subtask not started' => 'Подзадача не започна', + 'Subtask currently in progress' => 'Подзадачата е во тек', + 'Subtask completed' => 'Потзадачата е завршена', + 'Subtask added successfully.' => 'Подзадача успешно додаде.', + '%d subtasks added successfully.' => 'Успешно додадени подзадачи %d.', + 'Enter one subtask by line.' => 'Внесете една подзадача по линија.', + 'Predefined Contents' => 'Преддефинирана содржина', + 'Predefined contents' => 'Преддефинирана содржина', + 'Predefined Task Description' => 'Преддефиниран опис на задачата', + 'Do you really want to remove this template? "%s"' => 'Дали сте сигурни дека сакате да го отстраните овој образец? "%s"', + 'Add predefined task description' => 'Додаден е предефиниран опис на задачата', + 'Predefined Task Descriptions' => 'Преддефиниран опис на задачата', + 'Template created successfully.' => 'Шаблонот е успешно создаден', + 'Unable to create this template.' => 'Не може да се создаде овој шаблон', + 'Template updated successfully.' => 'Шаблонот е успешно ажуриран', + 'Unable to update this template.' => 'Не може да се ажурира овој образец', + 'Template removed successfully.' => 'Шаблонот е успешно отстранет', + 'Unable to remove this template.' => 'Не може да се отстрани овој образец', + 'Template for the task description' => 'Шаблон за опис на задача', + 'The start date is greater than the end date' => 'Датумот на почеток е поголем од датумот на крај', + 'Tags must be separated by a comma' => 'Ознаките мора да се одделат со запирки', + 'Only the task title is required' => 'Потребен е само наслов на задачата', + 'Creator Username' => 'Корисничко име на креаторот', + 'Color Name' => 'Име на боја', + 'Column Name' => 'Име на колона', + 'Swimlane Name' => 'Име на патеката', + 'Time Estimated' => 'Проценето време', + 'Time Spent' => 'Потрошено време', + 'External Link' => 'Надворешна врска', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Оваа функционалност овозможува приказ на iCal фид, RSS фид и јавна табла.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Запрете го тајмерот на сите подзадачи кога ја преместувате задачата во друга колона', + 'Subtask Title' => 'Наслов на подзадача', + 'Add a subtask and activate the timer when moving a task to another column' => 'Додадете подзадача и активирајте тајмер кога ќе ја преместите задачата во друга колона', + 'days' => 'денови', + 'minutes' => 'минути', + 'seconds' => 'секунди', + 'Assign automatically a color when preset start date is reached' => 'Автоматски ја поставува бојата кога ќе се достигне претходно поставениот датум за почеток', + 'Move the task to another column once a predefined start date is reached' => 'Поместете ја задачата во втората колона кога ќе се достигне предефинираниот датум за почеток', + 'This task is now linked to the task %s with the relation "%s"' => 'Задачата сега е поврзана со задачата %s со релација "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'Врската со релацијата "%s" со задачата %s е отстранета', + 'Custom Filter:' => 'Прилагодено филтер', + 'Unable to find this group.' => 'Не може да се најде оваа група.', + '%s moved the task #%d to the column "%s"' => '%s ја премести задачата #%d во колоната "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => '%s ја премести задачата #%d во позиција %d во колоната "%s"', + '%s moved the task #%d to the swimlane "%s"' => '%s ја премести задачата #%d на патеката "%s"', + '%sh spent' => '%sч потрошени', + '%sh estimated' => '%sч проценето', + 'Select All' => 'Изберете сè', + 'Unselect All' => 'Врати сè', + 'Apply action' => 'Примени акција', + 'Move selected tasks to another column or swimlane' => 'Преместете ја избраната задача во друга колона или патека', + 'Edit tasks in bulk' => 'Масовно уредување на задачите', + 'Choose the properties that you would like to change for the selected tasks.' => 'Изберете ги својствата што сакате да ги промените за избраните задачи.', + 'Configure this project' => 'Конфигурирајте го овој проект', + 'Start now' => 'Почни сега', + '%s removed a file from the task #%d' => '%s ја отстрани датотеката од задачата #%d', + 'Attachment removed from task #%d: %s' => 'Приклучокот е отстранет од задачата #%d: %s', + 'No color' => 'Без боја', + 'Attachment removed "%s"' => 'Прилогот е отстранет "%s"', + '%s removed a file from the task %s' => '%s ја отстрани датотека од задача %s', + 'Move the task to another swimlane when assigned to a user' => 'Поместете ја задачата на друга патека кога задачата е доделена на корисникот', + 'Destination swimlane' => 'Патека за дестинација', + 'Assign a category when the task is moved to a specific swimlane' => 'Доделете категорија кога задачата е преместена на одредена патека', + 'Move the task to another swimlane when the category is changed' => 'Поместете ја задачата на друга патека кога категоријата се менува', + 'Reorder this column by priority (ASC)' => 'Променете го редоследот на оваа колона според приоритет (РАСТЕЧКИ)', + 'Reorder this column by priority (DESC)' => 'Променете го редоследот на оваа колона по приоритет (ОПАЃАЧКИ)', + 'Reorder this column by assignee and priority (ASC)' => 'Променете го редоследот на оваа колона според извршителот (РАСТЕЧКИ)', + 'Reorder this column by assignee and priority (DESC)' => 'Променете го редоследот на оваа колона според извршителот (ОПАЃАЧКИ)', + 'Reorder this column by assignee (A-Z)' => 'Променете го редоследот на оваа колона според извршителот (А-Ш)', + 'Reorder this column by assignee (Z-A)' => 'Променете го редоследот на оваа колона од извршител (Ш-A)', + 'Reorder this column by due date (ASC)' => 'Променете го редоследот на оваа колона според рок за завршување (РАСТЕЧКИ)', + 'Reorder this column by due date (DESC)' => 'Променете го редоследот на оваа колона според рок за завршување (ОПАЃАЧКИ)', + 'Reorder this column by id (ASC)' => 'Променете го редоследот на оваа колона според ИД (РАСТЕЧКИ)', + 'Reorder this column by id (DESC)' => 'Променете го редоследот на оваа колона според ИД (ОПАЃАЧКИ)', + '%s moved the task #%d "%s" to the project "%s"' => '%s ја премести задачата #%d "%s" за да проектира "%s"', + 'Task #%d "%s" has been moved to the project "%s"' => 'Задачата #%d "%s" е преместена во проектот "%s"', + 'Move the task to another column when the due date is less than a certain number of days' => 'Поместете ја задачата во друга колона кога рок за завршување е пократок од одреден број денови', + 'Automatically update the start date when the task is moved away from a specific column' => 'Автоматски ажурирајте го датумот на почеток кога задачата е отстранета од одредена колона', + 'HTTP Client:' => 'Клиент за HTTP', + 'Assigned' => 'Доделени', + 'Task limits apply to each swimlane individually' => 'Границата за бројот на задачите се однесува на секоја патеката одделно', + 'Column task limits apply to each swimlane individually' => 'Границата за бројот на задачи во колоната се однесува на секоја патеката одделно', + 'Column task limits are applied to each swimlane individually' => 'Границата за бројот на задачи во колоната се однесува на секоја патеката одделно', + 'Column task limits are applied across swimlanes' => 'Границата за бројот на задачи во колоната се однесува на повеќе патеки', + 'Task limit: ' => 'Границата за бројот на задача:', + 'Change to global tag' => 'Промена на глобална ознака', + 'Do you really want to make the tag "%s" global?' => 'Дали навистина сакате ознаката "%s" да ја направите глобална?', + 'Enable global tags for this project' => 'Овозможете глобални ознаки за овој проект', + 'Group membership(s):' => 'Членство во група:', + '%s is a member of the following group(s): %s' => '%s е член на група(и): %s', + '%d/%d group(s) shown' => '%d/%d прикажана група(и)', + 'Subtask creation or modification' => 'Создавање или модифицирање на подзадача', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Доделете ја задачата на одреден корисник кога задачата е преместена на одредена патека', + 'Comment' => 'Коментар', + 'Collapse vertically' => 'Собери вертикално', + 'Expand vertically' => 'Проширете вертикално', + 'MXN - Mexican Peso' => 'MXN - Мексикански пезос', + 'Estimated vs actual time per column' => 'Проценето наспроти реалното време по колона', + 'HUF - Hungarian Forint' => 'HUF - Унгарска форинта', + 'XBT - Bitcoin' => 'XBT - биткоин', + 'You must select a file to upload as your avatar!' => 'Мора да изберете датотека што ќе ја поставите како ваш аватар!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'Датотеката што ја поставивте не е валидна слика! (Дозволени се само *.gif, *.jpg, *.jpeg и *.png!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Автоматски поставете го рок за завршување кога задачата ќе се оддалечи од одредена колона', + 'No other projects found.' => 'Не се пронајдени други проекти.', + 'Tasks copied successfully.' => 'Задачите се успешно копирани.', + 'Unable to copy tasks.' => 'Не може да се копираат задачите.', + 'Theme' => 'Тема', + 'Theme:' => 'Тема:', + 'Light theme' => 'Светла тема', + 'Dark theme' => 'Темна тема', + 'Automatic theme - Sync with system' => 'Автоматска тема - Синхронизирај со системот', + 'Application managers or more' => 'Менаџери на апликации или повеќе', + 'Administrators' => 'Администратори', + 'Visibility:' => 'Видливост:', + 'Standard users' => 'Стандардни корисници', + 'Visibility is required' => 'Видливоста е задолжителна', + 'The visibility should be an app role' => 'Видливоста треба да биде улога на апликацијата', + 'Reply' => 'Одговори', + '%s wrote: ' => '%s напиша: ', + 'Number of visible tasks in this column and swimlane' => 'Број на видливи задачи во оваа колона и патека', + 'Number of tasks in this swimlane' => 'Број на задачи во оваа патека', + 'Unable to find another subtask in progress, you can close this window.' => 'Не можам да најдам друг подзадача во тек, можете да го затворите овој прозорец.', + 'This theme is invalid' => 'Оваа тема е неважечка', + 'This role is invalid' => 'Оваа улога е неважечка', + 'This timezone is invalid' => 'Оваа временска зона е неважечка', + 'This language is invalid' => 'Овој јазик е неважечки', + 'This URL is invalid' => 'Оваа URL адреса е неважечка', + 'Date format invalid' => 'Неважечки формат на датум', + 'Time format invalid' => 'Неважечки формат на време', + 'Invalid Mail transport' => 'Неважечки транспорт на пошта', + 'Color invalid' => 'Неважечка боја', + 'This value must be greater or equal to %d' => 'Оваа вредност мора да биде поголема или еднаква на %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Додадете BOM на почетокот на датотеката (задолжително за Microsoft Excel)', + 'Just add these tag(s)' => 'Само додадете ги овие ознаки', + 'Remove internal link(s)' => 'Отстрани внатрешна врска(и)', + 'Import tasks from another project' => 'Увези задачи од друг проект', + 'Select the project to copy tasks from' => 'Изберете го проектот од кој сакате да копирате задачи', + 'The total maximum allowed attachments size is %sB.' => 'Вкупната максимална дозволена големина на прилози е %sB.', + 'Add attachments' => 'Додади прилози', + 'Task #%d "%s" is overdue' => 'Задачата #%d "%s" е задоцнета', + 'Enable notifications by default for all new users' => 'Овозможете известувања по подразбирање за сите нови корисници', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Додели ја задачата на нејзиниот креатор за одредени колони ако не е рачно поставен извршител', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Додели ја задачата на најавениот корисник при промена на колоната во зададената колона ако нема доделен корисник', +]; diff --git a/app/Locale/my_MY/translations.php b/app/Locale/my_MY/translations.php new file mode 100644 index 0000000..8e3f593 --- /dev/null +++ b/app/Locale/my_MY/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => '.', + 'number.thousands_separator' => ',', + 'None' => 'Tiada', + 'Edit' => 'Sunting', + 'Remove' => 'Hapus', + 'Yes' => 'Ya', + 'No' => 'Tidak', + 'cancel' => 'batal', + 'or' => 'atau', + 'Yellow' => 'Kuning', + 'Blue' => 'Biru', + 'Green' => 'Hijau', + 'Purple' => 'Ungu', + 'Red' => 'Merah', + 'Orange' => 'Oren', + 'Grey' => 'Kelabu', + 'Brown' => 'Coklat', + 'Deep Orange' => 'Oren Gelap', + 'Dark Grey' => 'Kelabu Malap', + 'Pink' => 'Merah Jambu', + 'Teal' => 'Teal', + 'Cyan' => 'Sian', + 'Lime' => 'Lime', + 'Light Green' => 'Hijau Muda', + 'Amber' => 'Amber', + 'Save' => 'Simpan', + 'Login' => 'Masuk', + 'Official website:' => 'Laman rasmi :', + 'Unassigned' => 'Belum ditugaskan', + 'View this task' => 'Lihat tugas ini', + 'Remove user' => 'Hapus pengguna', + 'Do you really want to remove this user: "%s"?' => 'Anda yakin mahu menghapus pengguna ini : « %s » ?', + 'All users' => 'Semua pengguna', + 'Username' => 'Nama pengguna', + 'Password' => 'Kata laluan', + 'Administrator' => 'Pentadbir', + 'Sign in' => 'Masuk', + 'Users' => 'Para Pengguna', + 'Forbidden' => 'Larangan', + 'Access Forbidden' => 'Akses Dilarang', + 'Edit user' => 'Ubah Pengguna', + 'Logout' => 'Keluar', + 'Bad username or password' => 'Nama pengguna atau kata laluan tidak sepadan', + 'Edit project' => 'Ubah projek', + 'Name' => 'Nama', + 'Projects' => 'Projek', + 'No project' => 'Tiada projek', + 'Project' => 'Projek', + 'Status' => 'Status', + 'Tasks' => 'Tugasan', + 'Board' => 'Papan', + 'Actions' => 'Tindakan', + 'Inactive' => 'Tidak Aktif', + 'Active' => 'Aktif', + 'Unable to update this board.' => 'Tidak berupaya mengemaskini papan ini', + 'Disable' => 'Nyah-Upaya', + 'Enable' => 'Aktifkan', + 'New project' => 'Projek Baru', + 'Do you really want to remove this project: "%s"?' => 'Anda yakin mahu menghapus projek ini : « %s » ?', + 'Remove project' => 'Hapus projek', + 'Edit the board for "%s"' => 'Ubah papan untuk « %s »', + 'Add a new column' => 'Tambah kolom baru', + 'Title' => 'Judul', + 'Assigned to %s' => 'Ditugaskan ke %s', + 'Remove a column' => 'Hapus kolom', + 'Unable to remove this column.' => 'Tidak dapat menghapus kolom ini.', + 'Do you really want to remove this column: "%s"?' => 'Apakah anda yakin akan menghapus kolom ini : « %s » ?', + 'Settings' => 'Penetapan', + 'Application settings' => 'Penetapan aplikasi', + 'Language' => 'Bahasa', + 'Webhook token:' => 'Token webhook :', + 'API token:' => 'Token API :', + 'Database size:' => 'Saiz pengkalan data:', + 'Download the database' => 'Muat turun pengkalan data', + 'Optimize the database' => 'Optimakan pengkalan data', + '(VACUUM command)' => '(perintah VACUUM)', + '(Gzip compressed Sqlite file)' => '(File Sqlite yang termampat Gzip)', + 'Close a task' => 'Tutup tugas', + 'Column' => 'Kolom', + 'Color' => 'Warna', + 'Assignee' => 'Orang yang ditugaskan', + 'Create another task' => 'Buat tugas lain', + 'New task' => 'Tugasan baru', + 'Open a task' => 'Buka tugas', + 'Do you really want to open this task: "%s"?' => 'Anda yakin untuk buka tugas ini : « %s » ?', + 'Back to the board' => 'Kembali ke papan', + 'There is nobody assigned' => 'Tidak ada orang yand ditugaskan', + 'Column on the board:' => 'Kolom di dalam papan : ', + 'Close this task' => 'Tutup tugas ini', + 'Open this task' => 'Buka tugas ini', + 'There is no description.' => 'Tidak ada keterangan.', + 'Add a new task' => 'Tambah tugas baru', + 'The username is required' => 'Nama pengguna adalah wajib', + 'The maximum length is %d characters' => 'Panjang maksimum adalah %d karakter', + 'The minimum length is %d characters' => 'Panjang minimum adalah %d karakter', + 'The password is required' => 'Kata laluan adalah wajib', + 'This value must be an integer' => 'Nilai ini harus integer', + 'The username must be unique' => 'Nama pengguna semestinya unik', + 'The user id is required' => 'Id Pengguna adalah wajib', + 'Passwords don\'t match' => 'Kata laluan tidak sepadan', + 'The confirmation is required' => 'Pengesahan diperlukan', + 'The project is required' => 'Projek diperlukan', + 'The id is required' => 'Id diperlukan', + 'The project id is required' => 'Id projek diperlukan', + 'The project name is required' => 'Nama projek diperlukan', + 'The title is required' => 'Judul diperlukan', + 'Settings saved successfully.' => 'Penetapan berjaya disimpan.', + 'Unable to save your settings.' => 'Tidak dapat menyimpan penetapan anda.', + 'Database optimization done.' => 'Optimasi pengkalan data selesai.', + 'Your project has been created successfully.' => 'Projek anda berhasil dibuat.', + 'Unable to create your project.' => 'Tidak dapat membuat projek anda.', + 'Project updated successfully.' => 'projek berhasil diperbaharui.', + 'Unable to update this project.' => 'Tidak dapat memperbaharui projek ini.', + 'Unable to remove this project.' => 'Tidak dapat menghapus projek ini.', + 'Project removed successfully.' => 'projek berhasil dihapus.', + 'Project activated successfully.' => 'projek berhasil diaktivasi.', + 'Unable to activate this project.' => 'Tidak dapat mengaktifkan projek ini.', + 'Project disabled successfully.' => 'projek berhasil dinonaktifkan.', + 'Unable to disable this project.' => 'Tidak dapat menonaktifkan projek ini.', + 'Unable to open this task.' => 'Tidak dapat membuka tugas ini.', + 'Task opened successfully.' => 'Tugas berhasil dibuka.', + 'Unable to close this task.' => 'Tidak dapat menutup tugas ini.', + 'Task closed successfully.' => 'Tugas berhasil ditutup.', + 'Unable to update your task.' => 'Tidak dapat memperbaharui tugas ini.', + 'Task updated successfully.' => 'Tugas berhasil diperbaharui.', + 'Unable to create your task.' => 'Tidak dapat membuat tugas anda.', + 'Task created successfully.' => 'Tugas berhasil dibuat.', + 'User created successfully.' => 'Pengguna berhasil dibuat.', + 'Unable to create your user.' => 'Tidak dapat membuat pengguna anda.', + 'User updated successfully.' => 'Pengguna berhasil diperbaharui.', + 'User removed successfully.' => 'pengguna berhasil dihapus.', + 'Unable to remove this user.' => 'Tidak dapat menghapus pengguna ini.', + 'Board updated successfully.' => 'Papan berhasil diperbaharui.', + 'Ready' => 'Siap', + 'Backlog' => 'Tertunda', + 'Work in progress' => 'Sedang dalam pengerjaan', + 'Done' => 'Selesai', + 'Application version:' => 'Versi aplikasi :', + 'Id' => 'Id.', + 'Public link' => 'Pautan publik', + 'Timezone' => 'Zona waktu', + 'Sorry, I didn\'t find this information in my database!' => 'Maaf, saya tidak menemukan informasi ini dalam basis data saya !', + 'Page not found' => 'Halaman tidak ditemukan', + 'Complexity' => 'Kompleksitas', + 'Task limit' => 'Batas tugas.', + 'Task count' => 'Jumlah tugas', + 'User' => 'Pengguna', + 'Comments' => 'Komentar', + 'Comment is required' => 'Komentar diperlukan', + 'Comment added successfully.' => 'Komentar berhasil ditambahkan.', + 'Unable to create your comment.' => 'Tidak dapat menambahkan komentar anda.', + 'Due Date' => 'Batas Tanggal Terakhir', + 'Invalid date' => 'Tanggal tidak valid', + 'Automatic actions' => 'Tindakan otomatis', + 'Your automatic action has been created successfully.' => 'Tindakan otomatis anda berhasil dibuat.', + 'Unable to create your automatic action.' => 'Tidak dapat membuat tindakan otomatis anda.', + 'Remove an action' => 'Hapus tindakan', + 'Unable to remove this action.' => 'Tidak dapat menghapus tindakan ini', + 'Action removed successfully.' => 'Tindakan berhasil dihapus.', + 'Automatic actions for the project "%s"' => 'Tindakan otomatis untuk projek ini « %s »', + 'Add an action' => 'Tambah tindakan', + 'Event name' => 'Nama acara', + 'Action' => 'Tindakan', + 'Event' => 'Acara', + 'When the selected event occurs execute the corresponding action.' => 'Ketika acara yang dipilih terjadi, melakukan tindakan yang sesuai.', + 'Next step' => 'Langkah selanjutnya', + 'Define action parameters' => 'Definisi parameter tindakan', + 'Do you really want to remove this action: "%s"?' => 'Apakah anda yakin akan menghapus tindakan ini « %s » ?', + 'Remove an automatic action' => 'Hapus tindakan otomatis', + 'Assign the task to a specific user' => 'Menetapkan tugas untuk pengguna tertentu', + 'Assign the task to the person who does the action' => 'Memberikan tugas untuk orang yang melakukan tindakan', + 'Duplicate the task to another project' => 'Duplikasi tugas ke projek lain', + 'Move a task to another column' => 'Pindahkan tugas ke kolom lain', + 'Task modification' => 'Modifikasi tugas', + 'Task creation' => 'Membuat tugas', + 'Closing a task' => 'Menutup tugas', + 'Assign a color to a specific user' => 'Menetapkan warna untuk pengguna tertentu', + 'Position' => 'Posisi', + 'Duplicate to project' => 'Duplikasi ke projek lain', + 'Duplicate' => 'Duplikasi', + 'Link' => 'Pautan', + 'Comment updated successfully.' => 'Komentar berhasil diperbaharui.', + 'Unable to update your comment.' => 'Tidak dapat memperbaharui komentar anda.', + 'Remove a comment' => 'Hapus komentar', + 'Comment removed successfully.' => 'Komentar berhasil dihapus.', + 'Unable to remove this comment.' => 'Tidak dapat menghapus komentar ini.', + 'Do you really want to remove this comment?' => 'Apakah anda yakin akan menghapus komentar ini ?', + 'Current password for the user "%s"' => 'Kata laluan saat ini untuk pengguna « %s »', + 'The current password is required' => 'Kata laluan saat ini diperlukan', + 'Wrong password' => 'Kata laluan salah', + 'Unknown' => 'Tidak diketahui', + 'Last logins' => 'Masuk terakhir', + 'Login date' => 'Tanggal masuk', + 'Authentication method' => 'Metode otentifikasi', + 'IP address' => 'Alamat IP', + 'User agent' => 'Agen Pengguna', + 'Persistent connections' => 'Koneksi persisten', + 'No session.' => 'Tidak ada sesi.', + 'Expiration date' => 'Tanggal kadaluarsa', + 'Remember Me' => 'Ingat Saya', + 'Creation date' => 'Tanggal dibuat', + 'Everybody' => 'Semua orang', + 'Open' => 'Terbuka', + 'Closed' => 'Ditutup', + 'Search' => 'Cari', + 'Nothing found.' => 'Tidak ditemukan.', + 'Due date' => 'Batas tanggal terakhir', + 'Description' => 'Deskripsi', + '%d comments' => '%d komentar', + '%d comment' => '%d komentar', + 'Email address invalid' => 'Alamat email tidak valid', + 'Your external account is not linked anymore to your profile.' => 'Akaun eksternal anda tidak lagi terhubung ke profil anda.', + 'Unable to unlink your external account.' => 'Tidak dapat memutuskan Akaun eksternal anda.', + 'External authentication failed' => 'Otentifikasi eksternal gagal', + 'Your external account is linked to your profile successfully.' => 'Akaun eksternal anda berhasil dihubungkan ke profil anda.', + 'Email' => 'Email', + 'Task removed successfully.' => 'Tugas berhasil dihapus.', + 'Unable to remove this task.' => 'Tidak dapat menghapus tugas ini.', + 'Remove a task' => 'Hapus tugas', + 'Do you really want to remove this task: "%s"?' => 'Apakah anda yakin akan menghapus tugas ini « %s » ?', + 'Assign automatically a color based on a category' => 'Otomatis menetapkan warna berdasarkan kategori', + 'Assign automatically a category based on a color' => 'Otomatis menetapkan kategori berdasarkan warna', + 'Task creation or modification' => 'Tugas dibuat atau di mofifikasi', + 'Category' => 'Kategori', + 'Category:' => 'Kategori :', + 'Categories' => 'Kategori', + 'Your category has been created successfully.' => 'Kategori anda berhasil dibuat.', + 'This category has been updated successfully.' => 'Kategori anda berhasil diperbaharui.', + 'Unable to update this category.' => 'Tidak dapat memperbaharui kategori anda.', + 'Remove a category' => 'Hapus kategori', + 'Category removed successfully.' => 'Kategori berhasil dihapus.', + 'Unable to remove this category.' => 'Tidak dapat menghapus kategori ini.', + 'Category modification for the project "%s"' => 'Modifikasi kategori untuk projek « %s »', + 'Category Name' => 'Nama Kategori', + 'Add a new category' => 'Tambah kategori baru', + 'Do you really want to remove this category: "%s"?' => 'Apakah anda yakin akan menghapus kategori ini « %s » ?', + 'All categories' => 'Semua kategori', + 'No category' => 'Tidak ada kategori', + 'The name is required' => 'Nama diperlukan', + 'Remove a file' => 'Hapus berkas', + 'Unable to remove this file.' => 'Tidak dapat menghapus berkas ini.', + 'File removed successfully.' => 'Berkas berhasil dihapus.', + 'Attach a document' => 'Lampirkan dokumen', + 'Do you really want to remove this file: "%s"?' => 'Apakah anda yakin akan menghapus berkas ini « %s » ?', + 'Attachments' => 'Lampiran', + 'Edit the task' => 'Modifikasi tugas', + 'Add a comment' => 'Tambahkan komentar', + 'Edit a comment' => 'Modifikasi komentar', + 'Summary' => 'Ringkasan', + 'Time tracking' => 'Pelacakan waktu', + 'Estimate:' => 'Estimasi :', + 'Spent:' => 'Menghabiskan:', + 'Do you really want to remove this sub-task?' => 'Apakah anda yakin akan menghapus sub-tugas ini ?', + 'Remaining:' => 'Tersisa:', + 'hours' => 'jam', + 'estimated' => 'perkiraan', + 'Sub-Tasks' => 'Sub-tugas', + 'Add a sub-task' => 'Tambahkan sub-tugas', + 'Original estimate' => 'Perkiraan semula', + 'Create another sub-task' => 'Tambahkan sub-tugas lainnya', + 'Time spent' => 'Waktu yang dihabiskan', + 'Edit a sub-task' => 'Modifikasi sub-tugas', + 'Remove a sub-task' => 'Hapus sub-tugas', + 'The time must be a numeric value' => 'Waktu harus berisikan numerik', + 'Todo' => 'Yang harus dilakukan', + 'In progress' => 'Sedang proses', + 'Sub-task removed successfully.' => 'Sub-tugas berhasil dihapus.', + 'Unable to remove this sub-task.' => 'Tidak dapat menghapus sub-tugas.', + 'Sub-task updated successfully.' => 'Sub-tugas berhasil diperbaharui.', + 'Unable to update your sub-task.' => 'Tidak dapat memperbaharui sub-tugas anda.', + 'Unable to create your sub-task.' => 'Tidak dapat membuat sub-tugas anda.', + 'Maximum size: ' => 'Ukuran maksimum: ', + 'Display another project' => 'Lihat projek lain', + 'Created by %s' => 'Dibuat oleh %s', + 'Tasks Export' => 'Ekspor Tugas', + 'Start Date' => 'Tanggal Mulai', + 'Execute' => 'Eksekusi', + 'Task Id' => 'Id Tugas', + 'Creator' => 'Pembuat', + 'Modification date' => 'Tanggal modifikasi', + 'Completion date' => 'Tanggal penyelesaian', + 'Clone' => 'Klon', + 'Project cloned successfully.' => 'Kloning projek berhasil.', + 'Unable to clone this project.' => 'Tidak dapat mengkloning projek.', + 'Enable email notifications' => 'Aktifkan pemberitahuan dari email', + 'Task position:' => 'Posisi tugas :', + 'The task #%d has been opened.' => 'Tugas #%d telah dibuka.', + 'The task #%d has been closed.' => 'Tugas #%d telah ditutup.', + 'Sub-task updated' => 'Sub-tugas diperbaharui', + 'Title:' => 'Judul :', + 'Status:' => 'Status :', + 'Assignee:' => 'Ditugaskan ke :', + 'Time tracking:' => 'Pelacakan waktu :', + 'New sub-task' => 'Sub-tugas baru', + 'New attachment added "%s"' => 'Lampiran baru ditambahkan « %s »', + 'New comment posted by %s' => 'Komentar baru ditambahkan oleh « %s »', + 'New comment' => 'Komentar baru', + 'Comment updated' => 'Komentar diperbaharui', + 'New subtask' => 'Sub-tugas baru', + 'I only want to receive notifications for these projects:' => 'Saya ingin menerima pemberitahuan hanya untuk projek-projek yang dipilih :', + 'view the task on Kanboard' => 'lihat tugas di Kanboard', + 'Public access' => 'Akses awam', + 'Disable public access' => 'Nyahaktifkan akses awam', + 'Enable public access' => 'Aktifkan akses awam', + 'Public access disabled' => 'Akses awam dinyahaktif', + 'Move the task to another project' => 'Pindahkan tugas ke projek lain', + 'Move to project' => 'Pindahkan ke projek lain', + 'Do you really want to duplicate this task?' => 'Anda yakin mengembarkan tugas ini ?', + 'Duplicate a task' => 'Kembarkan tugas', + 'External accounts' => 'Akaun luaran', + 'Account type' => 'Jenis Akaun', + 'Local' => 'Lokal', + 'Remote' => 'Jauh', + 'Enabled' => 'Aktif', + 'Disabled' => 'Tidak aktif', + 'Login:' => 'Nama pengguna :', + 'Full Name:' => 'Nama:', + 'Email:' => 'Emel:', + 'Notifications:' => 'Makluman:', + 'Notifications' => 'Makluman', + 'Account type:' => 'Jenis Akaun :', + 'Edit profile' => 'Sunting profil', + 'Change password' => 'Rubah kata sandri', + 'Password modification' => 'Modifikasi kata laluan', + 'External authentications' => 'Otentifikasi eksternal', + 'Never connected.' => 'Tidak pernah terhubung.', + 'No external authentication enabled.' => 'Tidak ada otentifikasi eksternal yang aktif.', + 'Password modified successfully.' => 'Kata laluan telah berjaya ditukar.', + 'Unable to change the password.' => 'Tidak dapat merubah kata laluanr.', + 'Change category' => 'Tukar kategori', + '%s updated the task %s' => '%s memperbaharui tugas %s', + '%s opened the task %s' => '%s membuka tugas %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s memindahkan tugas %s ke posisi n°%d dalam kolom « %s »', + '%s moved the task %s to the column "%s"' => '%s memindahkan tugas %s ke kolom « %s »', + '%s created the task %s' => '%s membuat tugas %s', + '%s closed the task %s' => '%s menutup tugas %s', + '%s created a subtask for the task %s' => '%s membuat subtugas untuk tugas %s', + '%s updated a subtask for the task %s' => '%s memperbaharui subtugas untuk tugas %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Ditugaskan untuk %s dengan perkiraan %s/%sh', + 'Not assigned, estimate of %sh' => 'Tiada yang ditugaskan, perkiraan %sh', + '%s updated a comment on the task %s' => '%s memperbaharui komentar pada tugas %s', + '%s commented the task %s' => '%s memberikan komentar pada tugas %s', + '%s\'s activity' => 'Aktifitas dari %s', + 'RSS feed' => 'RSS feed', + '%s updated a comment on the task #%d' => '%s memperbaharui komentar pada tugas n°%d', + '%s commented on the task #%d' => '%s memberikan komentar pada tugas n°%d', + '%s updated a subtask for the task #%d' => '%s memperbaharui subtugas untuk tugas n°%d', + '%s created a subtask for the task #%d' => '%s membuat subtugas untuk tugas n°%d', + '%s updated the task #%d' => '%s memperbaharui tugas n°%d', + '%s created the task #%d' => '%s membuat tugas n°%d', + '%s closed the task #%d' => '%s menutup tugas n°%d', + '%s opened the task #%d' => '%s membuka tugas n°%d', + 'Activity' => 'Aktifitas', + 'Default values are "%s"' => 'Standar nilai adalah« %s »', + 'Default columns for new projects (Comma-separated)' => 'Kolom default untuk projek baru (dipisahkan dengan koma)', + 'Task assignee change' => 'Mengubah orang ditugaskan untuk tugas', + '%s changed the assignee of the task #%d to %s' => '%s rubah orang yang ditugaskan dari tugas n%d ke %s', + '%s changed the assignee of the task %s to %s' => '%s mengubah orang yang ditugaskan dari tugas %s ke %s', + 'New password for the user "%s"' => 'Kata laluan baru untuk pengguna « %s »', + 'Choose an event' => 'Pilih sebuah acara', + 'Create a task from an external provider' => 'Buat tugas dari pemasok eksternal', + 'Change the assignee based on an external username' => 'Rubah penugasan berdasarkan nama pengguna eksternal', + 'Change the category based on an external label' => 'Rubah kategori berdasarkan label eksternal', + 'Reference' => 'Referensi', + 'Label' => 'Label', + 'Database' => 'Pengkalan data', + 'About' => 'Tentang', + 'Database driver:' => 'Driver pengkalan data:', + 'Board settings' => 'Pengaturan papan', + 'Webhook settings' => 'Penetapan webhook', + 'Reset token' => 'Menetap semula token', + 'API endpoint:' => 'API endpoint :', + 'Refresh interval for personal board' => 'Interval pembaruan untuk papan pribadi', + 'Refresh interval for public board' => 'Interval pembaruan untuk papan publik', + 'Task highlight period' => 'Periode puncak tugas', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Periode (dalam detik) untuk mempertimbangkan tugas yang baru dimodifikasi (0 untuk menonaktifkan, standar 2 hari)', + 'Frequency in second (60 seconds by default)' => 'Frequensi dalam detik (standar 60 saat)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frekuensi dalam detik (0 untuk menonaktifkan fitur ini, standar 10 detik)', + 'Application URL' => 'URL Aplikasi', + 'Token regenerated.' => 'Token diregenerasi.', + 'Date format' => 'Format tarikh', + 'ISO format is always accepted, example: "%s" and "%s"' => 'Format ISO selalunya diterima, contoh: « %s » et « %s »', + 'New personal project' => 'Projek peribadi baharu', + 'This project is personal' => 'projek ini adalah peribadi', + 'Add' => 'Tambah', + 'Start date' => 'Tarikh mula', + 'Time estimated' => 'Anggaran masa', + 'There is nothing assigned to you.' => 'Tidak ada yang diberikan kepada anda.', + 'My tasks' => 'Tugas saya', + 'Activity stream' => 'Arus aktifitas', + 'Dashboard' => 'Dasbor', + 'Confirmation' => 'Konfirmasi', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Create a comment from an external provider' => 'Buat komentar dari pemasok eksternal', + 'Project management' => 'Manajemen projek', + 'Columns' => 'Kolom', + 'Task' => 'Tugas', + 'Percentage' => 'Persentasi', + 'Number of tasks' => 'Jumlah dari tugas', + 'Task distribution' => 'Pembagian tugas', + 'Analytics' => 'Analitis', + 'Subtask' => 'Subtugas', + 'User repartition' => 'Partisi ulang pengguna', + 'Clone this project' => 'Gandakan projek ini', + 'Column removed successfully.' => 'Kolom berhasil dihapus.', + 'Not enough data to show the graph.' => 'Tidak cukup data untuk menampilkan grafik.', + 'Previous' => 'Sebelumnya', + 'The id must be an integer' => 'Id harus integer', + 'The project id must be an integer' => 'Id projek harus integer', + 'The status must be an integer' => 'Status harus integer', + 'The subtask id is required' => 'Id subtugas diperlukan', + 'The subtask id must be an integer' => 'Id subtugas harus integer', + 'The task id is required' => 'Id tugas diperlukan', + 'The task id must be an integer' => 'Id tugas harus integer', + 'The user id must be an integer' => 'Id user harus integer', + 'This value is required' => 'Nilai ini diperlukan', + 'This value must be numeric' => 'Nilai ini harus angka', + 'Unable to create this task.' => 'Tidak dapat membuat tugas ini', + 'Cumulative flow diagram' => 'Diagram alir kumulatif', + 'Daily project summary' => 'Ringkasan projek harian', + 'Daily project summary export' => 'Ekspot ringkasan projek harian', + 'Exports' => 'Ekspor', + 'This export contains the number of tasks per column grouped per day.' => 'Ekspor ini berisi jumlah dari tugas per kolom dikelompokan perhari.', + 'Active swimlanes' => 'Swimlanes aktif', + 'Add a new swimlane' => 'Tambah swimlane baharu', + 'Default swimlane' => 'Piawai swimlane', + 'Do you really want to remove this swimlane: "%s"?' => 'Anda yakin untuk menghapus swimlane ini : « %s » ?', + 'Inactive swimlanes' => 'Swimlanes tidak aktif', + 'Remove a swimlane' => 'Padam swimlane', + 'Swimlane modification for the project "%s"' => 'Modifikasi swimlane untuk projek « %s »', + 'Swimlane removed successfully.' => 'Swimlane telah dipadamkan.', + 'Swimlanes' => 'Swimlanes', + 'Swimlane updated successfully.' => 'Swimlane telah dikemaskini.', + 'Unable to remove this swimlane.' => 'Tidak dapat menghapus swimlane ini.', + 'Unable to update this swimlane.' => 'Tidak dapat memperbaharui swimlane ini.', + 'Your swimlane has been created successfully.' => 'Swimlane anda berhasil dibuat.', + 'Example: "Bug, Feature Request, Improvement"' => 'Contoh: « Insiden, Permintaan Ciri, Pembaikan »', + 'Default categories for new projects (Comma-separated)' => 'Piawaian kategori untuk projek baru (asingkan guna koma)', + 'Integrations' => 'Integrasi', + 'Integration with third-party services' => 'Integrasi dengan khidmat pihak ketiga', + 'Subtask Id' => 'Id Subtugas', + 'Subtasks' => 'Subtugas', + 'Subtasks Export' => 'Ekspot Subtugas', + 'Task Title' => 'Judul Tugas', + 'Untitled' => 'Tanpa nama', + 'Application default' => 'Aplikasi Piawaian', + 'Language:' => 'Bahasa:', + 'Timezone:' => 'Zon masa:', + 'All columns' => 'Semua kolom', + 'Next' => 'Selanjutnya', + '#%d' => 'n°%d', + 'All swimlanes' => 'Semua swimlane', + 'All colors' => 'Semua warna', + 'Moved to column %s' => 'Pindah ke kolom %s', + 'User dashboard' => 'Papan Kenyataan pengguna', + 'Allow only one subtask in progress at the same time for a user' => 'Izinkan hanya satu subtugas dalam proses secara bersamaan untuk satu pengguna', + 'Edit column "%s"' => 'Modifikasi kolom « %s »', + 'Select the new status of the subtask: "%s"' => 'Pilih status baru untuk subtugas : « %s »', + 'Subtask timesheet' => 'Subtugas absen', + 'There is nothing to show.' => 'Tidak ada yang dapat diperlihatkan.', + 'Time Tracking' => 'Pelacakan waktu', + 'You already have one subtask in progress' => 'Anda sudah ada satu subtugas dalam proses', + 'Which parts of the project do you want to duplicate?' => 'Bagian dalam projek mana yang ingin anda duplikasi?', + 'Disallow login form' => 'Larang formulir masuk', + 'Start' => 'Mula', + 'End' => 'Selesai', + 'Task age in days' => 'Usia tugas dalam bentuk harian', + 'Days in this column' => 'Hari dalam kolom ini', + '%dd' => '%dj', + 'Add a new link' => 'Tambah Pautan baru', + 'Do you really want to remove this link: "%s"?' => 'Anda yakin akan menghapus Pautan ini : « %s » ?', + 'Do you really want to remove this link with task #%d?' => 'Anda yakin akan menghapus Pautan ini dengan tugas n°%d ?', + 'Field required' => 'Medan diperlukan', + 'Link added successfully.' => 'Pautan berhasil ditambahkan.', + 'Link updated successfully.' => 'Pautan berhasil diperbaharui.', + 'Link removed successfully.' => 'Pautan berhasil dihapus.', + 'Link labels' => 'Label Pautan', + 'Link modification' => 'Modifikasi Pautan', + 'Opposite label' => 'Label berlawanan', + 'Remove a link' => 'Hapus Pautan', + 'The labels must be different' => 'Label harus berbeda', + 'There is no link.' => 'Tidak ada Pautan.', + 'This label must be unique' => 'Label ini harus unik', + 'Unable to create your link.' => 'Tidak dapat membuat Pautan anda.', + 'Unable to update your link.' => 'Tidak dapat memperbaharui Pautan anda.', + 'Unable to remove this link.' => 'Tidak dapat menghapus Pautan ini.', + 'relates to' => 'berhubungan dengan', + 'blocks' => 'blok', + 'is blocked by' => 'diblokir oleh', + 'duplicates' => 'duplikat', + 'is duplicated by' => 'diduplikasi oleh', + 'is a child of' => 'anak dari', + 'is a parent of' => 'orant tua dari', + 'targets milestone' => 'milestone target', + 'is a milestone of' => 'adalah milestone dari', + 'fixes' => 'perbaikan', + 'is fixed by' => 'diperbaiki oleh', + 'This task' => 'Tugas ini', + '<1h' => '<1h', + '%dh' => '%dh', + 'Expand tasks' => 'Perluas tugas', + 'Collapse tasks' => 'Lipat tugas', + 'Expand/collapse tasks' => 'Perluas/lipat tugas', + 'Close dialog box' => 'Tutup kotak dialog', + 'Submit a form' => 'Submit formulir', + 'Board view' => 'Table halaman', + 'Keyboard shortcuts' => 'pintas keyboard', + 'Open board switcher' => 'Buka table switcher', + 'Application' => 'Aplikasi', + 'Compact view' => 'Tampilan kompak', + 'Horizontal scrolling' => 'Horisontal bergulir', + 'Compact/wide view' => 'Beralih antara tampilan kompak dan diperluas', + 'Currency' => 'Mata uang', + 'Personal project' => 'projek pribadi', + 'AUD - Australian Dollar' => 'AUD - Dollar Australia', + 'CAD - Canadian Dollar' => 'CAD - Dollar Kanada', + 'CHF - Swiss Francs' => 'CHF - Swiss Prancis', + 'Custom Stylesheet' => 'Kustomisasi Stylesheet', + 'EUR - Euro' => 'EUR - Euro', + 'GBP - British Pound' => 'GBP - Poundsterling inggris', + 'INR - Indian Rupee' => 'INR - Rupe India', + 'JPY - Japanese Yen' => 'JPY - Yen Jepang', + 'NZD - New Zealand Dollar' => 'NZD - Dollar Selandia baru', + 'PEN - Peruvian Sol' => 'PEN - Sol Peru', + 'RSD - Serbian dinar' => 'RSD - Dinar Serbia', + 'CNY - Chinese Yuan' => 'CNY - Yuan China', + 'USD - US Dollar' => 'USD - Dollar Amerika', + 'VES - Venezuelan Bolívar' => 'VES - Bolívar Venezuela', + 'Destination column' => 'Kolom tujuan', + 'Move the task to another column when assigned to a user' => 'Pindahkan tugas ke kolom lain ketika ditugaskan ke pengguna', + 'Move the task to another column when assignee is cleared' => 'Pindahkan tugas ke kolom lain ketika orang yang ditugaskan dibersihkan', + 'Source column' => 'Sumber kolom', + 'Transitions' => 'Transisi', + 'Executer' => 'Eksekusi', + 'Time spent in the column' => 'Waktu yang dihabiskan dalam kolom', + 'Task transitions' => 'Transisi tugas', + 'Task transitions export' => 'Ekspor transisi tugas', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Laporan ini berisi semua kolom yang pindah untuk setiap tugas dengan tanggal, pengguna dan waktu yang dihabiskan untuk setiap transisi.', + 'Currency rates' => 'Nilai tukar mata uang', + 'Rate' => 'Tarif', + 'Change reference currency' => 'Mengubah referensi mata uang', + 'Reference currency' => 'Referensi mata uang', + 'The currency rate has been added successfully.' => 'Nilai tukar mata uang berhasil ditambahkan.', + 'Unable to add this currency rate.' => 'Tidak dapat menambahkan nilai tukar mata uang', + 'Webhook URL' => 'URL webhook', + '%s removed the assignee of the task %s' => '%s menghapus penugasan dari tugas %s', + 'Information' => 'Informasi', + 'Check two factor authentication code' => 'Cek dua faktor kode otentifikasi', + 'The two factor authentication code is not valid.' => 'Kode dua faktor kode otentifikasi tidak valid.', + 'The two factor authentication code is valid.' => 'Kode dua faktor kode otentifikasi valid.', + 'Code' => 'Kode', + 'Two factor authentication' => 'Dua faktor otentifikasi', + 'This QR code contains the key URI: ' => 'kode QR ini mengandung kunci URI : ', + 'Check my code' => 'Memeriksa kode saya', + 'Secret key: ' => 'Kunci rahasia : ', + 'Test your device' => 'Menguji perangkat anda', + 'Assign a color when the task is moved to a specific column' => 'Menetapkan warna ketika tugas tersebut dipindahkan ke kolom tertentu', + '%s via Kanboard' => '%s via Kanboard', + 'Burndown chart' => 'Grafik Burndown', + 'This chart show the task complexity over the time (Work Remaining).' => 'Grafik ini menunjukkan kompleksitas tugas dari waktu ke waktu (Sisa Pekerjaan).', + 'Screenshot taken %s' => 'Screenshot diambil %s', + 'Add a screenshot' => 'Tambah screenshot', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Ambil tangkapan skrin dan tekan CTRL+V atau ⌘+V untuk tampal di sini.', + 'Screenshot uploaded successfully.' => 'Screenshot berhasil diunggah.', + 'SEK - Swedish Krona' => 'SEK - Krona Swedia', + 'Identifier' => 'Identifier', + 'Disable two factor authentication' => 'Matikan dua faktor otentifikasi', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Apakah anda yakin akan mematikan dua faktor otentifikasi untuk pengguna ini : « %s » ?', + 'Edit link' => 'Modifikasi Pautan', + 'Start to type task title...' => 'Mulai mengetik judul tugas...', + 'A task cannot be linked to itself' => 'Sebuah tugas tidak dapat dikaitkan dengan dirinya sendiri', + 'The exact same link already exists' => 'Pautan yang sama persis sudah ada', + 'Recurrent task is scheduled to be generated' => 'Tugas berulang dijadwalkan akan dihasilkan', + 'Score' => 'Skor', + 'The identifier must be unique' => 'Identifier harus unik', + 'This linked task id doesn\'t exists' => 'Id tugas terkait tidak ada', + 'This value must be alphanumeric' => 'Nilai harus alfanumerik', + 'Edit recurrence' => 'Modifikasi pengulangan', + 'Generate recurrent task' => 'Menghasilkan tugas berulang', + 'Trigger to generate recurrent task' => 'Memicu untuk menghasilkan tugas berulang', + 'Factor to calculate new due date' => 'Faktor untuk menghitung tanggal jatuh tempo baru', + 'Timeframe to calculate new due date' => 'Jangka waktu untuk menghitung tanggal jatuh tempo baru', + 'Base date to calculate new due date' => 'Tanggal dasar untuk menghitung tanggal jatuh tempo baru', + 'Action date' => 'Tanggal aksi', + 'Base date to calculate new due date: ' => 'Tanggal dasar untuk menghitung tanggal jatuh tempo baru: ', + 'This task has created this child task: ' => 'Tugas ini telah menciptakan tugas anak ini: ', + 'Day(s)' => 'Hari', + 'Existing due date' => 'Batas waktu yang ada', + 'Factor to calculate new due date: ' => 'Faktor untuk menghitung tanggal jatuh tempo baru: ', + 'Month(s)' => 'Bulan', + 'This task has been created by: ' => 'Tugas ini telah dibuat oleh:', + 'Recurrent task has been generated:' => 'Tugas berulang telah dihasilkan:', + 'Timeframe to calculate new due date: ' => 'Jangka waktu untuk menghitung tanggal jatuh tempo baru: ', + 'Trigger to generate recurrent task: ' => 'Pemicu untuk menghasilkan tugas berulang: ', + 'When task is closed' => 'Ketika tugas ditutup', + 'When task is moved from first column' => 'Ketika tugas dipindahkan dari kolom pertama', + 'When task is moved to last column' => 'Ketika tugas dipindahkan ke kolom terakhir', + 'Year(s)' => 'Tahun', + 'Project settings' => 'Pengaturan projek', + 'Automatically update the start date' => 'Otomatikkan pengemaskinian tanggal', + 'iCal feed' => 'iCal feed', + 'Preferences' => 'Keutamaan', + 'Security' => 'Keamanan', + 'Two factor authentication disabled' => 'Otentifikasi dua faktor dimatikan', + 'Two factor authentication enabled' => 'Otentifikasi dua faktor dihidupkan', + 'Unable to update this user.' => 'Tidak dapat memperbarui pengguna ini.', + 'There is no user management for personal projects.' => 'Tidak ada manajemen pengguna untuk projek-projek pribadi.', + 'User that will receive the email' => 'Pengguna yang akan menerima email', + 'Email subject' => 'Subjek Emel', + 'Date' => 'Tanggal', + 'Add a comment log when moving the task between columns' => 'Menambahkan log komentar ketika memindahkan tugas antara kolom', + 'Move the task to another column when the category is changed' => 'Pindahkan tugas ke kolom lain ketika kategori berubah', + 'Send a task by email to someone' => 'Kirim tugas melalui email ke seseorang', + 'Reopen a task' => 'Membuka kembali tugas', + 'Notification' => 'Pemberitahuan', + '%s moved the task #%d to the first swimlane' => '%s memindahkan tugas n°%d ke swimlane pertama', + 'Swimlane' => 'Swimlane', + '%s moved the task %s to the first swimlane' => '%s memindahkan tugas %s ke swimlane pertama', + '%s moved the task %s to the swimlane "%s"' => '%s memindahkan tugas %s ke swimlane « %s »', + 'This report contains all subtasks information for the given date range.' => 'Laporan ini berisi semua informasi subtugas untuk rentang tanggal tertentu.', + 'This report contains all tasks information for the given date range.' => 'Laporan ini berisi semua informasi tugas untuk rentang tanggal tertentu.', + 'Project activities for %s' => 'Aktifitas projek untuk « %s »', + 'view the board on Kanboard' => 'lihat papan di Kanboard', + 'The task has been moved to the first swimlane' => 'Tugas telah dipindahkan ke swimlane pertama', + 'The task has been moved to another swimlane:' => 'Tugas telah dipindahkan ke swimlane lain:', + 'New title: %s' => 'Judul baru : %s', + 'The task is not assigned anymore' => 'Tugas tidak ditugaskan lagi', + 'New assignee: %s' => 'Penerima baru : %s', + 'There is no category now' => 'Tidak ada kategori untuk sekarang', + 'New category: %s' => 'Kategori baru : %s', + 'New color: %s' => 'Warna baru : %s', + 'New complexity: %d' => 'Kompleksitas baru : %d', + 'The due date has been removed' => 'Tanggal jatuh tempo telah dihapus', + 'There is no description anymore' => 'Tidak ada deskripsi lagi', + 'Recurrence settings has been modified' => 'Pengaturan pengulangan telah dimodifikasi', + 'Time spent changed: %sh' => 'Waktu yang dihabiskan berubah : %sh', + 'Time estimated changed: %sh' => 'Perkiraan waktu berubah : %sh', + 'The field "%s" has been updated' => 'Field « %s » telah diperbaharui', + 'The description has been modified:' => 'Deskripsi telah dimodifikasi', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Apakah anda yakin akan menutup tugas « %s » beserta semua sub-tugasnya ?', + 'I want to receive notifications for:' => 'Saya ingin menerima pemberitahuan untuk :', + 'All tasks' => 'Semua tugas', + 'Only for tasks assigned to me' => 'Hanya untuk tugas yang ditugaskan ke saya', + 'Only for tasks created by me' => 'Hanya untuk tugas yang dibuat oleh saya', + 'Only for tasks created by me and tasks assigned to me' => 'Hanya untuk tugas yang dibuat oleh saya dan ditugaskan ke saya', + '%%Y-%%m-%%d' => '%%d/%%m/%%Y', + 'Total for all columns' => 'Total untuk semua kolom', + 'You need at least 2 days of data to show the chart.' => 'Anda memerlukan setidaknya 2 hari dari data yang menunjukkan grafik.', + '<15m' => '<15m', + '<30m' => '<30m', + 'Stop timer' => 'Hentikan timer', + 'Start timer' => 'Mulai timer', + 'My activity stream' => 'Aliran kegiatan saya', + 'Search tasks' => 'Cari tugas', + 'Reset filters' => 'Reset ulang filter', + 'My tasks due tomorrow' => 'Tugas saya yang berakhir besok', + 'Tasks due today' => 'Tugas yang berakhir hari ini', + 'Tasks due tomorrow' => 'Tugas yang berakhir besok', + 'Tasks due yesterday' => 'Tugas yang berakhir kemarin', + 'Closed tasks' => 'Tugas yang ditutup', + 'Open tasks' => 'Buka Tugas', + 'Not assigned' => 'Tidak ditugaskan', + 'View advanced search syntax' => 'Lihat sintaks pencarian lanjutan', + 'Overview' => 'Ikhtisar', + 'Board/Calendar/List view' => 'Tampilan Papan/Kalender/Daftar', + 'Switch to the board view' => 'Beralih ke tampilan papan', + 'Switch to the list view' => 'Beralih ke tampilan daftar', + 'Go to the search/filter box' => 'Pergi ke kotak pencarian/filter', + 'There is no activity yet.' => 'Tidak ada aktifitas saat ini.', + 'No tasks found.' => 'Tidak ada tugas yang ditemukan.', + 'Keyboard shortcut: "%s"' => 'Keyboard shortcut : « %s »', + 'List' => 'Daftar', + 'Filter' => 'Filter', + 'Advanced search' => 'Pencarian lanjutan', + 'Example of query: ' => 'Contoh dari query : ', + 'Search by project: ' => 'Pencarian berdasarkan projek : ', + 'Search by column: ' => 'Pencarian berdasarkan kolom : ', + 'Search by assignee: ' => 'Pencarian berdasarkan penerima : ', + 'Search by color: ' => 'Pencarian berdasarkan warna : ', + 'Search by category: ' => 'Pencarian berdasarkan kategori : ', + 'Search by description: ' => 'Pencarian berdasarkan deskripsi : ', + 'Search by due date: ' => 'Pencarian berdasarkan tanggal jatuh tempo : ', + 'Average time spent in each column' => 'Rata-rata waktu yang dihabiskan dalam setiap kolom', + 'Average time spent' => 'Rata-rata waktu yang dihabiskan', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Grafik ini menunjukkan rata-rata waktu yang dihabiskan dalam setiap kolom untuk %d tugas.', + 'Average Lead and Cycle time' => 'Rata-rata Memimpin dan Siklus waktu', + 'Average lead time: ' => 'Rata-rata waktu pimpinan : ', + 'Average cycle time: ' => 'Rata-rata siklus waktu : ', + 'Cycle Time' => 'Siklus Waktu', + 'Lead Time' => 'Lead Time', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Grafik ini menunjukkan memimpin rata-rata dan waktu siklus untuk %d tugas terakhir dari waktu ke waktu.', + 'Average time into each column' => 'Rata-rata waktu ke setiap kolom', + 'Lead and cycle time' => 'Lead dan siklus waktu', + 'Lead time: ' => 'Lead time : ', + 'Cycle time: ' => 'Siklus waktu : ', + 'Time spent in each column' => 'Waktu yang dihabiskan di setiap kolom', + 'The lead time is the duration between the task creation and the completion.' => 'Lead time adalah durasi antara pembuatan tugas dan penyelesaian.', + 'The cycle time is the duration between the start date and the completion.' => 'Siklus waktu adalah durasi antara tanggal mulai dan tanggal penyelesaian.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Jika tugas tidak ditutup waktu saat ini yang digunakan sebagai pengganti tanggal penyelesaian.', + 'Set the start date automatically' => 'Secara otomatis mengatur tanggal mulai', + 'Edit Authentication' => 'Modifikasi Otentifikasi', + 'Remote user' => 'Pengguna jauh', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Pengguna jauh tidak menyimpan kata laluan mereka dalam basis data Kanboard, contoh: Akaun LDAP, Google dan Github.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Jika anda mencentang kotak "Larang formulir login", kredensial masuk ke formulis login akan diabaikan.', + 'Default task color' => 'Standar warna tugas', + 'This feature does not work with all browsers.' => 'Ciri ini tidak dapat digunakan pada semua browsers', + 'There is no destination project available.' => 'Tiada destinasi projek yang tersedia.', + 'Trigger automatically subtask time tracking' => 'Picu pengesanan subtugas secara otomatik', + 'Include closed tasks in the cumulative flow diagram' => 'Termasuk tugas yang ditutup pada diagram aliran kumulatif', + 'Current swimlane: %s' => 'Swimlane saat ini : %s', + 'Current column: %s' => 'Kolom saat ini : %s', + 'Current category: %s' => 'Kategori saat ini : %s', + 'no category' => 'tiada kategori', + 'Current assignee: %s' => 'Saat ini ditugaskan pada: %s', + 'not assigned' => 'Belum ditugaskan', + 'Author:' => 'Penulis:', + 'contributors' => 'Penggiat', + 'License:' => 'Lesen:', + 'License' => 'Lesen', + 'Enter the text below' => 'Masukkan teks di bawah', + 'Start date:' => 'Tanggal mulai:', + 'Due date:' => 'Batas waktu:', + 'People who are project managers' => 'Orang-orang yang menjadi pengurus projek', + 'People who are project members' => 'Orang-orang yang menjadi anggota projek', + 'NOK - Norwegian Krone' => 'NOK - Krone Norwegia', + 'Show this column' => 'Perlihatkan kolom ini', + 'Hide this column' => 'Sembunyikan kolom ini', + 'End date' => 'Waktu berakhir', + 'Users overview' => 'Ikhtisar pengguna', + 'Members' => 'Anggota', + 'Shared project' => 'projek bersama', + 'Project managers' => 'Pengurus projek', + 'Projects list' => 'Senarai projek', + 'End date:' => 'Waktu berakhir :', + 'Change task color when using a specific task link' => 'Rubah warna tugas ketika menggunakan Pautan tugas yang spesifik', + 'Task link creation or modification' => 'Pautan tugas pada penciptaan atau penyuntingan', + 'Milestone' => 'Batu Tanda', + 'Reset the search/filter box' => 'Tetap semula pencarian/saringan', + 'Documentation' => 'Dokumentasi', + 'Author' => 'Pengarang', + 'Version' => 'Versi', + 'Plugins' => 'Plugin', + 'There is no plugin loaded.' => 'Tiada plugin yang dimuat.', + 'My notifications' => 'Notifikasi saya', + 'Custom filters' => 'Penapis kustom', + 'Your custom filter has been created successfully.' => 'Penapis kustom anda telah berjaya dicipta.', + 'Unable to create your custom filter.' => 'Tidak dapat mencipta penapis kustom anda.', + 'Custom filter removed successfully.' => 'Penapis kustom berjaya dibuang.', + 'Unable to remove this custom filter.' => 'Tidak dapat membuang penapis kustom ini.', + 'Edit custom filter' => 'Edit penapis kustom', + 'Your custom filter has been updated successfully.' => 'Penapis kustom anda telah berjaya dikemas kini.', + 'Unable to update custom filter.' => 'Tidak dapat mengemaskini penapis kustom.', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Lampiran baru pada tugas #%d: %s', + 'New comment on task #%d' => 'Komen baru pada tugas #%d', + 'Comment updated on task #%d' => 'Komen dikemas kini pada tugas #%d', + 'New subtask on task #%d' => 'Subtugas baru pada tugas #%d', + 'Subtask updated on task #%d' => 'Subtugas dikemas kini pada tugas #%d', + 'New task #%d: %s' => 'Tugas baru #%d: %s', + 'Task updated #%d' => 'Tugas dikemas kini #%d', + 'Task #%d closed' => 'Tugas #%d ditutup', + 'Task #%d opened' => 'Tugas #%d dibuka', + 'Column changed for task #%d' => 'Lajur diubah untuk tugas #%d', + 'New position for task #%d' => 'Kedudukan baru untuk tugas #%d', + 'Swimlane changed for task #%d' => 'Swimlane diubah untuk tugas #%d', + 'Assignee changed on task #%d' => 'Penerima tugas diubah pada tugas #%d', + '%d overdue tasks' => '%d tugas tertunggak', + 'No notification.' => 'Tiada notifikasi.', + 'Mark all as read' => 'Tandakan semua sebagai dibaca', + 'Mark as read' => 'Tandakan sebagai dibaca', + 'Total number of tasks in this column across all swimlanes' => 'Jumlah tugas dalam lajur ini merentasi semua swimlane', + 'Collapse swimlane' => 'Lipat swimlane', + 'Expand swimlane' => 'Kembangkan swimlane', + 'Add a new filter' => 'Tambah penapis baru', + 'Share with all project members' => 'Kongsi dengan semua ahli projek', + 'Shared' => 'Dikongsi', + 'Owner' => 'Pemilik', + 'Unread notifications' => 'Notifikasi belum dibaca', + 'Notification methods:' => 'Kaedah notifikasi:', + 'Unable to read your file' => 'Tidak dapat membaca fail anda', + '%d task(s) have been imported successfully.' => '%d tugas telah berjaya diimport.', + 'Nothing has been imported!' => 'Tiada yang diimport!', + 'Import users from CSV file' => 'Import pengguna dari fail CSV', + '%d user(s) have been imported successfully.' => '%d pengguna telah berjaya diimport.', + 'Comma' => 'Koma', + 'Semi-colon' => 'Koma bertitik', + 'Tab' => 'Tab', + 'Vertical bar' => 'Bar menegak', + 'Double Quote' => 'Tanda Petik Ganda', + 'Single Quote' => 'Tanda Petik Tunggal', + '%s attached a file to the task #%d' => '%s melampirkan fail ke tugas #%d', + 'There is no column or swimlane activated in your project!' => 'Tiada lajur atau swimlane yang diaktifkan dalam projek anda!', + 'Append filter (instead of replacement)' => 'Tambah penapis (bukannya penggantian)', + 'Append/Replace' => 'Tambah/Ganti', + 'Append' => 'Tambah', + 'Replace' => 'Ganti', + 'Import' => 'Import', + 'Change sorting' => 'Ubah susunan', + 'Tasks Importation' => 'Pengimportan Tugas', + 'Delimiter' => 'Pembatas', + 'Enclosure' => 'Penutup', + 'CSV File' => 'Fail CSV', + 'Instructions' => 'Arahan', + 'Your file must use the predefined CSV format' => 'Fail anda mesti menggunakan format CSV yang telah ditentukan', + 'Your file must be encoded in UTF-8' => 'Fail anda mesti dikodkan dalam UTF-8', + 'The first row must be the header' => 'Baris pertama mestilah tajuk', + 'Duplicates are not verified for you' => 'Pendua tidak disahkan untuk anda', + 'The due date must use the ISO format: YYYY-MM-DD' => 'Tarikh tamat tempoh mesti menggunakan format ISO: YYYY-MM-DD', + 'Download CSV template' => 'Muat turun templat CSV', + 'No external integration registered.' => 'Tiada integrasi luaran yang berdaftar.', + 'Duplicates are not imported' => 'Pendua tidak diimport', + 'Usernames must be lowercase and unique' => 'Nama pengguna mestilah huruf kecil dan unik', + 'Passwords will be encrypted if present' => 'Kata laluan akan disulitkan jika ada', + '%s attached a new file to the task %s' => '%s melampirkan fail baru ke tugas %s', + 'Link type' => 'Jenis pautan', + 'Assign automatically a category based on a link' => 'Tetapkan kategori secara automatik berdasarkan pautan', + 'BAM - Konvertible Mark' => 'BAM - Mark Boleh Tukar', + 'Assignee Username' => 'Nama Pengguna Penerima Tugas', + 'Assignee Name' => 'Nama Penerima Tugas', + 'Groups' => 'Kumpulan', + 'Members of %s' => 'Ahli %s', + 'New group' => 'Kumpulan baru', + 'Group created successfully.' => 'Kumpulan berjaya dicipta.', + 'Unable to create your group.' => 'Tidak dapat mencipta kumpulan anda.', + 'Edit group' => 'Edit kumpulan', + 'Group updated successfully.' => 'Kumpulan berjaya dikemas kini.', + 'Unable to update your group.' => 'Tidak dapat mengemaskini kumpulan anda.', + 'Add group member to "%s"' => 'Tambah ahli kumpulan ke "%s"', + 'Group member added successfully.' => 'Ahli kumpulan berjaya ditambah.', + 'Unable to add group member.' => 'Tidak dapat menambah ahli kumpulan.', + 'Remove user from group "%s"' => 'Buang pengguna dari kumpulan "%s"', + 'User removed successfully from this group.' => 'Pengguna berjaya dibuang dari kumpulan ini.', + 'Unable to remove this user from the group.' => 'Tidak dapat membuang pengguna ini dari kumpulan.', + 'Remove group' => 'Buang kumpulan', + 'Group removed successfully.' => 'Kumpulan berjaya dibuang.', + 'Unable to remove this group.' => 'Tidak dapat membuang kumpulan ini.', + 'Project Permissions' => 'Kebenaran Projek', + 'Manager' => 'Pengurus', + 'Project Manager' => 'Pengurus Projek', + 'Project Member' => 'Ahli Projek', + 'Project Viewer' => 'Penonton Projek', + 'Your account is locked for %d minutes' => 'Akaun anda dikunci selama %d minit', + 'Invalid captcha' => 'Captcha tidak sah', + 'The name must be unique' => 'Nama mestilah unik', + 'View all groups' => 'Lihat semua kumpulan', + 'There is no user available.' => 'Tiada pengguna yang tersedia.', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Adakah anda benar-benar mahu membuang pengguna "%s" dari kumpulan "%s"?', + 'There is no group.' => 'Tiada kumpulan.', + 'Add group member' => 'Tambah ahli kumpulan', + 'Do you really want to remove this group: "%s"?' => 'Adakah anda benar-benar mahu membuang kumpulan ini: "%s"?', + 'There is no user in this group.' => 'Tiada pengguna dalam kumpulan ini.', + 'Permissions' => 'Kebenaran', + 'Allowed Users' => 'Pengguna Dibenarkan', + 'No specific user has been allowed.' => 'Tiada pengguna tertentu yang dibenarkan.', + 'Role' => 'Peranan', + 'Enter user name...' => 'Masukkan nama pengguna...', + 'Allowed Groups' => 'Kumpulan Dibenarkan', + 'No group has been allowed.' => 'Tiada kumpulan yang dibenarkan.', + 'Group' => 'Kumpulan', + 'Group Name' => 'Nama Kumpulan', + 'Enter group name...' => 'Masukkan nama kumpulan...', + 'Role:' => 'Peranan', + 'Project members' => 'Anggota projek', + '%s mentioned you in the task #%d' => '%s menyebut anda dalam tugas #%d', + '%s mentioned you in a comment on the task #%d' => '%s menyebut anda dalam komen pada tugas #%d', + 'You were mentioned in the task #%d' => 'Anda disebut dalam tugas #%d', + 'You were mentioned in a comment on the task #%d' => 'Anda disebut dalam komen pada tugas #%d', + 'Estimated hours: ' => 'Anggaran jam: ', + 'Actual hours: ' => 'Jam sebenar: ', + 'Hours Spent' => 'Jam Dihabiskan', + 'Hours Estimated' => 'Jam Dianggarkan', + 'Estimated Time' => 'Masa Dianggarkan', + 'Actual Time' => 'Masa Sebenar', + 'Estimated vs actual time' => 'Masa anggaran vs sebenar', + 'RUB - Russian Ruble' => 'RUB - Rubel Rusia', + 'Assign the task to the person who does the action when the column is changed' => 'Tetapkan tugas kepada orang yang melakukan tindakan apabila lajur diubah', + 'Close a task in a specific column' => 'Tutup tugas dalam lajur tertentu', + 'Time-based One-time Password Algorithm' => 'Algoritma Kata Laluan Satu Kali Berasaskan Masa', + 'Two-Factor Provider: ' => 'Pembekal Dua Faktor: ', + 'Disable two-factor authentication' => 'Nyahaktifkan pengesahan dua faktor', + 'Enable two-factor authentication' => 'Aktifkan pengesahan dua faktor', + 'There is no integration registered at the moment.' => 'Tiada integrasi yang berdaftar pada masa ini.', + 'Password Reset for Kanboard' => 'Tetapan Semula Kata Laluan untuk Kanboard', + 'Forgot password?' => 'Lupa kata laluan?', + 'Enable "Forget Password"' => 'Aktifkan "Lupa Kata Laluan"', + 'Password Reset' => 'Tetapan Semula Kata Laluan', + 'New password' => 'Kata laluan baru', + 'Change Password' => 'Tukar Kata Laluan', + 'To reset your password click on this link:' => 'Untuk menetapkan semula kata laluan anda klik pada pautan ini:', + 'Last Password Reset' => 'Tetapan Semula Kata Laluan Terakhir', + 'The password has never been reinitialized.' => 'Kata laluan tidak pernah ditetapkan semula.', + 'Creation' => 'Ciptaan', + 'Expiration' => 'Jangka hayat', + 'Password reset history' => 'Sirah tetap semula kata laluan', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Semua tugas dalam lajur "%s" dan swimlane "%s" telah berjaya ditutup.', + 'Do you really want to close all tasks of this column?' => 'Adakah anda benar-benar mahu menutup semua tugas dalam lajur ini?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d tugas dalam lajur "%s" dan swimlane "%s" akan ditutup.', + 'Close all tasks in this column and this swimlane' => 'Tutup semua tugas dalam lajur ini dan swimlane ini', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Tiada plugin yang mendaftarkan kaedah notifikasi projek. Anda masih boleh mengkonfigurasi notifikasi individu dalam profil pengguna anda.', + 'My dashboard' => 'Papan pemuka saya', + 'My profile' => 'Profil saya', + 'Project owner: ' => 'Pemilik projek: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'Pengecam projek adalah pilihan dan mesti alfanumerik, contoh: PROJEKSAYA.', + 'Project owner' => 'Pemilik projek', + 'Personal projects do not have users and groups management.' => 'Projek peribadi tidak mempunyai pengurusan pengguna dan kumpulan.', + 'There is no project member.' => 'Tiada ahli projek.', + 'Priority' => 'Keutamaan', + 'Task priority' => 'Keutamaan tugas', + 'General' => 'Umum', + 'Dates' => 'Tarikh', + 'Default priority' => 'Keutamaan lalai', + 'Lowest priority' => 'Keutamaan terendah', + 'Highest priority' => 'Keutamaan tertinggi', + 'Close a task when there is no activity' => 'Tutup tugas apabila tiada aktiviti', + 'Duration in days' => 'Tempoh dalam hari', + 'Send email when there is no activity on a task' => 'Hantar e-mel apabila tiada aktiviti pada tugas', + 'Unable to fetch link information.' => 'Tidak dapat mengambil maklumat pautan.', + 'Daily background job for tasks' => 'Tugasan latar belakang harian untuk tugas', + 'Auto' => 'Auto', + 'Related' => 'Berkaitan', + 'Attachment' => 'Lampiran', + 'Web Link' => 'Pautan Web', + 'External links' => 'Pautan luaran', + 'Add external link' => 'Tambah pautan luaran', + 'Type' => 'Jenis', + 'Dependency' => 'Kebergantungan', + 'Add internal link' => 'Tambah pautan dalaman', + 'Add a new external link' => 'Tambah pautan luaran baru', + 'Edit external link' => 'Edit pautan luaran', + 'External link' => 'Pautan luaran', + 'Copy and paste your link here...' => 'Salin dan tampal pautan anda di sini...', + 'URL' => 'URL', + 'Internal links' => 'Pautan dalaman', + 'Assign to me' => 'Tetapkan kepada saya', + 'Me' => 'Saya', + 'Do not duplicate anything' => 'Jangan pendua apa-apa', + 'Projects management' => 'Pengurusan projek', + 'Users management' => 'Pengurusan pengguna', + 'Groups management' => 'Pengurusan kumpulan', + 'Create from another project' => 'Cipta dari projek lain', + 'open' => 'terbuka', + 'closed' => 'ditutup', + 'Priority:' => 'Keutamaan:', + 'Reference:' => 'Rujukan:', + 'Complexity:' => 'Kerumitan:', + 'Swimlane:' => 'Swimlane:', + 'Column:' => 'Lajur:', + 'Position:' => 'Kedudukan:', + 'Creator:' => 'Pencipta:', + 'Time estimated:' => 'Masa dianggarkan:', + '%s hours' => '%s jam', + 'Time spent:' => 'Masa dihabiskan:', + 'Created:' => 'Dicipta:', + 'Modified:' => 'Diubah suai:', + 'Completed:' => 'Selesai:', + 'Started:' => 'Dimulakan:', + 'Moved:' => 'Dipindahkan:', + 'Task #%d' => 'Tugas #%d', + 'Time format' => 'Format masa', + 'Start date: ' => 'Tarikh mula: ', + 'End date: ' => 'Tarikh tamat: ', + 'New due date: ' => 'Tarikh tamat tempoh baru: ', + 'Start date changed: ' => 'Tarikh mula diubah: ', + 'Disable personal projects' => 'Nyahaktifkan projek peribadi', + 'Do you really want to remove this custom filter: "%s"?' => 'Adakah anda benar-benar mahu membuang penapis kustom ini: "%s"?', + 'Remove a custom filter' => 'Buang penapis kustom', + 'User activated successfully.' => 'Pengguna berjaya diaktifkan.', + 'Unable to enable this user.' => 'Tidak dapat mengaktifkan pengguna ini.', + 'User disabled successfully.' => 'Pengguna berjaya dinyahaktifkan.', + 'Unable to disable this user.' => 'Tidak dapat menyahaktifkan pengguna ini.', + 'All files have been uploaded successfully.' => 'Semua fail telah berjaya dimuat naik.', + 'The maximum allowed file size is %sB.' => 'Saiz fail maksimum yang dibenarkan ialah %sB.', + 'Drag and drop your files here' => 'Seret dan lepaskan fail anda di sini', + 'choose files' => 'pilih fail', + 'View profile' => 'Lihat profil', + 'Two Factor' => 'Dua Faktor', + 'Disable user' => 'Nyahaktifkan pengguna', + 'Do you really want to disable this user: "%s"?' => 'Adakah anda benar-benar mahu menyahaktifkan pengguna ini: "%s"?', + 'Enable user' => 'Aktifkan pengguna', + 'Do you really want to enable this user: "%s"?' => 'Adakah anda benar-benar mahu mengaktifkan pengguna ini: "%s"?', + 'Download' => 'Muat turun', + 'Uploaded: %s' => 'Dimuat naik: %s', + 'Size: %s' => 'Saiz: %s', + 'Uploaded by %s' => 'Dimuat naik oleh %s', + 'Filename' => 'Nama fail', + 'Size' => 'Saiz', + 'Column created successfully.' => 'Lajur berjaya dicipta.', + 'Another column with the same name exists in the project' => 'Lajur lain dengan nama yang sama wujud dalam projek', + 'Default filters' => 'Penapis lalai', + 'Your board doesn\'t have any columns!' => 'Papan anda tidak mempunyai sebarang lajur!', + 'Change column position' => 'Ubah kedudukan lajur', + 'Switch to the project overview' => 'Beralih ke gambaran keseluruhan projek', + 'User filters' => 'Penapis pengguna', + 'Category filters' => 'Penapis kategori', + 'Upload a file' => 'Muat naik fail', + 'View file' => 'Lihat fail', + 'Last activity' => 'Aktiviti terakhir', + 'Change subtask position' => 'Ubah kedudukan subtugas', + 'This value must be greater than %d' => 'Nilai ini mestilah lebih besar daripada %d', + 'Another swimlane with the same name exists in the project' => 'Swimlane lain dengan nama yang sama wujud dalam projek', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Contoh: https://example.kanboard.org/ (digunakan untuk menjana URL mutlak)', + 'Actions duplicated successfully.' => 'Tindakan berjaya diduplikat.', + 'Unable to duplicate actions.' => 'Tidak dapat menduplikat tindakan.', + 'Add a new action' => 'Tambah tindakan baru', + 'Import from another project' => 'Import dari projek lain', + 'There is no action at the moment.' => 'Tiada tindakan pada masa ini.', + 'Import actions from another project' => 'Import tindakan dari projek lain', + 'There is no available project.' => 'Tiada projek yang tersedia.', + 'Local File' => 'Fail Tempatan', + 'Configuration' => 'Konfigurasi', + 'PHP version:' => 'Versi PHP:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'Versi OS:', + 'Database version:' => 'Versi pangkalan data:', + 'Browser:' => 'Pelayar:', + 'Task view' => 'Paparan tugas', + 'Edit task' => 'Edit tugas', + 'Edit description' => 'Edit penerangan', + 'New internal link' => 'Pautan dalaman baru', + 'Display list of keyboard shortcuts' => 'Papar senarai pintasan papan kekunci', + 'Avatar' => 'Avatar', + 'Upload my avatar image' => 'Muat naik imej avatar saya', + 'Remove my image' => 'Buang imej saya', + 'The OAuth2 state parameter is invalid' => 'Parameter keadaan OAuth2 tidak sah', + 'User not found.' => 'Pengguna tidak dijumpai.', + 'Search in activity stream' => 'Cari dalam aliran aktiviti', + 'My activities' => 'Aktiviti saya', + 'Activity until yesterday' => 'Aktiviti sehingga semalam', + 'Activity until today' => 'Aktiviti sehingga hari ini', + 'Search by creator: ' => 'Cari mengikut pencipta: ', + 'Search by creation date: ' => 'Cari mengikut tarikh cipta: ', + 'Search by task status: ' => 'Cari mengikut status tugas: ', + 'Search by task title: ' => 'Cari mengikut tajuk tugas: ', + 'Activity stream search' => 'Carian aliran aktiviti', + 'Projects where "%s" is manager' => 'Projek di mana "%s" adalah pengurus', + 'Projects where "%s" is member' => 'Projek di mana "%s" adalah ahli', + 'Open tasks assigned to "%s"' => 'Tugas terbuka yang ditetapkan kepada "%s"', + 'Closed tasks assigned to "%s"' => 'Tugas tertutup yang ditetapkan kepada "%s"', + 'Assign automatically a color based on a priority' => 'Tetapkan warna secara automatik berdasarkan keutamaan', + 'Overdue tasks for the project(s) "%s"' => 'Tugas terlambat untuk projek « %s »', + 'Upload files' => 'Muat naik fail', + 'Installed Plugins' => 'Plugin yang Dipasang', + 'Plugin Directory' => 'Direktori Plugin', + 'Plugin installed successfully.' => 'Plugin berjaya dipasang.', + 'Plugin updated successfully.' => 'Plugin berjaya dikemas kini.', + 'Plugin removed successfully.' => 'Plugin berjaya dibuang.', + 'Subtask converted to task successfully.' => 'Subtugas berjaya ditukar kepada tugas.', + 'Unable to convert the subtask.' => 'Tidak dapat menukar subtugas.', + 'Unable to extract plugin archive.' => 'Tidak dapat mengekstrak arkib plugin.', + 'Plugin not found.' => 'Plugin tidak dijumpai.', + 'You don\'t have the permission to remove this plugin.' => 'Anda tidak mempunyai kebenaran untuk membuang plugin ini.', + 'Unable to download plugin archive.' => 'Tidak dapat memuat turun arkib plugin.', + 'Unable to write temporary file for plugin.' => 'Tidak dapat menulis fail sementara untuk plugin.', + 'Unable to open plugin archive.' => 'Tidak dapat membuka arkib plugin.', + 'There is no file in the plugin archive.' => 'Tiada fail dalam arkib plugin.', + 'Create tasks in bulk' => 'Cipta tugas secara pukal', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Instans Kanboard anda tidak dikonfigurasi untuk memasang plugin dari antara muka pengguna.', + 'There is no plugin available.' => 'Tiada plugin yang tersedia.', + 'Install' => 'Pasang', + 'Update' => 'Kemas kini', + 'Up to date' => 'Terkini', + 'Not available' => 'Tidak tersedia', + 'Remove plugin' => 'Buang plugin', + 'Do you really want to remove this plugin: "%s"?' => 'Adakah anda benar-benar mahu membuang plugin ini: "%s"?', + 'Uninstall' => 'Nyahpasang', + 'Listing' => 'Penyenaraian', + 'Metadata' => 'Metadata', + 'Manage projects' => 'Urus projek', + 'Convert to task' => 'Tukar kepada tugas', + 'Convert sub-task to task' => 'Tukar sub-tugas kepada tugas', + 'Do you really want to convert this sub-task to a task?' => 'Adakah anda benar-benar mahu menukar sub-tugas ini kepada tugas?', + 'My task title' => 'Tajuk tugas saya', + 'Enter one task by line.' => 'Masukkan satu tugas setiap baris.', + 'Number of failed login:' => 'Bilangan log masuk gagal:', + 'Account locked until:' => 'Akaun dikunci sehingga:', + 'Email settings' => 'Tetapan e-mel', + 'Email sender address' => 'Alamat penghantar e-mel', + 'Email transport' => 'Pengangkutan e-mel', + 'Webhook token' => 'Token webhook', + 'Project tags management' => 'Pengurusan tag projek', + 'Tag created successfully.' => 'Tag berjaya dicipta.', + 'Unable to create this tag.' => 'Tidak dapat mencipta tag ini.', + 'Tag updated successfully.' => 'Tag berjaya dikemas kini.', + 'Unable to update this tag.' => 'Tidak dapat mengemaskini tag ini.', + 'Tag removed successfully.' => 'Tag berjaya dibuang.', + 'Unable to remove this tag.' => 'Tidak dapat membuang tag ini.', + 'Global tags management' => 'Pengurusan tag global', + 'Tags' => 'Tag', + 'Tags management' => 'Pengurusan tag', + 'Add new tag' => 'Tambah tag baru', + 'Edit a tag' => 'Edit tag', + 'Project tags' => 'Tag projek', + 'There is no specific tag for this project at the moment.' => 'Tiada tag khusus untuk projek ini pada masa ini.', + 'Tag' => 'Tag', + 'Remove a tag' => 'Buang tag', + 'Do you really want to remove this tag: "%s"?' => 'Adakah anda benar-benar mahu membuang tag ini: "%s"?', + 'Global tags' => 'Tag global', + 'There is no global tag at the moment.' => 'Tiada tag global pada masa ini.', + 'This field cannot be empty' => 'Medan ini tidak boleh kosong', + 'Close a task when there is no activity in a specific column' => 'Tutup tugas apabila tiada aktiviti dalam lajur tertentu', + '%s removed a subtask for the task #%d' => '%s membuang subtugas untuk tugas #%d', + '%s removed a comment on the task #%d' => '%s membuang komen pada tugas #%d', + 'Comment removed on task #%d' => 'Komen dibuang pada tugas #%d', + 'Subtask removed on task #%d' => 'Subtugas dibuang pada tugas #%d', + 'Hide tasks in this column in the dashboard' => 'Sembunyikan tugas dalam lajur ini di papan pemuka', + '%s removed a comment on the task %s' => '%s membuang komen pada tugas %s', + '%s removed a subtask for the task %s' => '%s membuang subtugas untuk tugas %s', + 'Comment removed' => 'Komen dibuang', + 'Subtask removed' => 'Subtugas dibuang', + '%s set a new internal link for the task #%d' => '%s menetapkan pautan dalaman baru untuk tugas #%d', + '%s removed an internal link for the task #%d' => '%s membuang pautan dalaman untuk tugas #%d', + 'A new internal link for the task #%d has been defined' => 'Pautan dalaman baru untuk tugas #%d telah ditentukan', + 'Internal link removed for the task #%d' => 'Pautan dalaman dibuang untuk tugas #%d', + '%s set a new internal link for the task %s' => '%s menetapkan pautan dalaman baru untuk tugas %s', + '%s removed an internal link for the task %s' => '%s membuang pautan dalaman untuk tugas %s', + 'Automatically set the due date on task creation' => 'Tetapkan tarikh tamat tempoh secara automatik semasa penciptaan tugas', + 'Move the task to another column when closed' => 'Pindahkan tugas ke lajur lain apabila ditutup', + 'Move the task to another column when not moved during a given period' => 'Pindahkan tugas ke lajur lain apabila tidak dipindahkan dalam tempoh yang diberikan', + 'Dashboard for %s' => 'Papan pemuka untuk %s', + 'Tasks overview for %s' => 'Gambaran keseluruhan tugas untuk %s', + 'Subtasks overview for %s' => 'Gambaran keseluruhan subtugas untuk %s', + 'Projects overview for %s' => 'Gambaran keseluruhan projek untuk %s', + 'Activity stream for %s' => 'Strim aktiviti untuk %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Berikan warna apabila tugas dipindahkan ke lajur tertentu', + 'Assign a priority when the task is moved to a specific swimlane' => 'Berikan keutamaan apabila tugas dipindahkan ke lajur tertentu', + 'User unlocked successfully.' => 'Pengguna berjaya dinyahkunci.', + 'Unable to unlock the user.' => 'Tidak dapat menyahkunci pengguna.', + 'Move a task to another swimlane' => 'Pindahkan tugas ke lajur lain', + 'Creator Name' => 'Nama Pencipta', + 'Time spent and estimated' => 'Masa yang dihabiskan dan dianggarkan', + 'Move position' => 'Alih kedudukan', + 'Move task to another position on the board' => 'Pindahkan tugas ke kedudukan lain pada papan', + 'Insert before this task' => 'Sisipkan sebelum tugas ini', + 'Insert after this task' => 'Sisipkan selepas tugas ini', + 'Unlock this user' => 'Nyahkunci pengguna ini', + 'Custom Project Roles' => 'Peranan Projek Tersuai', + 'Add a new custom role' => 'Tambah peranan tersuai baharu', + 'Restrictions for the role "%s"' => 'Sekatan untuk peranan "%s"', + 'Add a new project restriction' => 'Tambah sekatan projek baharu', + 'Add a new drag and drop restriction' => 'Tambah sekatan seret dan lepas baharu', + 'Add a new column restriction' => 'Tambah sekatan lajur baharu', + 'Edit this role' => 'Edit peranan ini', + 'Remove this role' => 'Alih keluar peranan ini', + 'There is no restriction for this role.' => 'Tiada sekatan untuk peranan ini.', + 'Only moving task between those columns is permitted' => 'Hanya memindahkan tugas antara lajur tersebut dibenarkan', + 'Close a task in a specific column when not moved during a given period' => 'Tutup tugas dalam lajur tertentu apabila tidak dipindahkan dalam tempoh yang diberikan', + 'Edit columns' => 'Edit lajur', + 'The column restriction has been created successfully.' => 'Sekatan lajur telah berjaya dicipta.', + 'Unable to create this column restriction.' => 'Tidak dapat mencipta sekatan lajur ini.', + 'Column restriction removed successfully.' => 'Sekatan lajur berjaya dialih keluar.', + 'Unable to remove this restriction.' => 'Tidak dapat mengalih keluar sekatan ini.', + 'Your custom project role has been created successfully.' => 'Peranan projek tersuai anda berjaya dicipta.', + 'Unable to create custom project role.' => 'Tidak dapat mencipta peranan projek tersuai.', + 'Your custom project role has been updated successfully.' => 'Peranan projek tersuai anda berjaya dikemas kini.', + 'Unable to update custom project role.' => 'Tidak dapat mengemas kini peranan projek tersuai.', + 'Custom project role removed successfully.' => 'Peranan projek tersuai berjaya dialih keluar.', + 'Unable to remove this project role.' => 'Tidak dapat mengalih keluar peranan projek ini.', + 'The project restriction has been created successfully.' => 'Sekatan projek telah berjaya dicipta.', + 'Unable to create this project restriction.' => 'Tidak dapat mencipta sekatan projek ini.', + 'Project restriction removed successfully.' => 'Sekatan projek berjaya dialih keluar.', + 'You cannot create tasks in this column.' => 'Anda tidak dapat mencipta tugas dalam lajur ini.', + 'Task creation is permitted for this column' => 'Penciptaan tugas dibenarkan untuk lajur ini', + 'Closing or opening a task is permitted for this column' => 'Menutup atau membuka tugas dibenarkan untuk lajur ini', + 'Task creation is blocked for this column' => 'Penciptaan tugas disekat untuk lajur ini', + 'Closing or opening a task is blocked for this column' => 'Menutup atau membuka tugas disekat untuk lajur ini', + 'Task creation is not permitted' => 'Penciptaan tugas tidak dibenarkan', + 'Closing or opening a task is not permitted' => 'Menutup atau membuka tugas tidak dibenarkan', + 'New drag and drop restriction for the role "%s"' => 'Sekatan seret dan lepas baharu untuk peranan "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Orang yang mempunyai peranan ini hanya boleh memindahkan tugas antara lajur sumber dan destinasi.', + 'Remove a column restriction' => 'Alih keluar sekatan lajur', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Adakah anda benar-benar mahu mengalih keluar sekatan lajur ini: "%s" ke "%s"?', + 'New column restriction for the role "%s"' => 'Sekatan lajur baharu untuk peranan "%s"', + 'Rule' => 'Peraturan', + 'Do you really want to remove this column restriction?' => 'Adakah anda benar-benar mahu mengalih keluar sekatan lajur ini?', + 'Custom roles' => 'Peranan tersuai', + 'New custom project role' => 'Peranan projek tersuai baharu', + 'Edit custom project role' => 'Edit peranan projek tersuai', + 'Remove a custom role' => 'Alih keluar peranan tersuai', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Adakah anda benar-benar mahu mengalih keluar peranan tersuai ini: "%s"? Semua orang yang diberikan peranan ini akan menjadi ahli projek.', + 'There is no custom role for this project.' => 'Tiada peranan tersuai untuk projek ini.', + 'New project restriction for the role "%s"' => 'Sekatan projek baharu untuk peranan "%s"', + 'Restriction' => 'Sekatan', + 'Remove a project restriction' => 'Alih keluar sekatan projek', + 'Do you really want to remove this project restriction: "%s"?' => 'Adakah anda benar-benar mahu mengalih keluar sekatan projek ini: "%s"?', + 'Duplicate to multiple projects' => 'Gandakan ke pelbagai projek', + 'This field is required' => 'Ruang ini diperlukan', + 'Moving a task is not permitted' => 'Memindahkan tugas tidak dibenarkan', + 'This value must be in the range %d to %d' => 'Nilai ini mestilah dalam julat %d hingga %d', + 'You are not allowed to move this task.' => 'Anda tidak dibenarkan untuk memindahkan tugas ini.', + 'API User Access' => 'Akses Pengguna API', + 'Preview' => 'Pratonton', + 'Write' => 'Tulis', + 'Write your text in Markdown' => 'Tulis teks anda dalam Markdown', + 'No personal API access token registered.' => 'Tiada token akses API peribadi didaftarkan.', + 'Your personal API access token is "%s"' => 'Token akses API peribadi anda ialah "%s"', + 'Remove your token' => 'Alih keluar token anda', + 'Generate a new token' => 'Jana token baharu', + 'Showing %d-%d of %d' => 'Memaparkan %d-%d daripada %d', + 'Outgoing Emails' => 'E-mel Keluar', + 'Add or change currency rate' => 'Tambah atau ubah kadar mata wang', + 'Reference currency: %s' => 'Mata wang rujukan: %s', + 'Add custom filters' => 'Tambah penapis tersuai', + 'Export' => 'Eksport', + 'Add link label' => 'Tambah label pautan', + 'Incompatible Plugins' => 'Pemalam Tidak Serasi', + 'Compatibility' => 'Keserasian', + 'Permissions and ownership' => 'Kebenaran dan pemilikan', + 'Priorities' => 'Keutamaan', + 'Close this window' => 'Tutup tetingkap ini', + 'Unable to upload this file.' => 'Tidak dapat memuat naik fail ini.', + 'Import tasks' => 'Import tugas', + 'Choose a project' => 'Pilih projek', + 'Profile' => 'Profil', + 'Application role' => 'Peranan aplikasi', + '%d invitations were sent.' => '%d jemputan telah dihantar.', + '%d invitation was sent.' => '%d jemputan telah dihantar.', + 'Unable to create this user.' => 'Tidak dapat mencipta pengguna ini.', + 'Kanboard Invitation' => 'Jemputan Kanboard', + 'Visible on dashboard' => 'Terlihat pada papan pemuka', + 'Created at:' => 'Dicipta pada:', + 'Updated at:' => 'Dikemas kini pada:', + 'There is no custom filter.' => 'Tiada penapis tersuai.', + 'New User' => 'Pengguna Baharu', + 'Authentication' => 'Pengesahan', + 'If checked, this user will use a third-party system for authentication.' => 'Jika ditandakan, pengguna ini akan menggunakan sistem pihak ketiga untuk pengesahan.', + 'The password is necessary only for local users.' => 'Kata laluan hanya diperlukan untuk pengguna tempatan.', + 'You have been invited to register on Kanboard.' => 'Anda telah dijemput untuk mendaftar di Kanboard.', + 'Click here to join your team' => 'Klik di sini untuk menyertai pasukan anda', + 'Invite people' => 'Jemput orang', + 'Emails' => 'E-mel', + 'Enter one email address by line.' => 'Masukkan satu alamat e-mel setiap baris.', + 'Add these people to this project' => 'Tambah orang-orang ini ke projek ini', + 'Add this person to this project' => 'Tambah orang ini ke projek ini', + 'Sign-up' => 'Daftar', + 'Credentials' => 'Kelayakan', + 'New user' => 'Pengguna baharu', + 'This username is already taken' => 'Nama pengguna ini sudah digunakan', + 'Your profile must have a valid email address.' => 'Profil anda mesti mempunyai alamat e-mel yang sah.', + 'TRL - Turkish Lira' => 'TRL - Lira Turki', + 'The project email is optional and could be used by several plugins.' => 'E-mel projek adalah pilihan dan boleh digunakan oleh beberapa pemalam.', + 'The project email must be unique across all projects' => 'E-mel projek mestilah unik di semua projek', + 'The email configuration has been disabled by the administrator.' => 'Konfigurasi e-mel telah dilumpuhkan oleh pentadbir.', + 'Close this project' => 'Tutup projek ini', + 'Open this project' => 'Buka projek ini', + 'Close a project' => 'Tutup projek', + 'Do you really want to close this project: "%s"?' => 'Adakah anda benar-benar mahu menutup projek ini: "%s"?', + 'Reopen a project' => 'Buka semula projek', + 'Do you really want to reopen this project: "%s"?' => 'Adakah anda benar-benar mahu membuka semula projek ini: "%s"?', + 'This project is open' => 'Projek ini dibuka', + 'This project is closed' => 'Projek ini ditutup', + 'Unable to upload files, check the permissions of your data folder.' => 'Tidak dapat memuat naik fail, periksa kebenaran folder data anda.', + 'Another category with the same name exists in this project' => 'Kategori lain dengan nama yang sama wujud dalam projek ini', + 'Comment sent by email successfully.' => 'Komen berjaya dihantar melalui e-mel.', + 'Sent by email to "%s" (%s)' => 'Dihantar melalui e-mel kepada "%s" (%s)', + 'Unable to read uploaded file.' => 'Tidak dapat membaca fail yang dimuat naik.', + 'Database uploaded successfully.' => 'Pangkalan data berjaya dimuat naik.', + 'Task sent by email successfully.' => 'Tugas berjaya dihantar melalui e-mel.', + 'There is no category in this project.' => 'Tiada kategori dalam projek ini.', + 'Send by email' => 'Hantar melalui e-mel', + 'Create and send a comment by email' => 'Cipta dan hantar komen melalui e-mel', + 'Subject' => 'Subjek', + 'Upload the database' => 'Muat naik pangkalan data', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Anda boleh memuat naik pangkalan data Sqlite yang dimuat turun sebelum ini (format Gzip).', + 'Database file' => 'Fail pangkalan data', + 'Upload' => 'Muat naik', + 'Your project must have at least one active swimlane.' => 'Projek anda mesti mempunyai sekurang-kurangnya satu lajur aktif.', + 'Project: %s' => 'Projek: %s', + 'Automatic action not found: "%s"' => 'Tindakan automatik tidak ditemui: "%s"', + '%d projects' => '%d projek', + '%d project' => '%d projek', + 'There is no project.' => 'Tiada projek.', + 'Sort' => 'Isih', + 'Project ID' => 'ID Projek', + 'Project name' => 'Nama projek', + 'Public' => 'Awam', + 'Personal' => 'Peribadi', + '%d tasks' => '%d tugas', + '%d task' => '%d tugas', + 'Task ID' => 'ID Tugas', + 'Assign automatically a color when due date is expired' => 'Berikan warna secara automatik apabila tarikh tamat tempoh telah tamat', + 'Total score in this column across all swimlanes' => 'Jumlah markah dalam lajur ini di semua lajur', + 'HRK - Kuna' => 'HRK - Kuna', + 'ARS - Argentine Peso' => 'ARS - Peso Argentina', + 'COP - Colombian Peso' => 'COP - Peso Colombia', + '%d groups' => '%d kumpulan', + '%d group' => '%d kumpulan', + 'Group ID' => 'ID Kumpulan', + 'External ID' => 'ID Luaran', + '%d users' => '%d pengguna', + '%d user' => '%d pengguna', + 'Hide subtasks' => 'Sembunyikan subtugas', + 'Show subtasks' => 'Tunjukkan subtugas', + 'Authentication Parameters' => 'Parameter Pengesahan', + 'API Access' => 'Akses API', + 'No users found.' => 'Tiada pengguna ditemui.', + 'User ID' => 'ID Pengguna', + 'Notifications are activated' => 'Pemberitahuan diaktifkan', + 'Notifications are disabled' => 'Pemberitahuan dilumpuhkan', + 'User disabled' => 'Pengguna dilumpuhkan', + '%d notifications' => '%d pemberitahuan', + '%d notification' => '%d pemberitahuan', + 'There is no external integration installed.' => 'Tiada integrasi luaran dipasang.', + 'You are not allowed to update tasks assigned to someone else.' => 'Anda tidak dibenarkan untuk mengemas kini tugas yang diberikan kepada orang lain.', + 'You are not allowed to change the assignee.' => 'Anda tidak dibenarkan untuk menukar penerima tugas.', + 'Task suppression is not permitted' => 'Penindasan tugas tidak dibenarkan', + 'Changing assignee is not permitted' => 'Menukar penerima tugas tidak dibenarkan', + 'Update only assigned tasks is permitted' => 'Kemas kini hanya tugas yang diberikan dibenarkan', + 'Only for tasks assigned to the current user' => 'Hanya untuk tugas yang diberikan kepada pengguna semasa', + 'My projects' => 'Projek saya', + 'You are not a member of any project.' => 'Anda bukan ahli mana-mana projek.', + 'My subtasks' => 'Subtugas saya', + '%d subtasks' => '%d subtugas', + '%d subtask' => '%d subtugas', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Memindahkan tugas antara lajur tersebut hanya dibenarkan untuk tugas yang diberikan kepada pengguna semasa', + '[DUPLICATE]' => '[DUPLIKAT]', + 'DKK - Danish Krona' => 'DKK - Krona Denmark', + 'Remove user from group' => 'Alih keluar pengguna dari kumpulan', + 'Assign the task to its creator' => 'Berikan tugas kepada penciptanya', + 'This task was sent by email to "%s" with subject "%s".' => 'Tugas ini telah dihantar melalui e-mel kepada "%s" dengan subjek "%s".', + 'Predefined Email Subjects' => 'Subjek E-mel Pratakrif', + 'Write one subject by line.' => 'Tulis satu subjek setiap baris.', + 'Create another link' => 'Cipta pautan lain', + 'BRL - Brazilian Real' => 'BRL - Real Brazil', + 'Add a new Kanboard task' => 'Tambah tugas Kanboard baharu', + 'Subtask not started' => 'Subtugas belum dimulakan', + 'Subtask currently in progress' => 'Subtugas sedang dijalankan', + 'Subtask completed' => 'Subtugas selesai', + 'Subtask added successfully.' => 'Subtugas berjaya ditambahkan.', + '%d subtasks added successfully.' => '%d subtugas berjaya ditambahkan.', + 'Enter one subtask by line.' => 'Masukkan satu subtugas setiap baris.', + 'Predefined Contents' => 'Kandungan Pratakrif', + 'Predefined contents' => 'Kandungan pratakrif', + 'Predefined Task Description' => 'Penerangan Tugas Pratakrif', + 'Do you really want to remove this template? "%s"' => 'Adakah anda benar-benar mahu mengalih keluar templat ini? "%s"', + 'Add predefined task description' => 'Tambah penerangan tugas pratakrif', + 'Predefined Task Descriptions' => 'Penerangan Tugas Pratakrif', + 'Template created successfully.' => 'Templat berjaya dicipta.', + 'Unable to create this template.' => 'Tidak dapat mencipta templat ini.', + 'Template updated successfully.' => 'Templat berjaya dikemas kini.', + 'Unable to update this template.' => 'Tidak dapat mengemas kini templat ini.', + 'Template removed successfully.' => 'Templat berjaya dialih keluar.', + 'Unable to remove this template.' => 'Tidak dapat mengalih keluar templat ini.', + 'Template for the task description' => 'Templat untuk penerangan tugas', + 'The start date is greater than the end date' => 'Tarikh mula lebih besar daripada tarikh tamat', + 'Tags must be separated by a comma' => 'Tag mestilah dipisahkan oleh koma', + 'Only the task title is required' => 'Hanya tajuk tugas diperlukan', + 'Creator Username' => 'Nama Pengguna Pencipta', + 'Color Name' => 'Nama Warna', + 'Column Name' => 'Nama Lajur', + 'Swimlane Name' => 'Nama Lajur', + 'Time Estimated' => 'Anggaran Masa', + 'Time Spent' => 'Masa Dihabiskan', + 'External Link' => 'Pautan Luaran', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Ciri ini membolehkan suapan iCal, suapan RSS dan paparan papan awam.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Hentikan pemasa semua subtugas apabila memindahkan tugas ke lajur lain', + 'Subtask Title' => 'Tajuk Subtugas', + 'Add a subtask and activate the timer when moving a task to another column' => 'Tambah subtugas dan aktifkan pemasa apabila memindahkan tugas ke lajur lain', + 'days' => 'hari', + 'minutes' => 'minit', + 'seconds' => 'saat', + 'Assign automatically a color when preset start date is reached' => 'Berikan warna secara automatik apabila tarikh mula pratetap dicapai', + 'Move the task to another column once a predefined start date is reached' => 'Pindahkan tugas ke lajur lain apabila tarikh mula pratakrif dicapai', + 'This task is now linked to the task %s with the relation "%s"' => 'Tugas ini kini dipautkan ke tugas %s dengan hubungan "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'Pautan dengan hubungan "%s" ke tugas %s telah dialih keluar', + 'Custom Filter:' => 'Penapis Tersuai:', + 'Unable to find this group.' => 'Tidak dapat mencari kumpulan ini.', + '%s moved the task #%d to the column "%s"' => '%s memindahkan tugas #%d ke lajur "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => '%s memindahkan tugas #%d ke kedudukan %d dalam lajur "%s"', + '%s moved the task #%d to the swimlane "%s"' => '%s memindahkan tugas #%d ke lajur "%s"', + '%sh spent' => '%sj dihabiskan', + '%sh estimated' => '%sj dianggarkan', + 'Select All' => 'Pilih Semua', + 'Unselect All' => 'Nyahpilih Semua', + 'Apply action' => 'Mohon tindakan', + 'Move selected tasks to another column or swimlane' => 'Pindahkan tugas terpilih ke lajur atau lajur lain', + 'Edit tasks in bulk' => 'Edit tugas secara pukal', + 'Choose the properties that you would like to change for the selected tasks.' => 'Pilih sifat yang anda ingin ubah untuk tugas terpilih.', + 'Configure this project' => 'Konfigurasi projek ini', + 'Start now' => 'Mula sekarang', + '%s removed a file from the task #%d' => '%s mengalih keluar fail dari tugas #%d', + 'Attachment removed from task #%d: %s' => 'Lampiran dialih keluar dari tugas #%d: %s', + 'No color' => 'Tiada warna', + 'Attachment removed "%s"' => 'Lampiran dialih keluar "%s"', + '%s removed a file from the task %s' => '%s mengalih keluar fail dari tugas %s', + 'Move the task to another swimlane when assigned to a user' => 'Pindahkan tugas ke lajur lain apabila diberikan kepada pengguna', + 'Destination swimlane' => 'Lajur destinasi', + 'Assign a category when the task is moved to a specific swimlane' => 'Berikan kategori apabila tugas dipindahkan ke lajur tertentu', + 'Move the task to another swimlane when the category is changed' => 'Pindahkan tugas ke lajur lain apabila kategori ditukar', + 'Reorder this column by priority (ASC)' => 'Susun semula lajur ini mengikut keutamaan (ASC)', + 'Reorder this column by priority (DESC)' => 'Susun semula lajur ini mengikut keutamaan (DESC)', + 'Reorder this column by assignee and priority (ASC)' => 'Susun semula lajur ini mengikut penerima dan keutamaan (ASC)', + 'Reorder this column by assignee and priority (DESC)' => 'Susun semula lajur ini mengikut penerima dan keutamaan (DESC)', + 'Reorder this column by assignee (A-Z)' => 'Susun semula lajur ini mengikut penerima (A-Z)', + 'Reorder this column by assignee (Z-A)' => 'Susun semula lajur ini mengikut penerima (Z-A)', + 'Reorder this column by due date (ASC)' => 'Susun semula lajur ini mengikut tarikh tamat tempoh (ASC)', + 'Reorder this column by due date (DESC)' => 'Susun semula lajur ini mengikut tarikh tamat tempoh (DESC)', + 'Reorder this column by id (ASC)' => 'Susun semula lajur ini mengikut id (ASC)', + 'Reorder this column by id (DESC)' => 'Susun semula lajur ini mengikut id (DESC)', + '%s moved the task #%d "%s" to the project "%s"' => '%s memindahkan tugas #%d "%s" ke projek "%s"', + 'Task #%d "%s" has been moved to the project "%s"' => 'Tugas #%d "%s" telah dipindahkan ke projek "%s"', + 'Move the task to another column when the due date is less than a certain number of days' => 'Pindahkan tugas ke lajur lain apabila tarikh tamat tempoh kurang daripada bilangan hari tertentu', + 'Automatically update the start date when the task is moved away from a specific column' => 'Kemas kini tarikh mula secara automatik apabila tugas dialihkan dari lajur tertentu', + 'HTTP Client:' => 'Klien HTTP:', + 'Assigned' => 'Ditugaskan', + 'Task limits apply to each swimlane individually' => 'Had tugas dikenakan kepada setiap lajur secara individu', + 'Column task limits apply to each swimlane individually' => 'Had tugas lajur dikenakan kepada setiap lajur secara individu', + 'Column task limits are applied to each swimlane individually' => 'Had tugas lajur dikenakan kepada setiap lajur secara individu', + 'Column task limits are applied across swimlanes' => 'Had tugas lajur dikenakan di seluruh lajur', + 'Task limit: ' => 'Had tugas: ', + 'Change to global tag' => 'Tukar kepada tag global', + 'Do you really want to make the tag "%s" global?' => 'Adakah anda benar-benar mahu menjadikan tag "%s" global?', + 'Enable global tags for this project' => 'Dayakan tag global untuk projek ini', + 'Group membership(s):' => 'Keahlian kumpulan:', + '%s is a member of the following group(s): %s' => '%s adalah ahli kumpulan berikut: %s', + '%d/%d group(s) shown' => '%d/%d kumpulan ditunjukkan', + 'Subtask creation or modification' => 'Penciptaan atau pengubahsuaian subtugas', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Berikan tugas kepada pengguna tertentu apabila tugas dipindahkan ke lajur tertentu', + 'Comment' => 'Komen', + 'Collapse vertically' => 'Runtuhkan secara menegak', + 'Expand vertically' => 'Kembangkan secara menegak', + 'MXN - Mexican Peso' => 'MXN - Peso Meksiko', + 'Estimated vs actual time per column' => 'Anggaran vs masa sebenar setiap lajur', + 'HUF - Hungarian Forint' => 'HUF - Forint Hungary', + 'XBT - Bitcoin' => 'XBT - Bitcoin', + 'You must select a file to upload as your avatar!' => 'Anda mesti memilih fail untuk dimuat naik sebagai avatar anda!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'Fail yang anda muat naik bukan imej yang sah! (Hanya *.gif, *.jpg, *.jpeg dan *.png dibenarkan!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Tetapkan tarikh tamat tempoh secara automatik apabila tugas dialihkan dari lajur tertentu', + 'No other projects found.' => 'Tiada projek lain ditemui.', + 'Tasks copied successfully.' => 'Tugas berjaya disalin.', + 'Unable to copy tasks.' => 'Tidak dapat menyalin tugas.', + 'Theme' => 'Tema', + 'Theme:' => 'Tema:', + 'Light theme' => 'Tema cerah', + 'Dark theme' => 'Tema gelap', + 'Automatic theme - Sync with system' => 'Tema automatik - Segerak dengan sistem', + 'Application managers or more' => 'Pengurus aplikasi atau lebih', + 'Administrators' => 'Pentadbir', + 'Visibility:' => 'Kebolehlihatan:', + 'Standard users' => 'Pengguna standard', + 'Visibility is required' => 'Kebolehlihatan diperlukan', + 'The visibility should be an app role' => 'Kebolehlihatan hendaklah peranan aplikasi', + 'Reply' => 'Balas', + '%s wrote: ' => '%s menulis: ', + 'Number of visible tasks in this column and swimlane' => 'Bilangan tugas yang kelihatan dalam lajur dan laluan renang ini', + 'Number of tasks in this swimlane' => 'Bilangan tugas dalam laluan renang ini', + 'Unable to find another subtask in progress, you can close this window.' => 'Tidak dapat mencari subtugas lain dalam proses, anda boleh menutup tetingkap ini.', + 'This theme is invalid' => 'Tema ini tidak sah', + 'This role is invalid' => 'Peranan ini tidak sah', + 'This timezone is invalid' => 'Zon waktu ini tidak sah', + 'This language is invalid' => 'Bahasa ini tidak sah', + 'This URL is invalid' => 'URL ini tidak sah', + 'Date format invalid' => 'Format tarikh tidak sah', + 'Time format invalid' => 'Format masa tidak sah', + 'Invalid Mail transport' => 'Pengangkutan Mel tidak sah', + 'Color invalid' => 'Warna tidak sah', + 'This value must be greater or equal to %d' => 'Nilai ini mestilah lebih besar daripada atau sama dengan %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Tambah BOM di permulaan fail (diperlukan untuk Microsoft Excel)', + 'Just add these tag(s)' => 'Hanya tambah tag ini', + 'Remove internal link(s)' => 'Buang pautan dalaman', + 'Import tasks from another project' => 'Import tugas dari projek lain', + 'Select the project to copy tasks from' => 'Pilih projek untuk menyalin tugas dari', + 'The total maximum allowed attachments size is %sB.' => 'Jumlah saiz lampiran maksimum yang dibenarkan ialah %sB.', + 'Add attachments' => 'Tambah lampiran', + 'Task #%d "%s" is overdue' => 'Tugas #%d « %s » telah tamat tempoh', + 'Enable notifications by default for all new users' => 'Dayakan pemberitahuan secara lalai untuk semua pengguna baharu', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Tetapkan tugasan kepada penciptanya untuk lajur tertentu jika tiada penerima tugasan ditetapkan secara manual', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Tetapkan tugasan kepada pengguna yang log masuk apabila lajur berubah ke lajur yang ditetapkan jika tiada pengguna ditetapkan', +]; diff --git a/app/Locale/nb_NO/translations.php b/app/Locale/nb_NO/translations.php new file mode 100644 index 0000000..44dfea1 --- /dev/null +++ b/app/Locale/nb_NO/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => '.', + 'None' => 'Ingen', + 'Edit' => 'Rediger', + 'Remove' => 'Fjern', + 'Yes' => 'Ja', + 'No' => 'Nei', + 'cancel' => 'avbryt', + 'or' => 'eller', + 'Yellow' => 'Gul', + 'Blue' => 'Blå', + 'Green' => 'Grønn', + 'Purple' => 'Lilla', + 'Red' => 'Rød', + 'Orange' => 'Orange', + 'Grey' => 'Grå', + 'Brown' => 'Brun', + 'Deep Orange' => 'Mørk orange', + 'Dark Grey' => 'Mørk grå', + 'Pink' => 'Rosa', + 'Teal' => 'Sjøgrønn', + 'Cyan' => 'Cyan', + 'Lime' => 'Lime', + 'Light Green' => 'Lys grønn', + 'Amber' => 'Ravgul', + 'Save' => 'Lagre', + 'Login' => 'Logg inn', + 'Official website:' => 'Offisielt nettsted:', + 'Unassigned' => 'Ikke tildelt', + 'View this task' => 'Se denne oppgaven', + 'Remove user' => 'Fjern bruker', + 'Do you really want to remove this user: "%s"?' => 'Er du sikker på at du vil fjerne brukeren: "%s"?', + 'All users' => 'Alle brukere', + 'Username' => 'Brukernavn', + 'Password' => 'Passord', + 'Administrator' => 'Administrator', + 'Sign in' => 'Logg inn', + 'Users' => 'Brukere', + 'Forbidden' => 'Ikke tillatt', + 'Access Forbidden' => 'Adgang ikke tillatt', + 'Edit user' => 'Rediger bruker', + 'Logout' => 'Logg ut', + 'Bad username or password' => 'Feil brukernavn eller passord', + 'Edit project' => 'Endre prosjekt', + 'Name' => 'Navn', + 'Projects' => 'Prosjekter', + 'No project' => 'Ingen prosjekter', + 'Project' => 'Prosjekt', + 'Status' => 'Status', + 'Tasks' => 'Oppgaver', + 'Board' => 'Tavle', + 'Actions' => 'Handlinger', + 'Inactive' => 'Inaktiv', + 'Active' => 'Aktiv', + 'Unable to update this board.' => 'Ikke mulig å oppdatere tavlesiden', + 'Disable' => 'Slå av', + 'Enable' => 'Slå på', + 'New project' => 'Nytt prosjekt', + 'Do you really want to remove this project: "%s"?' => 'Er du sikker på at du vil du slette dette prosjektet: "%s"?', + 'Remove project' => 'Slett prosjekt', + 'Edit the board for "%s"' => 'Endre tavlesiden for "%s"', + 'Add a new column' => 'Legg til ny kolonne', + 'Title' => 'Tittel', + 'Assigned to %s' => 'Tildelt: %s', + 'Remove a column' => 'Fjern en kolonne', + 'Unable to remove this column.' => 'Kan ikke fjerne kolonnen', + 'Do you really want to remove this column: "%s"?' => 'Er du sikker på at du vil fjerne denne kolonnen: "%s"?', + 'Settings' => 'Innstillinger', + 'Application settings' => 'Applikasjonsinnstillinger', + 'Language' => 'Språk', + 'Webhook token:' => 'Webhook token:', + 'API token:' => 'API token:', + 'Database size:' => 'Databasestørrelse:', + 'Download the database' => 'Last ned databasen', + 'Optimize the database' => 'Optimaliser databasen', + '(VACUUM command)' => '(VACUUM kommando)', + '(Gzip compressed Sqlite file)' => '(Gzip-komprimert Sqlite fil)', + 'Close a task' => 'Lukk oppgave', + 'Column' => 'Kolonne', + 'Color' => 'Farge', + 'Assignee' => 'Tildelt', + 'Create another task' => 'Opprett en ny oppgave etter denne', + 'New task' => 'Ny oppgave', + 'Open a task' => 'Åpne oppgave', + 'Do you really want to open this task: "%s"?' => 'Er du sikker på at du vil åpne denne oppgaven: "%s"?', + 'Back to the board' => 'Tilbake til tavlesiden', + 'There is nobody assigned' => 'Mangler tildeling', + 'Column on the board:' => 'Kolonne:', + 'Close this task' => 'Lukk oppgave', + 'Open this task' => 'Åpne oppgave', + 'There is no description.' => 'Ingen beskrivelse.', + 'Add a new task' => 'Lag ny oppgave', + 'The username is required' => 'Brukernavn er påkrevd', + 'The maximum length is %d characters' => 'Den maksimale lengden er %d tegn', + 'The minimum length is %d characters' => 'Den minimale lengden er %d tegn', + 'The password is required' => 'Passord påkrevet', + 'This value must be an integer' => 'Verdien må være et heltall', + 'The username must be unique' => 'Brukernavnet må være unikt', + 'The user id is required' => 'Bruker-id påkrevet', + 'Passwords don\'t match' => 'Passordene stemmer ikke overens', + 'The confirmation is required' => 'Bekreftelse påkrevet', + 'The project is required' => 'Prosjekt påkrevet', + 'The id is required' => 'ID påkrevet', + 'The project id is required' => 'Prosjekt-id påkrevet', + 'The project name is required' => 'Prosjektnavn påkrevet', + 'The title is required' => 'Tittel påkrevet', + 'Settings saved successfully.' => 'Innstillinger lagret.', + 'Unable to save your settings.' => 'Feil ved lagring av innstillinger.', + 'Database optimization done.' => 'Databaseoptimalisering er fullført.', + 'Your project has been created successfully.' => 'Prosjektet er opprettet.', + 'Unable to create your project.' => 'Prosjektet kunne ikke opprettes', + 'Project updated successfully.' => 'Prosjektet er oppdatert.', + 'Unable to update this project.' => 'Prosjektet kunne ikke oppdateres.', + 'Unable to remove this project.' => 'Prosjektet kunne ikke slettes.', + 'Project removed successfully.' => 'Prosjektet er slettet.', + 'Project activated successfully.' => 'Prosjektet er aktivert.', + 'Unable to activate this project.' => 'Prosjektet kunne ikke aktiveres.', + 'Project disabled successfully.' => 'Prosjektet er deaktiveret.', + 'Unable to disable this project.' => 'Prosjektet kunne ikke deaktiveres.', + 'Unable to open this task.' => 'Oppgaven kunne ikke åpnes.', + 'Task opened successfully.' => 'Oppgaven er åpnet.', + 'Unable to close this task.' => 'Oppgaven kunne ikke åpnes.', + 'Task closed successfully.' => 'Oppgaven er lukket.', + 'Unable to update your task.' => 'Oppgaven kunne ikke oppdateres.', + 'Task updated successfully.' => 'Oppgaven er oppdatert.', + 'Unable to create your task.' => 'Oppgave kunne ikke opprettes.', + 'Task created successfully.' => 'Oppgaven er opprettet.', + 'User created successfully.' => 'Brukeren er opprettet.', + 'Unable to create your user.' => 'Brukeren kunne ikke opprettes.', + 'User updated successfully.' => 'Brukeren er oppdatert', + 'User removed successfully.' => 'Brukeren er fjernet.', + 'Unable to remove this user.' => 'Brukeren kunne ikke slettes.', + 'Board updated successfully.' => 'Hovedsiden er oppdatert.', + 'Ready' => 'Klar', + 'Backlog' => 'Backlog', + 'Work in progress' => 'Under arbeid', + 'Done' => 'Utført', + 'Application version:' => 'Versjon:', + 'Id' => 'ID', + 'Public link' => 'Offentligt lenke', + 'Timezone' => 'Tidssone', + 'Sorry, I didn\'t find this information in my database!' => 'Denne informasjonen kunne ikke finnes i databasen!', + 'Page not found' => 'Siden er ikke funnet', + 'Complexity' => 'Kompleksitet', + 'Task limit' => 'Oppgavebegrensning', + 'Task count' => 'Antall oppgaver', + 'User' => 'Bruker', + 'Comments' => 'Kommentarer', + 'Comment is required' => 'Kommentar må legges inn', + 'Comment added successfully.' => 'Kommentaren er lagt til.', + 'Unable to create your comment.' => 'Din kommentar kunne ikke opprettes.', + 'Due Date' => 'Frist', + 'Invalid date' => 'Ugyldig dato', + 'Automatic actions' => 'Automatiske handlinger', + 'Your automatic action has been created successfully.' => 'Din automatiske handling er opprettet.', + 'Unable to create your automatic action.' => 'Din automatiske handling kunne ikke opprettes.', + 'Remove an action' => 'Fjern en handling', + 'Unable to remove this action.' => 'Handlingen kunne ikke fjernes.', + 'Action removed successfully.' => 'Handlingen er fjernet.', + 'Automatic actions for the project "%s"' => 'Automatiske handlinger for prosjektet "%s"', + 'Add an action' => 'Legg til en handling', + 'Event name' => 'Hendelsehet', + 'Action' => 'Handling', + 'Event' => 'Hendelse', + 'When the selected event occurs execute the corresponding action.' => 'Når den valgte hendelsen oppstår, utfør tilsvarende handling.', + 'Next step' => 'Neste', + 'Define action parameters' => 'Definer handlingsparametre', + 'Do you really want to remove this action: "%s"?' => 'Vil du slette denne handlingen: "%s"?', + 'Remove an automatic action' => 'Fjern en automatisk handling', + 'Assign the task to a specific user' => 'Tildel oppgaven til en bestemt bruker', + 'Assign the task to the person who does the action' => 'Tildel oppgaven til den person, som utfører handlingen', + 'Duplicate the task to another project' => 'Kopier oppgaven til et annet prosjekt', + 'Move a task to another column' => 'Flytt oppgaven til en annen kolonne', + 'Task modification' => 'Oppgaveendring', + 'Task creation' => 'Oppgaveoprettelse', + 'Closing a task' => 'Lukke en oppgave', + 'Assign a color to a specific user' => 'Tildel en farge til en bestemt bruker', + 'Position' => 'Posisjon', + 'Duplicate to project' => 'Kopier til et annet prosjekt', + 'Duplicate' => 'Kopier', + 'Link' => 'Link', + 'Comment updated successfully.' => 'Kommentar oppdatert.', + 'Unable to update your comment.' => 'Din kommentar kunne ikke oppdateres.', + 'Remove a comment' => 'Fjern en kommentar', + 'Comment removed successfully.' => 'Kommentaren ble fjernet.', + 'Unable to remove this comment.' => 'Kommentaren kunne ikke fjernes.', + 'Do you really want to remove this comment?' => 'Vil du fjerne denne kommentaren?', + 'Current password for the user "%s"' => 'Aktivt passord for brukeren "%s"', + 'The current password is required' => 'Passord er påkrevet', + 'Wrong password' => 'Feil passord', + 'Unknown' => 'Ukjent', + 'Last logins' => 'Siste innlogging', + 'Login date' => 'Login dato', + 'Authentication method' => 'Godkjenningsmetode', + 'IP address' => 'IP Adresse', + 'User agent' => 'User Agent', + 'Persistent connections' => 'Nettforbindelser', + 'No session.' => 'Ingen sesjoner.', + 'Expiration date' => 'Utløpsdato', + 'Remember Me' => 'Husk meg', + 'Creation date' => 'Opprettelsesdato', + 'Everybody' => 'Alle', + 'Open' => 'Åpen', + 'Closed' => 'Lukket', + 'Search' => 'Søk', + 'Nothing found.' => 'Intet funnet.', + 'Due date' => 'Tidsfrist', + 'Description' => 'Beskrivelse', + '%d comments' => '%d kommentarer', + '%d comment' => '%d kommentar', + 'Email address invalid' => 'Ugyldig epost', + 'Your external account is not linked anymore to your profile.' => 'Din eksterne konto er ikke lenger lenket til profilen din', + 'Unable to unlink your external account.' => 'Kan ikke fjerne lenken til din eksterne konto', + 'External authentication failed' => 'Ekstern autentisering feilet', + 'Your external account is linked to your profile successfully.' => 'Den eksterne konto er lenket til profilen din', + 'Email' => 'E-post', + 'Task removed successfully.' => 'Oppgaven er fjernet.', + 'Unable to remove this task.' => 'Oppgaven kunne ikke fjernes.', + 'Remove a task' => 'Fjern en oppgave', + 'Do you really want to remove this task: "%s"?' => 'Vil du fjerne denne oppgaven: "%s"?', + 'Assign automatically a color based on a category' => 'Tildel automatisk en farge baseret for en kategori', + 'Assign automatically a category based on a color' => 'Tildel automatisk en kategori basert på en farve', + 'Task creation or modification' => 'Oppgaveopprettelse eller endring', + 'Category' => 'Kategori', + 'Category:' => 'Kategori:', + 'Categories' => 'Kategorier', + 'Your category has been created successfully.' => 'Kategorien er opprettet.', + 'This category has been updated successfully.' => 'Kategorien er oppdatert.', + 'Unable to update this category.' => 'Kategorien kunne ikke oppdateres.', + 'Remove a category' => 'Fjern en kategori', + 'Category removed successfully.' => 'Kategorien er fjernet.', + 'Unable to remove this category.' => 'Kategorien kunne ikke fjernes.', + 'Category modification for the project "%s"' => 'Endring av kategori for prosjektet "%s"', + 'Category Name' => 'Kategorinavn', + 'Add a new category' => 'Legg til ny kategori', + 'Do you really want to remove this category: "%s"?' => 'Vil du fjerne kategorien: "%s"?', + 'All categories' => 'Alle kategorier', + 'No category' => 'Ingen kategori', + 'The name is required' => 'Navnet er påkrevet', + 'Remove a file' => 'Fjern en fil', + 'Unable to remove this file.' => 'Filen kunne ikke fjernes.', + 'File removed successfully.' => 'Filen er fjernet.', + 'Attach a document' => 'Legg til et dokument', + 'Do you really want to remove this file: "%s"?' => 'Vil du fjerne filen: "%s"?', + 'Attachments' => 'Vedlegg', + 'Edit the task' => 'Rediger oppgaven', + 'Add a comment' => 'Legg til en kommentar', + 'Edit a comment' => 'Rediger en kommentar', + 'Summary' => 'Sammendrag', + 'Time tracking' => 'Tidsregistrering', + 'Estimate:' => 'Estimat:', + 'Spent:' => 'Brukt:', + 'Do you really want to remove this sub-task?' => 'Vil du fjerne denne deloppgaven?', + 'Remaining:' => 'Gjenværende:', + 'hours' => 'timer', + 'estimated' => 'estimat', + 'Sub-Tasks' => 'Deloppgave', + 'Add a sub-task' => 'Legg til en deloppgave', + 'Original estimate' => 'Estimert tidsbruk', + 'Create another sub-task' => 'Legg til en ny deloppgave', + 'Time spent' => 'Tidsforbruk', + 'Edit a sub-task' => 'Rediger en deloppgave', + 'Remove a sub-task' => 'Fjern en deloppgave', + 'The time must be a numeric value' => 'Tiden skal være en nummerisk erdi', + 'Todo' => 'Gjøremål', + 'In progress' => 'Under arbeid', + 'Sub-task removed successfully.' => 'Deloppgaven er fjernet.', + 'Unable to remove this sub-task.' => 'Deloppgaven kunne ikke fjernes.', + 'Sub-task updated successfully.' => 'Deloppgaven er opdateret.', + 'Unable to update your sub-task.' => 'Deloppgaven kunne ikke opdateres.', + 'Unable to create your sub-task.' => 'Deloppgaven kunne ikke oprettes.', + 'Maximum size: ' => 'Maksimum størrelse: ', + 'Display another project' => 'Vis annet prosjekt...', + 'Created by %s' => 'Opprettet av %s', + 'Tasks Export' => 'Oppgave eksport', + 'Start Date' => 'Startdato', + 'Execute' => 'KKjør', + 'Task Id' => 'Oppgave ID', + 'Creator' => 'Laget av', + 'Modification date' => 'Endringsdato', + 'Completion date' => 'Ferdigstillingsdato', + 'Clone' => 'Kopier', + 'Project cloned successfully.' => 'Prosjektet er kopiert.', + 'Unable to clone this project.' => 'Prosjektet kunne ikke kopieres', + 'Enable email notifications' => 'Aktiver epostvarslinger', + 'Task position:' => 'Posisjon:', + 'The task #%d has been opened.' => 'Oppgaven #%d er åpnet.', + 'The task #%d has been closed.' => 'Oppgaven #%d er lukket.', + 'Sub-task updated' => 'Deloppgaven er oppdatert', + 'Title:' => 'Tittel:', + 'Status:' => 'Status:', + 'Assignee:' => 'Ansvarlig:', + 'Time tracking:' => 'Tidsmåling:', + 'New sub-task' => 'Ny deloppgave', + 'New attachment added "%s"' => 'Nytt vedlegg er lagt tilet "%s"', + 'New comment posted by %s' => 'Ny kommentar fra %s', + 'New comment' => 'Ny kommentar', + 'Comment updated' => 'Kommentar oppdatert', + 'New subtask' => 'Ny deloppgave', + 'I only want to receive notifications for these projects:' => 'Jeg vil kun ha varslinger for disse prosjekter:', + 'view the task on Kanboard' => 'se oppgaven', + 'Public access' => 'Offentlig tilgang', + 'Disable public access' => 'Deaktiver offentlig tilgang', + 'Enable public access' => 'Aktiver offentlig tilgang', + 'Public access disabled' => 'Offentlig tilgang er deaktivert', + 'Move the task to another project' => 'Flytt oppgaven til et annet prosjekt', + 'Move to project' => 'Flytt til et annet prosjekt', + 'Do you really want to duplicate this task?' => 'Vil du kopiere denne oppgaven?', + 'Duplicate a task' => 'Kopier en oppgave', + 'External accounts' => 'Eksterne kontoer', + 'Account type' => 'Kontotype', + 'Local' => 'Lokal', + 'Remote' => 'Fjernstyrt', + 'Enabled' => 'Aktiv', + 'Disabled' => 'Deaktivert', + 'Login:' => 'Brukernavn', + 'Full Name:' => 'Navn:', + 'Email:' => 'Epost:', + 'Notifications:' => 'Varslinger:', + 'Notifications' => 'Varslinger', + 'Account type:' => 'Konto type:', + 'Edit profile' => 'Rediger profil', + 'Change password' => 'Endre passord', + 'Password modification' => 'Passordendring', + 'External authentications' => 'Ekstern godkjenning', + 'Never connected.' => 'Aldri innlogget.', + 'No external authentication enabled.' => 'Ingen eksterne godkjenninger aktiveret.', + 'Password modified successfully.' => 'Passord er endret.', + 'Unable to change the password.' => 'Passordet kuenne ikke endres.', + 'Change category' => 'Endre kategori', + '%s updated the task %s' => '%s oppdaterte oppgaven %s', + '%s opened the task %s' => '%s åpnet oppgaven %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s flyttet oppgaven %s til posisjon #%d i kolonne "%s"', + '%s moved the task %s to the column "%s"' => '%s flyttet oppgaven %s til kolonnen "%s"', + '%s created the task %s' => '%s opprettet oppgaven %s', + '%s closed the task %s' => '%s lukket oppgaven %s', + '%s created a subtask for the task %s' => '%s opprettet en deloppgave for oppgaven %s', + '%s updated a subtask for the task %s' => '%s oppdaterte en deloppgave for oppgaven %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Tildelt til %s med et estimat på %s/%sh', + 'Not assigned, estimate of %sh' => 'Ikke tildelt, estimert til %sh', + '%s updated a comment on the task %s' => '%s oppdaterte en kommentar til oppgaven %s', + '%s commented the task %s' => '%s har kommentert oppgaven %s', + '%s\'s activity' => 'Aktiviteter: %s', + 'RSS feed' => 'RSS feed', + '%s updated a comment on the task #%d' => '%s oppdaterte en kommentar til oppgaven #%d', + '%s commented on the task #%d' => '%s kommenterte oppgaven #%d', + '%s updated a subtask for the task #%d' => '%s oppdaterte en deloppgave til oppgaven #%d', + '%s created a subtask for the task #%d' => '%s opprettet en deloppgave til oppgaven #%d', + '%s updated the task #%d' => '%s oppdaterte oppgaven #%d', + '%s created the task #%d' => '%s opprettet oppgaven #%d', + '%s closed the task #%d' => '%s lukket oppgaven #%d', + '%s opened the task #%d' => '%s åpnet oppgaven #%d', + 'Activity' => 'Aktivitetslogg', + 'Default values are "%s"' => 'Standardverdier er "%s"', + 'Default columns for new projects (Comma-separated)' => 'Standard kolonne for nye prosjekter (kommaseparert)', + 'Task assignee change' => 'Endring av oppgaveansvarlig', + '%s changed the assignee of the task #%d to %s' => '%s endre ansvarlig for oppgaven #%d til %s', + '%s changed the assignee of the task %s to %s' => '%s endret ansvarlig for oppgaven %s til %s', + 'New password for the user "%s"' => 'Nytt passord for brukeren "%s"', + 'Choose an event' => 'Velg en hendelse', + 'Create a task from an external provider' => 'Oppret en oppgave fra en ekstern tilbyder', + 'Change the assignee based on an external username' => 'Endre ansvarlige baseret på et eksternt brukernavn', + 'Change the category based on an external label' => 'Endre kategorien basert på en ekstern etikett', + 'Reference' => 'Referanse', + 'Label' => 'Etikett', + 'Database' => 'Database', + 'About' => 'Om', + 'Database driver:' => 'Databasedriver:', + 'Board settings' => 'Tavlesiden', + 'Webhook settings' => 'Webhook innstillinger', + 'Reset token' => 'Resette token', + 'API endpoint:' => 'API endpoint:', + 'Refresh interval for personal board' => 'Oppdateringsintervall for privat tavleside', + 'Refresh interval for public board' => 'Oppdateringsintervall for offentlig tavleside', + 'Task highlight period' => 'Periode for fremheving av oppgaver', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Periode for å fastslå en oppgave nylig er endret (0 for å deaktivere, 2 dager er standard)', + 'Frequency in second (60 seconds by default)' => 'Frekevens i sekunder (60 sekunder som standard)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frekvens i sekunder (0 for å deaktivere denne funksjonen, 10 sekunder er standard)', + 'Application URL' => 'Applikasjon-URL', + 'Token regenerated.' => 'Token regenerert.', + 'Date format' => 'Datoformat', + 'ISO format is always accepted, example: "%s" and "%s"' => 'ISO format, eksempel: "%s" og "%s"', + 'New personal project' => 'Nytt privat prosjekt', + 'This project is personal' => 'Dette projektet er privat', + 'Add' => 'Legg til', + 'Start date' => 'Startdato', + 'Time estimated' => 'Tid estimert', + 'There is nothing assigned to you.' => 'Ingen elementer er tildelt deg.', + 'My tasks' => 'Mine oppgaver', + 'Activity stream' => 'Aktivitetslogg', + 'Dashboard' => 'Hovedsiden', + 'Confirmation' => 'Bekreftelse', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Create a comment from an external provider' => 'Opprett en kommentar fra en ekstern tilbyder', + 'Project management' => 'Prosjektinnstillinger', + 'Columns' => 'Kolonner', + 'Task' => 'Oppgave', + 'Percentage' => 'Prosent', + 'Number of tasks' => 'Antall oppgaver', + 'Task distribution' => 'Kolonnefordeling', + 'Analytics' => 'Analyser', + 'Subtask' => 'Deloppgave', + 'User repartition' => 'Brukerfordeling', + 'Clone this project' => 'Kopier dette prosjektet', + 'Column removed successfully.' => 'Kolonne flyttet', + 'Not enough data to show the graph.' => 'Ikke nok data til å vise grafen', + 'Previous' => 'Forrige', + 'The id must be an integer' => 'ID må være heltall', + 'The project id must be an integer' => 'ProsjektID må være et heltall', + 'The status must be an integer' => 'Status må være et heltall', + 'The subtask id is required' => 'Du må angi ID for deloppgaven', + 'The subtask id must be an integer' => 'ID for deloppgaven må være et heltall', + 'The task id is required' => 'OppgaveID er påkrevet', + 'The task id must be an integer' => 'OppgaveID må være et heltall', + 'The user id must be an integer' => 'BrukerID må være et heltall', + 'This value is required' => 'Denne verdien er påkrevet', + 'This value must be numeric' => 'Verdien må være numerisk', + 'Unable to create this task.' => 'Kunne ikke opprette oppgaven', + 'Cumulative flow diagram' => 'Kumulativt flytdiagram', + 'Daily project summary' => 'Daglig prosjektsammendrag', + 'Daily project summary export' => 'Daglig prosjektsammendrag', + 'Exports' => 'Eksporter', + 'This export contains the number of tasks per column grouped per day.' => 'Denne eksporten inneholde antall oppgaver pr. kolonne gruppert pr. dag', + 'Active swimlanes' => 'Aktive svømmebaner', + 'Add a new swimlane' => 'Legg til en ny svømmebane', + 'Default swimlane' => 'Standard svømmebane', + 'Do you really want to remove this swimlane: "%s"?' => 'Er du sikker på at du vil fjerne svømmebane: "%s"?', + 'Inactive swimlanes' => 'Deaktiverte svømmebaner', + 'Remove a swimlane' => 'Fjern en svømmebane', + 'Swimlane modification for the project "%s"' => 'Svømmebane endrei i prosjektet "%s"', + 'Swimlane removed successfully.' => 'Svømmebane fjernet', + 'Swimlanes' => 'Svømmebaner', + 'Swimlane updated successfully.' => 'Svømmebane oppdatert', + 'Unable to remove this swimlane.' => 'Kunne ikke fjerne svømmebanen', + 'Unable to update this swimlane.' => 'Kunne ikke endre svømmebanen', + 'Your swimlane has been created successfully.' => 'Svømmebanen er opprettet!', + 'Example: "Bug, Feature Request, Improvement"' => 'Eksempel: Avvik, forbedringsforslag, ny funksjonalitet', + 'Default categories for new projects (Comma-separated)' => 'Standardkategorier for nye prosjekter (kommaseparert)', + 'Integrations' => 'Integrasjoner', + 'Integration with third-party services' => 'Integrasjoner med tredjeparts-tjenester', + 'Subtask Id' => 'Deloppgave ID', + 'Subtasks' => 'Deloppgaver', + 'Subtasks Export' => 'Eksporter deloppgaver', + 'Task Title' => 'Oppgavetittel', + 'Untitled' => 'Uten navn', + 'Application default' => 'Standardinstilling', + 'Language:' => 'Språk', + 'Timezone:' => 'Tidssone', + 'All columns' => 'Alle kolonner', + 'Next' => 'Neste', + '#%d' => '#%d', + 'All swimlanes' => 'Alle svømmebaner', + 'All colors' => 'Alle farger', + 'Moved to column %s' => 'Flyttet til kolonne %s', + 'User dashboard' => 'Brukerens hovedside', + 'Allow only one subtask in progress at the same time for a user' => 'Tillat kun en aktiv deloppgave for brukerne', + 'Edit column "%s"' => 'Endre kolonne "%s"', + 'Select the new status of the subtask: "%s"' => 'Sett ny status for deloppgave: "%s"', + 'Subtask timesheet' => 'Tidsskjema for deloppgaver', + 'There is nothing to show.' => 'Ingen data å vise', + 'Time Tracking' => 'Tidsregistrering', + 'You already have one subtask in progress' => 'Du har allerede en aktiv deloppgave i dette prosjektet', + 'Which parts of the project do you want to duplicate?' => 'Hvilke deler av dette prosjektet ønsker du å kopiere?', + 'Disallow login form' => 'Deaktiver innloggingsbilde', + 'Start' => 'Start', + 'End' => 'Slutt', + 'Task age in days' => 'Antall dager siden siden oppgaven ble opprettet', + 'Days in this column' => 'Antall dager siden oppgaven ble lagt i denne kolonnen', + '%dd' => '%dd', + 'Add a new link' => 'Legg til en ny relasjon', + 'Do you really want to remove this link: "%s"?' => 'Ønsker du virkelig å fjerne lenken: "%s"', + 'Do you really want to remove this link with task #%d?' => 'Ønsker du virkelig å fjerne lenken oppgaven #%d?', + 'Field required' => 'Feltet må fylles ut', + 'Link added successfully.' => 'Ny relasjon er lagt til', + 'Link updated successfully.' => 'Relasjon er oppdatert', + 'Link removed successfully.' => 'Relasjon er fjernet', + 'Link labels' => 'Relasjoner', + 'Link modification' => 'Relasjonsmodifisering', + 'Opposite label' => 'Etikett for relatert motsatt oppgave', + 'Remove a link' => 'Fjern relasjon', + 'The labels must be different' => 'Titlene må være ulike', + 'There is no link.' => 'Ingen lenker funnet', + 'This label must be unique' => 'Tittelen må være unik', + 'Unable to create your link.' => 'Kunne ikke opprette lenken.', + 'Unable to update your link.' => 'Kunne ikke endre lenken.', + 'Unable to remove this link.' => 'Kunne ikke fjerne lenken', + 'relates to' => 'relatert til', + 'blocks' => 'blokkerer', + 'is blocked by' => 'blokkeres av', + 'duplicates' => 'kopierer', + 'is duplicated by' => 'er en kopi av', + 'is a child of' => 'er en underordnet oppgave av', + 'is a parent of' => 'er en overordnet oppgave for', + 'targets milestone' => 'milepel', + 'is a milestone of' => 'er en milepel for', + 'fixes' => 'løser', + 'is fixed by' => 'løses av', + 'This task' => 'Denne oppgaven', + '<1h' => '<1t', + '%dh' => '%dt', + 'Expand tasks' => 'Utvid oppgavevisning', + 'Collapse tasks' => 'Komprimer oppgavevisning', + 'Expand/collapse tasks' => 'Utvide/komprimere oppgavevisning', + 'Close dialog box' => 'Lukk dialogvindu', + 'Submit a form' => 'Send inn skjema', + 'Board view' => 'Tavlevisning', + 'Keyboard shortcuts' => 'Hurtigtaster', + 'Open board switcher' => 'Åpne tavleskifter', + 'Application' => 'Applikasjon', + 'Compact view' => 'Kompakt visning', + 'Horizontal scrolling' => 'Bla horisontalt', + 'Compact/wide view' => 'Kompakt/bred visning', + 'Currency' => 'Valuta', + 'Personal project' => 'Privat prosjekt', + 'AUD - Australian Dollar' => 'AUD - Australske dollar', + 'CAD - Canadian Dollar' => 'CAD - Kanadiske dollar', + 'CHF - Swiss Francs' => 'CHF - Sveitsiske frank', + 'Custom Stylesheet' => 'Egendefinert stilark', + 'EUR - Euro' => 'EUR - Euro', + 'GBP - British Pound' => 'GBP - Britiske pund', + 'INR - Indian Rupee' => 'INR - Indiske rupi', + 'JPY - Japanese Yen' => 'JPY - Japanske yen', + 'NZD - New Zealand Dollar' => 'NZD - New Zealand Dollar', + 'PEN - Peruvian Sol' => 'PEN - Peruansk sol', + 'RSD - Serbian dinar' => 'RSD - Serbiske dinarer', + 'CNY - Chinese Yuan' => 'CNY - Kinesiske yuan', + 'USD - US Dollar' => 'USD - Amerikanske dollar', + 'VES - Venezuelan Bolívar' => 'VES - Venezuelansk bolívar', + 'Destination column' => 'Ny kolonne', + 'Move the task to another column when assigned to a user' => 'Flytt oppgaven til en annen kolonne når den er tildelt en bruker', + 'Move the task to another column when assignee is cleared' => 'Flytt oppgaven til en annen kolonne når ppgavetildeling fjernes ', + 'Source column' => 'Opprinnelig kolonne', + 'Transitions' => 'Statusendringer', + 'Executer' => 'Utfører', + 'Time spent in the column' => 'Medgått tid i kolonnen', + 'Task transitions' => 'Oppgavebevegelser', + 'Task transitions export' => 'Eksportere oppgavebevegelser', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Rapporten inneholder alle kolonnebevegelser for hver oppgave, med dato, bruker og tidsforbruk for hver bevegelse', + 'Currency rates' => 'Valutakurser', + 'Rate' => 'Kurs', + 'Change reference currency' => 'Endre refereransevaluta', + 'Reference currency' => 'Referansevaluta', + 'The currency rate has been added successfully.' => 'Referansevaluta er endret', + 'Unable to add this currency rate.' => 'Kan ikke legge til denne valutakursen', + 'Webhook URL' => 'Webhook URL', + '%s removed the assignee of the task %s' => '%s fjernet ansvarlige for oppgaven %s', + 'Information' => 'Informasjon', + 'Check two factor authentication code' => 'Kontrollerer kode for tofaktor autensitering', + 'The two factor authentication code is not valid.' => 'Autentiseringskoden er ikke gyldig', + 'The two factor authentication code is valid.' => 'Autentiseringskoden er gyldig', + 'Code' => 'Kode', + 'Two factor authentication' => 'Tofaktor autentisering', + 'This QR code contains the key URI: ' => 'Denne QR-koden inneholder nøkkel URI: ', + 'Check my code' => 'Sjekk koden min', + 'Secret key: ' => 'Hemmelig nøkkel', + 'Test your device' => 'Test din enhet', + 'Assign a color when the task is moved to a specific column' => 'Endre til en valgt farge hvis en oppgave flyttes til en spesifikk kolonne', + '%s via Kanboard' => '%s via Kanboard', + 'Burndown chart' => 'Burndown diagram', + 'This chart show the task complexity over the time (Work Remaining).' => 'Dette diagrammet viser oppgavekopleksitet over tid (gjenstående arbeid)', + 'Screenshot taken %s' => 'Skjemskudd lagt inn %s', + 'Add a screenshot' => 'Legg til et skjermbilde', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Ta et skjermskudd og trykk CTRL+V eller ⌘+V for å lime inn her.', + 'Screenshot uploaded successfully.' => 'Skjermbilde opplastet', + 'SEK - Swedish Krona' => 'SEK - Svenske kroner', + 'Identifier' => 'Prosjektkode', + 'Disable two factor authentication' => 'Slå av tofaktor autentisering', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Er du sikker på at du vil slå av tofaktor autentisering for bruker: "%s"?', + 'Edit link' => 'Endre lenke', + 'Start to type task title...' => 'Skriv in tittel', + 'A task cannot be linked to itself' => 'Oppgaver kan ikke lenkes til seg selv', + 'The exact same link already exists' => 'Denne lenken finnes fra før', + 'Recurrent task is scheduled to be generated' => 'Gjentagende oppgave vil bli opprettet', + 'Score' => 'Poeng', + 'The identifier must be unique' => 'Identifikatoren må være unik', + 'This linked task id doesn\'t exists' => 'Den lenkede oppgave-IDen finnes ikke', + 'This value must be alphanumeric' => 'Verdien må være alfanumerisk', + 'Edit recurrence' => 'Endre gjentakelser', + 'Generate recurrent task' => 'Opprett gjentagende oppgave', + 'Trigger to generate recurrent task' => 'Betingelse for å generere gjentakende oppgave', + 'Factor to calculate new due date' => 'Faktor for å beregne ny tidsfrist', + 'Timeframe to calculate new due date' => 'Tidsramme for å beregne ny tidsfrist', + 'Base date to calculate new due date' => 'Grunnlagsdato for å beregne ny tidsfrist', + 'Action date' => 'Hendelsesdato', + 'Base date to calculate new due date: ' => 'Grunnlagsdato for å beregne ny tidsfrist', + 'This task has created this child task: ' => 'Denne oppgaven har opprettet denne relaterte oppgaven', + 'Day(s)' => 'Dager', + 'Existing due date' => 'Gjeldende tidsfrist', + 'Factor to calculate new due date: ' => 'Faktor for å beregne ny tidsfrist', + 'Month(s)' => 'Måneder', + 'This task has been created by: ' => 'Denne oppgaven er opprettet av:', + 'Recurrent task has been generated:' => 'Repeterende oppgave er opprettet', + 'Timeframe to calculate new due date: ' => 'Tidsrom for beregning av ny tidsfrist: ', + 'Trigger to generate recurrent task: ' => 'Trigger for å generere gjentagende oppgave: ', + 'When task is closed' => 'Når oppgaven er lukket', + 'When task is moved from first column' => 'Når oppgaven er flyttet fra første kolon', + 'When task is moved to last column' => 'Når oppgaven er flyttet til siste kolonne', + 'Year(s)' => 'år', + 'Project settings' => 'Prosjektinnstillinger', + 'Automatically update the start date' => 'Oppdater automatisk start-datoen', + 'iCal feed' => 'iCal strøm', + 'Preferences' => 'Preferanser', + 'Security' => 'Sikkerhet', + 'Two factor authentication disabled' => 'Dobbelgodkjenning deaktivert', + 'Two factor authentication enabled' => 'Dobbelgodkjenning aktivert', + 'Unable to update this user.' => 'Kan ikke oppdatere brukeren', + 'There is no user management for personal projects.' => 'Private prosjekter har ikke brukeradministrasjon', + 'User that will receive the email' => 'Bruker som vil motta den nye e-posten', + 'Email subject' => 'E-post emne', + 'Date' => 'Dato', + 'Add a comment log when moving the task between columns' => 'Legg til en kommentar i loggen når en oppgave flyttes mellom kolonnene', + 'Move the task to another column when the category is changed' => 'Flytt oppgaven til en annen kolonne når kategorien endres', + 'Send a task by email to someone' => 'Send en oppgave på epost til noen', + 'Reopen a task' => 'Gjenåpne oppgave', + 'Notification' => 'Varsel', + '%s moved the task #%d to the first swimlane' => '%s flyttet oppgaven #%d til første svømmebane', + 'Swimlane' => 'Svømmebane', + '%s moved the task %s to the first swimlane' => '%s flyttet oppgaven %s til første svømmebane', + '%s moved the task %s to the swimlane "%s"' => '%s flyttet oppgaven %s til svømmebanen "%s"', + 'This report contains all subtasks information for the given date range.' => 'Rapporten viser informasjon om alle deloppgaver for valgte tidsrom.', + 'This report contains all tasks information for the given date range.' => 'Rapporten inneholder informasjon om alle oppgaver for valgte tidsrom', + 'Project activities for %s' => 'Prosjektaktiviteter for %s', + 'view the board on Kanboard' => 'vis tavlen', + 'The task has been moved to the first swimlane' => 'Oppgaven er flyttet til første svømmebane', + 'The task has been moved to another swimlane:' => 'Oppgaven er flyttet til en annen svømmebane:', + 'New title: %s' => 'Ny tittel: %s', + 'The task is not assigned anymore' => 'Oppgaven har ikke lenger noen ansvarlig', + 'New assignee: %s' => 'Ny ansvarlig: %s', + 'There is no category now' => 'Ingen kategori nå', + 'New category: %s' => 'Ny kategori: %s', + 'New color: %s' => 'Ny farge: %s', + 'New complexity: %d' => 'Ny kompleksitet: %d', + 'The due date has been removed' => 'Fristdato er fjernet', + 'There is no description anymore' => 'Ikke lenger noen beskrivelse', + 'Recurrence settings has been modified' => 'Instillinger for gjentagelse er endret', + 'Time spent changed: %sh' => 'Tidsforbruk er endret: %sh', + 'Time estimated changed: %sh' => 'Tidsestimat er endret: %sh', + 'The field "%s" has been updated' => 'Feltet "%s" er oppdatert', + 'The description has been modified:' => 'Beskrivelsen er endret', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Ønsker du virkelig å lukke oppgaven "%s" og alle tilhørende deloppgaver?', + 'I want to receive notifications for:' => 'Jeg vil motta varslinger om:', + 'All tasks' => 'Alle oppgaver', + 'Only for tasks assigned to me' => 'Kun oppgaver som er tildelt meg', + 'Only for tasks created by me' => 'Kun oppgaver som er opprettet av meg', + 'Only for tasks created by me and tasks assigned to me' => 'Kun oppgaver som er opprettet av meg og tildelt meg', + '%%Y-%%m-%%d' => '%%d-%%m-%%Y', + 'Total for all columns' => 'Totalt for alle kolonner', + 'You need at least 2 days of data to show the chart.' => 'Du må ha minimum 2 dager med data for å kunne se dette diagrammet', + '<15m' => '<15m', + '<30m' => '<30m', + 'Stop timer' => 'Stopp timer', + 'Start timer' => 'Start timer', + 'My activity stream' => 'Aktivitetslogg', + 'Search tasks' => 'Søk oppgave', + 'Reset filters' => 'Nullstill filter', + 'My tasks due tomorrow' => 'Mine oppgaver med frist i morgen', + 'Tasks due today' => 'Oppgaver med frist i dag', + 'Tasks due tomorrow' => 'Oppgaver med frist i morgen', + 'Tasks due yesterday' => 'Oppgaver med frist i går', + 'Closed tasks' => 'Fullførte oppgaver', + 'Open tasks' => 'Åpne oppgaver', + 'Not assigned' => 'Ikke tildelt', + 'View advanced search syntax' => 'Vis hjelp for avansert søk ', + 'Overview' => 'Oversikt', + 'Board/Calendar/List view' => 'Oversikt/kalender/listevisning', + 'Switch to the board view' => 'Oversiktsvisning', + 'Switch to the list view' => 'Listevisning', + 'Go to the search/filter box' => 'Gå til søk/filter', + 'There is no activity yet.' => 'Ingen aktiviteter ennå.', + 'No tasks found.' => 'Ingen oppgaver funnet', + 'Keyboard shortcut: "%s"' => 'Hurtigtaster: "%s"', + 'List' => 'Liste', + 'Filter' => 'Filter', + 'Advanced search' => 'Avansert søk', + 'Example of query: ' => 'Eksempel på spørring', + 'Search by project: ' => 'Søk etter prosjekt', + 'Search by column: ' => 'Søk etter kolonne', + 'Search by assignee: ' => 'Søk etter tildelt', + 'Search by color: ' => 'Søk etter farge', + 'Search by category: ' => 'Søk etter kategori', + 'Search by description: ' => 'Søk etter beskrivelse', + 'Search by due date: ' => 'Søk etter frist', + 'Average time spent in each column' => 'Gjennomsnittlig tid i hver kolonne', + 'Average time spent' => 'Gjennomsnittlig tidsbruk', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Dette diagrammet viser gjennomsnittlig tid i hver kolonne for de siste %d oppgavene.', + 'Average Lead and Cycle time' => 'Gjennomsnittlig ledetid og syklustid', + 'Average lead time: ' => 'Gjennomsnittlig ledetid', + 'Average cycle time: ' => 'Gjennomsnitlig syklustid', + 'Cycle Time' => 'Syklustid', + 'Lead Time' => 'Ledetid', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Dette diagrammet viser gjennomsnittlig ledetid og syklustid for de siste %d oppgavene.', + 'Average time into each column' => 'Gjennomsnittlig tid i hver kolonne', + 'Lead and cycle time' => 'Ledetid og syklustid', + 'Lead time: ' => 'Ledetid', + 'Cycle time: ' => 'Syklustid', + 'Time spent in each column' => 'Tid brukt i hver kolonne', + 'The lead time is the duration between the task creation and the completion.' => 'Ledetid er tidsrommet fra oppgaven opprettes til den er fullført', + 'The cycle time is the duration between the start date and the completion.' => 'Syklustid er tidsrommet fra oppgaven starter til den er fullført', + 'If the task is not closed the current time is used instead of the completion date.' => 'Dersom oppgaven ikke er lukket brukes dagens dato i stedet for fullført dato.', + 'Set the start date automatically' => 'Sett startdato automatisk', + 'Edit Authentication' => 'Endre autentisering', + 'Remote user' => 'Ekstern bruker', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Eksterne brukere har ikke passordet lagret i Kanboard. Eksempel: LDAP, Google og Github-kontoer.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Hvis du har krysset av for "Deaktiver innloggingsbilde" vil opplysninger som tastes inn i innloggingsskjemaet bli ignorert', + 'Default task color' => 'Standard oppgavefarge', + 'This feature does not work with all browsers.' => 'Denne funksjonen virker ikke i alle nettlesere', + 'There is no destination project available.' => 'Finner ikke noe målprosjekt', + 'Trigger automatically subtask time tracking' => 'Automatisk tidsforbruk for deloppgaver', + 'Include closed tasks in the cumulative flow diagram' => 'Ta med lukkede oppgaver i kumulativt flytdiagram', + 'Current swimlane: %s' => 'Nåværende svømmebane: %s', + 'Current column: %s' => 'Nåværende kolonne: %s', + 'Current category: %s' => ': %s', + 'no category' => 'ingen kategori', + 'Current assignee: %s' => 'Tildelt til %s', + 'not assigned' => 'ikke tildelt', + 'Author:' => 'Opprettet av', + 'contributors' => 'bidragsytere', + 'License:' => 'Lisens:', + 'License' => 'Lisens', + 'Enter the text below' => 'Skriv inn bokstavene du ser i bildet', + 'Start date:' => 'Startdato:', + 'Due date:' => 'Frist:', + 'People who are project managers' => 'Prosjektledere', + 'People who are project members' => 'Prosjektmedlemmer', + 'NOK - Norwegian Krone' => 'NOK - Norske kroner', + 'Show this column' => 'Vis denne kolonnen', + 'Hide this column' => 'Skjul denne kolonnen', + 'End date' => 'Sluttdato', + 'Users overview' => 'Brukeroversikt', + 'Members' => 'Medlemmer', + 'Shared project' => 'Delt prosjekt', + 'Project managers' => 'Prosjektledere', + 'Projects list' => 'Prosjektliste', + 'End date:' => 'Sluttdato:', + 'Change task color when using a specific task link' => 'Endre oppgavefarge når det brukes en definert oppgavelenke', + 'Task link creation or modification' => 'Oppgavelenke opprettet eller endret', + 'Milestone' => 'Milepæl', + 'Reset the search/filter box' => 'Nullstill søk/filter', + 'Documentation' => 'Dokumentasjon', + 'Author' => 'Forfatter', + 'Version' => 'Versjon', + 'Plugins' => 'Innstikk', + 'There is no plugin loaded.' => 'Ingen innstikk er lastet', + 'My notifications' => 'Mine varslinger', + 'Custom filters' => 'Egendefinerte filtre', + 'Your custom filter has been created successfully.' => 'Egendefinert filter er opprettet.', + 'Unable to create your custom filter.' => 'Feil ved oppretting av egendefinert filter.', + 'Custom filter removed successfully.' => 'Egendefinert filter er slettet.', + 'Unable to remove this custom filter.' => 'Feil ved sletting av egendefinert filter.', + 'Edit custom filter' => 'Endre egendefinert filter', + 'Your custom filter has been updated successfully.' => 'Egendefinert filter er endret.', + 'Unable to update custom filter.' => 'Feil ved endring av egendefinert filter.', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Nytt vedlegg til oppgaven #%d: %s', + 'New comment on task #%d' => 'Ny kommentar på oppgaven #%d', + 'Comment updated on task #%d' => 'Kommentar endret for oppgaven #%d', + 'New subtask on task #%d' => 'Ny deloppgave for oppgaven #%d', + 'Subtask updated on task #%d' => 'Deloppgave er endret for oppgave #%d', + 'New task #%d: %s' => 'Ny oppgave #%d: %s', + 'Task updated #%d' => 'Oppgaven #%d er endret', + 'Task #%d closed' => 'Oppgaven #%d er lukket', + 'Task #%d opened' => 'Oppgaven #%d er åpnet', + 'Column changed for task #%d' => 'Kolonne endret for oppgaven #%d', + 'New position for task #%d' => 'Ny posisjon for oppgaven #%d', + 'Swimlane changed for task #%d' => 'Svømmebane er endret for oppgaven #%d', + 'Assignee changed on task #%d' => 'Ansvarlig er endret for oppgaven #%d', + '%d overdue tasks' => '%d forfalte oppgaver', + 'No notification.' => 'Ingen varslinger', + 'Mark all as read' => 'Merk alle som lest', + 'Mark as read' => 'Merk som lest', + 'Total number of tasks in this column across all swimlanes' => 'Totalt antall oppgaver i denne kolonnen gjennom alle svømmebaner', + 'Collapse swimlane' => 'Slå sammen svømmebanen', + 'Expand swimlane' => 'Fold ut svømmebane', + 'Add a new filter' => 'Lag nytt filter', + 'Share with all project members' => 'Del med alle prosjektdeltagere', + 'Shared' => 'Delt', + 'Owner' => 'Eier', + 'Unread notifications' => 'Uleste varslinger', + 'Notification methods:' => 'Varslingsmetoder', + 'Unable to read your file' => 'Kan ikke lese filen', + '%d task(s) have been imported successfully.' => '%d oppgave(r) er importert.', + 'Nothing has been imported!' => 'Ingenting ble importert!', + 'Import users from CSV file' => 'Importere brukere fra CSV-fil', + '%d user(s) have been imported successfully.' => '%d bruker(e) er imporert.', + 'Comma' => 'Komma', + 'Semi-colon' => 'Semikolon', + 'Tab' => 'Tabulator', + 'Vertical bar' => 'Loddrett strek', + 'Double Quote' => 'Rett sitattegn', + 'Single Quote' => 'Rett apostrof', + '%s attached a file to the task #%d' => '%s la ved en fil til oppgaven #%d', + 'There is no column or swimlane activated in your project!' => 'Prosjektet har ingen aktive kolonner eller svømmebaner!', + 'Append filter (instead of replacement)' => 'Tilføy filter (i stedet for å erstatte)', + 'Append/Replace' => 'Tilføu/erstatt', + 'Append' => 'Tilføy', + 'Replace' => 'Erstatt', + 'Import' => 'Importer', + 'Change sorting' => 'Endre sortering', + 'Tasks Importation' => 'Importere oppgaver', + 'Delimiter' => 'Feltavgrenser', + 'Enclosure' => 'Streng skilletegn', + 'CSV File' => 'CSV-fil', + 'Instructions' => 'Instruksjoner', + 'Your file must use the predefined CSV format' => 'Filen må ha et forhåndsdefinert CSV-format', + 'Your file must be encoded in UTF-8' => 'Filen må være kodet i UTF-8', + 'The first row must be the header' => 'Første rad må være overskrifter', + 'Duplicates are not verified for you' => 'Det blir ikke sjekket for duplikater', + 'The due date must use the ISO format: YYYY-MM-DD' => 'Tidsfrister må angis i ISO-format: ÅÅÅÅ-MM-DD', + 'Download CSV template' => 'Last ned en CSV-mal', + 'No external integration registered.' => 'Ingen ekstern intergasjon er registrert', + 'Duplicates are not imported' => 'Duplikater importeres ikke', + 'Usernames must be lowercase and unique' => 'Brukernavn må være unike og skrevet i små bokstaver', + 'Passwords will be encrypted if present' => 'Eventuelt oppgitte passord vil blir kryptert', + '%s attached a new file to the task %s' => '%s la ved en ny fil til oppgaven %s', + 'Link type' => 'Relasjonstype', + 'Assign automatically a category based on a link' => 'Tildel kategori automatisk basert på lenke', + 'BAM - Konvertible Mark' => 'BAM - Bosniske mark', + 'Assignee Username' => 'Brukernavn ansvarlig', + 'Assignee Name' => 'Fullt navn ansvarlig', + 'Groups' => 'Grupper', + 'Members of %s' => 'Medlemmer av %s', + 'New group' => 'Ny gruppe', + 'Group created successfully.' => 'Gruppen er opprettet', + 'Unable to create your group.' => 'Kunne ikke opprette gruppen', + 'Edit group' => 'Endre gruppe', + 'Group updated successfully.' => 'Gruppen er endret', + 'Unable to update your group.' => 'Kunne ikke endre gruppen', + 'Add group member to "%s"' => 'Legg gruppemedlem til "%s"', + 'Group member added successfully.' => 'Gruppemedlem lagt til', + 'Unable to add group member.' => 'Kunne ikke legge til gruppemedlem', + 'Remove user from group "%s"' => 'Fjern bruker fra gruppen "%s"', + 'User removed successfully from this group.' => 'Bruker er fjernet fra denne gruppen.', + 'Unable to remove this user from the group.' => 'Kunne ikke fjerne bruker fra gruppen.', + 'Remove group' => 'Fjern gruppe', + 'Group removed successfully.' => 'Gruppen er fjernet', + 'Unable to remove this group.' => 'Kunne ikke fjerne gruppen', + 'Project Permissions' => 'Prosjektrettigheter', + 'Manager' => 'Leder', + 'Project Manager' => 'Prosjektleder', + 'Project Member' => 'Prosjektdeltager', + 'Project Viewer' => 'Kun lesetilgang', + 'Your account is locked for %d minutes' => 'Kontoen din vil være låst i %d minutter', + 'Invalid captcha' => 'Ugyldig captcha', + 'The name must be unique' => 'Navnet må være unikt', + 'View all groups' => 'Vis alle grupper', + 'There is no user available.' => 'Ingen tilgjengelig bruker', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Ønsker du virkelig å fjerne bruker "%s" fra gruppen "%s"?', + 'There is no group.' => 'Ingen grupper opprettet', + 'Add group member' => 'Legg til medlem', + 'Do you really want to remove this group: "%s"?' => 'Ønsker du virkelig å fjerne gruppen "%s"', + 'There is no user in this group.' => 'Gruppen har ingen medlemmer', + 'Permissions' => 'Rettigheter', + 'Allowed Users' => 'Brukere med tilgang', + 'No specific user has been allowed.' => 'Ingen brukere er lagt inn foreløpig', + 'Role' => 'Rolle', + 'Enter user name...' => 'Skriv brukenavn', + 'Allowed Groups' => 'Brukergrupper med tilgang', + 'No group has been allowed.' => 'Ingen brukergrupper er lagt inn foreløpig', + 'Group' => 'Gruppe', + 'Group Name' => 'Gruppenavn', + 'Enter group name...' => 'Skriv gruppenavn', + 'Role:' => 'Rolle', + 'Project members' => 'Prosjektmedlemmer', + '%s mentioned you in the task #%d' => '%s nevnte deg i oppgaven #%d', + '%s mentioned you in a comment on the task #%d' => '%s nevnte deg i en kommentar på oppgaven #%d', + 'You were mentioned in the task #%d' => 'Du ble nevnt i oppgaven #%d', + 'You were mentioned in a comment on the task #%d' => 'Du ble nevnt i en kommentar på oppgaven #%d', + 'Estimated hours: ' => 'Estimerte timer:', + 'Actual hours: ' => 'Faktisk medgåtte timer', + 'Hours Spent' => 'Brukt timer', + 'Hours Estimated' => 'Estimerte timer', + 'Estimated Time' => 'Estimert tid', + 'Actual Time' => 'Medgått tid', + 'Estimated vs actual time' => 'Estimert mot virkelig tid', + 'RUB - Russian Ruble' => 'RUB - Russiske rubler', + 'Assign the task to the person who does the action when the column is changed' => 'Tildel oppgaven til personen som flytter den til en annen kolonne', + 'Close a task in a specific column' => 'Lukk en oppgave i en angitt kolonne', + 'Time-based One-time Password Algorithm' => 'Algoritme for tidsbasert engangspassord', + 'Two-Factor Provider: ' => 'Tofaktorleverandør:', + 'Disable two-factor authentication' => 'Deaktiver tofaktorautentisering', + 'Enable two-factor authentication' => 'Aktiver tofaktorautentisering', + 'There is no integration registered at the moment.' => 'Ingen registrert integrasjon.', + 'Password Reset for Kanboard' => 'Nytt passord til Kanboard', + 'Forgot password?' => 'Glemt passord?', + 'Enable "Forget Password"' => 'Bruk "Glemt passord"', + 'Password Reset' => 'Nytt passord', + 'New password' => 'Nytt passord', + 'Change Password' => 'Endre passord', + 'To reset your password click on this link:' => 'Bruk denne lenken for å tilbakestille passordet ditt:', + 'Last Password Reset' => 'Forrige passordendring', + 'The password has never been reinitialized.' => 'Passordet er aldri blitt endret', + 'Creation' => 'Opprettet', + 'Expiration' => 'Utløper', + 'Password reset history' => 'Passordhistorikk', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Alle oppgaver i kolonne "%s" og svømmebane "%s" er lukket.', + 'Do you really want to close all tasks of this column?' => 'Ønsker du virkelig å lukke ALLE saker i kolonnen?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d oppgave(r) i kolonne "%s" og svømmebane "%s" vil bli lukket.', + 'Close all tasks in this column and this swimlane' => 'Lukk alle saker i denne kolonnen', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Det finnes ingen innstikk for prosjektvarsling. Du kan likevel sette opp individuelle varslinger i brukerprofilen din.', + 'My dashboard' => 'Mitt dashbord', + 'My profile' => 'Min profil', + 'Project owner: ' => 'Prosjekteier: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'Du kan lage en valgfri, alfanumerisk prosjektkode — for eksempel MITTPROSJEKT', + 'Project owner' => 'Prosjekteier', + 'Personal projects do not have users and groups management.' => 'Private prosjekter har ikke brukere eller gruppeadministrasjon', + 'There is no project member.' => 'Ingen prosjektmedlemmer', + 'Priority' => 'Prioritet', + 'Task priority' => 'Prioritet', + 'General' => 'Generelt', + 'Dates' => 'Datoer', + 'Default priority' => 'Standard', + 'Lowest priority' => 'Lav', + 'Highest priority' => 'Høy', + 'Close a task when there is no activity' => 'Lukk enoppgave når det ikke er noen aktivitet', + 'Duration in days' => 'Varighet i dager', + 'Send email when there is no activity on a task' => 'Send e-post når det ikke er noen aktivitet på en oppgave', + 'Unable to fetch link information.' => 'Kan ikke hente lenkeinformasjon', + 'Daily background job for tasks' => 'Daglig bakgrunnsjobb for oppgaver', + 'Auto' => 'Auto', + 'Related' => 'Relasjon', + 'Attachment' => 'Vedlegg', + 'Web Link' => 'Web-lenke', + 'External links' => 'Eksterne lenker', + 'Add external link' => 'Ny ekstern lenke', + 'Type' => 'Type', + 'Dependency' => 'Avhenger av', + 'Add internal link' => 'Lag intern lenke', + 'Add a new external link' => 'Lag ekstern lenke', + 'Edit external link' => 'Endre ekstern lenke', + 'External link' => 'Ekstern lenke', + 'Copy and paste your link here...' => 'Kopier lenken og lim den inn her', + 'URL' => 'URL', + 'Internal links' => 'Interne lenker', + 'Assign to me' => 'Tildel denne til meg', + 'Me' => 'Meg', + 'Do not duplicate anything' => 'Ingen duplisering', + 'Projects management' => 'Prosjektadministrasjon', + 'Users management' => 'Brukeradministrasjon', + 'Groups management' => 'Gruppeadministrasjon', + 'Create from another project' => 'Lag fra et annet prosjekt', + 'open' => 'åpen', + 'closed' => 'lukket', + 'Priority:' => 'Prioritet', + 'Reference:' => 'Referanse', + 'Complexity:' => 'Kompleksitet', + 'Swimlane:' => 'Svømmebane', + 'Column:' => 'Kolonne', + 'Position:' => 'Posisjon', + 'Creator:' => 'Opprettet av', + 'Time estimated:' => 'Estimert tidsforbruk', + '%s hours' => '%s timer', + 'Time spent:' => 'Medgått tid', + 'Created:' => 'Opprettet', + 'Modified:' => 'Endret', + 'Completed:' => 'Fullført', + 'Started:' => 'Startet:', + 'Moved:' => 'Flyttet:', + 'Task #%d' => 'Oppgave #%d', + 'Time format' => 'Tidsformat', + 'Start date: ' => 'Startdato:', + 'End date: ' => 'Sluttdato:', + 'New due date: ' => 'Ny frist:', + 'Start date changed: ' => 'Startdato endret:', + 'Disable personal projects' => 'Deaktiver mulighet for private prosjekter', + 'Do you really want to remove this custom filter: "%s"?' => 'Ønsker du virkelig å fjerne det egendefinerte filteret: "%s"?', + 'Remove a custom filter' => 'Fjern egendefinert filter', + 'User activated successfully.' => 'Brukeren er aktivert.', + 'Unable to enable this user.' => 'Kan ikke aktivere brukeren.', + 'User disabled successfully.' => 'Brukeren er deaktivert.', + 'Unable to disable this user.' => 'Kan ikke deaktivere brukeren.', + 'All files have been uploaded successfully.' => 'Alle filer er lastet opp.', + 'The maximum allowed file size is %sB.' => 'Maks. tillatte filstørrelse er %sB', + 'Drag and drop your files here' => 'Dra og slipp filene her', + 'choose files' => 'velg filer', + 'View profile' => 'Vis profil', + 'Two Factor' => 'Tofaktor', + 'Disable user' => 'Deaktiver bruker', + 'Do you really want to disable this user: "%s"?' => 'Ønsker du virkelig å DEAKTIVERE denne brukeren: "%s"?', + 'Enable user' => 'Aktiver bruker', + 'Do you really want to enable this user: "%s"?' => 'Ønsker du virkelig å AKTIVERE denne brukeren: "%s"?', + 'Download' => 'Last ned', + 'Uploaded: %s' => 'Lastet opp: %s', + 'Size: %s' => 'Størrelse: %s', + 'Uploaded by %s' => 'Lastet opp av %s', + 'Filename' => 'Filnavn', + 'Size' => 'Størrelse', + 'Column created successfully.' => 'Kolonnen er opprettet', + 'Another column with the same name exists in the project' => 'Kolonnen finnes allerede i dette prosjektet', + 'Default filters' => 'Standardfiltere', + 'Your board doesn\'t have any columns!' => 'Tavlen har ingen kolonner!', + 'Change column position' => 'Endre kolonneplassering', + 'Switch to the project overview' => 'Bytt til prosjektoversikt', + 'User filters' => 'Brukerfilter', + 'Category filters' => 'Kategorifiltere', + 'Upload a file' => 'Last opp fil', + 'View file' => 'Vis fil', + 'Last activity' => 'Nyeste aktivitet', + 'Change subtask position' => 'Endre posisjon for deloppgave', + 'This value must be greater than %d' => 'Verdien må være større en %d', + 'Another swimlane with the same name exists in the project' => 'Denne svømmebanen finnes allerede i dette prosjektet', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Eksempel: https://eksempel.kanboard.org/ (brukes for å lage absolutte URLer)', + 'Actions duplicated successfully.' => 'Handlinger er duplisert.', + 'Unable to duplicate actions.' => 'Feil ved duplisering av handlinger.', + 'Add a new action' => 'Ny automatisk handling', + 'Import from another project' => 'Importer fra et annet prosjekt', + 'There is no action at the moment.' => 'Ingen automatiske handlinger', + 'Import actions from another project' => 'Importer handlinger fra et annet prosjekt', + 'There is no available project.' => 'Ingen prosjekter', + 'Local File' => 'Lokal fil', + 'Configuration' => 'Konfigurasjon', + 'PHP version:' => 'PHP versjon:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'OS versjon', + 'Database version:' => 'Databaseversjon', + 'Browser:' => 'Nettleser', + 'Task view' => 'Vis oppgave', + 'Edit task' => 'Endre oppgave', + 'Edit description' => 'Endre beskrivelse', + 'New internal link' => 'Ny intern lenke', + 'Display list of keyboard shortcuts' => 'Oversikt over tastatursnarveier', + 'Avatar' => 'Profilbilde', + 'Upload my avatar image' => 'Last opp profilbilde', + 'Remove my image' => 'Fjern profilbilde', + 'The OAuth2 state parameter is invalid' => 'Feil parameter for OAuth2 tilstand', + 'User not found.' => 'Bruker ikke funnet', + 'Search in activity stream' => 'Søk i aktivitetsstrøm', + 'My activities' => 'Mine aktiviteter', + 'Activity until yesterday' => 'Aktivitet fram til i går', + 'Activity until today' => 'Aktivitet fram til i dag', + 'Search by creator: ' => 'Søk > Opprettet av:', + 'Search by creation date: ' => 'Søk > Opprettet dato:', + 'Search by task status: ' => 'Søk > Oppgavestatus', + 'Search by task title: ' => 'Søk > Oppgavetittel', + 'Activity stream search' => 'Søk > Aktivitetsstrøm', + 'Projects where "%s" is manager' => 'Prosjekter hvor "%s" er prosjektleder', + 'Projects where "%s" is member' => 'Prosjekter hvor "%s" er medlem', + 'Open tasks assigned to "%s"' => 'Åpne oppgaver tildelt "%s"', + 'Closed tasks assigned to "%s"' => 'Lukkede oppgaver tildelt "%s"', + 'Assign automatically a color based on a priority' => 'Tilordne farge automatisk ut fra prioritet', + 'Overdue tasks for the project(s) "%s"' => 'Forsinkede oppgaver for prosjekt(ene) "%s"', + 'Upload files' => 'Last opp filer', + 'Installed Plugins' => 'Installerte innstikk', + 'Plugin Directory' => 'Instikk (katalog)', + 'Plugin installed successfully.' => 'Innstikket er installert', + 'Plugin updated successfully.' => 'Innstikket er oppdatert', + 'Plugin removed successfully.' => 'Innstikket er fjernet', + 'Subtask converted to task successfully.' => 'Deloppgaven er konvertert til en oppgave', + 'Unable to convert the subtask.' => 'Kunne ikke konvertere deloppgaven', + 'Unable to extract plugin archive.' => 'Feil ved utpakking av innstikk', + 'Plugin not found.' => 'Finner ikke innstikk', + 'You don\'t have the permission to remove this plugin.' => 'Du har ikke rettigheter til å fjerne dette innstikket', + 'Unable to download plugin archive.' => 'Feil ved nedlasting av innstikk', + 'Unable to write temporary file for plugin.' => 'Feil ved skriving av midlertidige filer for innstikket', + 'Unable to open plugin archive.' => 'Feil ved åpning av innstikk', + 'There is no file in the plugin archive.' => 'Innstikket inneholder ingen filer!', + 'Create tasks in bulk' => 'Masseoppretting av saker', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Denne Kanboardinstallasjonen er ikke satt opp for å legge til innstikk fra brukergrensensnittet.', + 'There is no plugin available.' => 'Finner ingen innstikk', + 'Install' => 'Installer', + 'Update' => 'Oppdater', + 'Up to date' => 'Ajour', + 'Not available' => 'Ikke tilgjengelig', + 'Remove plugin' => 'Fjern innstikk', + 'Do you really want to remove this plugin: "%s"?' => 'Er du sikker på at du vil fjerne innstikket "%s"?', + 'Uninstall' => 'Avinstaller', + 'Listing' => 'Oversikt', + 'Metadata' => 'Metadata', + 'Manage projects' => 'Administrere prosjekter', + 'Convert to task' => 'Gjør om til oppgave', + 'Convert sub-task to task' => 'Gjør om deloppgave til oppgave', + 'Do you really want to convert this sub-task to a task?' => 'Er du sikker på at du vil gjøre om denne deloppgaven til en oppgave?', + 'My task title' => 'Oppgavetittel', + 'Enter one task by line.' => 'Du kan skrive inn en oppgave pr rad', + 'Number of failed login:' => 'Antall feilede loginforsøk:', + 'Account locked until:' => 'Kontoen er låst til:', + 'Email settings' => 'E-post', + 'Email sender address' => 'Avsenderadresse (e-post):', + 'Email transport' => 'E-post metode', + 'Webhook token' => 'Webhook token', + 'Project tags management' => 'Etiketter', + 'Tag created successfully.' => 'Etiketten er opprettet', + 'Unable to create this tag.' => 'Kunne ikke opprette etiketten', + 'Tag updated successfully.' => 'Etiketten er endret', + 'Unable to update this tag.' => 'Kunne ikke endre etiketten', + 'Tag removed successfully.' => 'Etiketten er slettet', + 'Unable to remove this tag.' => 'Kunne ikke slette etiketten', + 'Global tags management' => 'Administrere globale etiketter', + 'Tags' => 'Etiketter', + 'Tags management' => 'Etiketter', + 'Add new tag' => 'Ny etikett', + 'Edit a tag' => 'Endre etikett', + 'Project tags' => 'Etiketter', + 'There is no specific tag for this project at the moment.' => 'Prosjektet har for tiden ingen etiketter', + 'Tag' => 'Etikett', + 'Remove a tag' => 'Fjern etikett', + 'Do you really want to remove this tag: "%s"?' => 'Er du sikker på at du vil fjerne etiketten: "%s"?', + 'Global tags' => 'Globale etiketter', + 'There is no global tag at the moment.' => 'Ingen globale etiketter', + 'This field cannot be empty' => 'Dette feltet kan ikke være tomt', + 'Close a task when there is no activity in a specific column' => 'Lukk oppgaven når det ikke er aktivitet i en angitt kolonne', + '%s removed a subtask for the task #%d' => '%s slettet en deloppgave for oppgaven #%d', + '%s removed a comment on the task #%d' => '%s slettet en kommentar på oppgaven #%d', + 'Comment removed on task #%d' => 'Kommentar slettet fra oppgave #%d', + 'Subtask removed on task #%d' => 'Deloppgave slettet fra oppgave #%d', + 'Hide tasks in this column in the dashboard' => 'Ikke vis oppgaver i denne kolonnen på dashbordet', + '%s removed a comment on the task %s' => '%s slettet en kommentar på oppgaven %s', + '%s removed a subtask for the task %s' => '%s slettet en deloppgave for oppgaven %s', + 'Comment removed' => 'Kommentaren er fjernet', + 'Subtask removed' => 'Deloppgaven er slettet', + '%s set a new internal link for the task #%d' => '%s lagde en internlenke på oppgaven #%d', + '%s removed an internal link for the task #%d' => '%s fjernet en internlenke på oppgaven #%d', + 'A new internal link for the task #%d has been defined' => 'Det er laget en ny internlenke for oppgaven #%d', + 'Internal link removed for the task #%d' => 'Internlenke fjernet på oppgaven #%d', + '%s set a new internal link for the task %s' => '%s laget en ny internlenke for oppgaven %s', + '%s removed an internal link for the task %s' => '%s fjernet en internlenke for opgaven %s', + 'Automatically set the due date on task creation' => 'Sett tidsfrist automatisk ved opprettelse av oppgave', + 'Move the task to another column when closed' => 'Flytt oppgaven til en annen kolonne når den lukkes', + 'Move the task to another column when not moved during a given period' => 'Flytt oppgaven til en annen kolonne når den ikke flyttes i et gitt tidsrom', + 'Dashboard for %s' => 'Dashbord for %s', + 'Tasks overview for %s' => 'Oppgaveoversikt for %s', + 'Subtasks overview for %s' => 'Deloppgaver for %s', + 'Projects overview for %s' => 'Prosjektoversikt for %s', + 'Activity stream for %s' => 'Aktivitetsstrøm for %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Tilordne farge når oppgaven flyttes til angitt svømmebane', + 'Assign a priority when the task is moved to a specific swimlane' => 'Tilordne prioritet når oppgaven flyttes til angitt svømmebane', + 'User unlocked successfully.' => 'Brukeren er låst opp', + 'Unable to unlock the user.' => 'Kunne ikke låse opp brukeren', + 'Move a task to another swimlane' => 'Flytt oppgaven til en annen svømmebane', + 'Creator Name' => 'Opprettet av', + 'Time spent and estimated' => 'Estimert og medgått tid', + 'Move position' => 'Endre posisjon', + 'Move task to another position on the board' => 'Flytt oppgaven', + 'Insert before this task' => 'Sett inn før denne oppgaven', + 'Insert after this task' => 'Sett inn etter denne oppgaven', + 'Unlock this user' => 'Lås opp bruker', + 'Custom Project Roles' => 'Egendefinerte prosjektroller', + 'Add a new custom role' => 'Ny egendefinert rolle', + 'Restrictions for the role "%s"' => 'Restriksjoner for rollen "%s"', + 'Add a new project restriction' => 'Ny prosjektrestriksjon', + 'Add a new drag and drop restriction' => 'Ny dra og slipp-restriksjon', + 'Add a new column restriction' => 'Ny kolonnerestriksjon', + 'Edit this role' => 'Rediger rolle', + 'Remove this role' => 'Fjern denne rollen', + 'There is no restriction for this role.' => 'Denne rollen har ingen restriksjoner', + 'Only moving task between those columns is permitted' => 'Oppgaver kan kun flyttes mellom disse kolonnene', + 'Close a task in a specific column when not moved during a given period' => 'Lukk oppgave i angitt kolonne når den ikke flyttes i en angitt periode', + 'Edit columns' => 'Endre kolonner', + 'The column restriction has been created successfully.' => 'Kolonnerestriksjonen er opprettet', + 'Unable to create this column restriction.' => 'Kunne ikke opprette denne kolonnerestriksjonen', + 'Column restriction removed successfully.' => 'Kolonnerestriksjon fjernet', + 'Unable to remove this restriction.' => 'Kan ikke fjerne denne restriksjonen', + 'Your custom project role has been created successfully.' => 'Egendefinert prosjektrolle er oppdatert', + 'Unable to create custom project role.' => 'Kunne ikke opprette egendefinert prosjektrolle', + 'Your custom project role has been updated successfully.' => 'Egendefinert ptosjektrolle er endret', + 'Unable to update custom project role.' => 'Kunne ikke endre egendefinert prosjektrolle', + 'Custom project role removed successfully.' => 'Egendefinert prosjektrolle er fjernet', + 'Unable to remove this project role.' => 'Kunne ikke fjerne denne prosjektrollen', + 'The project restriction has been created successfully.' => 'Prosjektrestriksjonen er opprettet', + 'Unable to create this project restriction.' => 'Kunne ikke opprette prosjektrestriksjon', + 'Project restriction removed successfully.' => 'Prosjektrestriksjon er fjernet', + 'You cannot create tasks in this column.' => 'Du kan ikke opprette oppgaver i denne kolonnen', + 'Task creation is permitted for this column' => 'Oppretting av oppgaver er tillatt i denne kolonnen', + 'Closing or opening a task is permitted for this column' => 'Lukking/åpning av oppgaver er tillatt i denne kolonnen', + 'Task creation is blocked for this column' => 'Denne kolonnen er låst for oppretting av nye oppgaver', + 'Closing or opening a task is blocked for this column' => 'Oppgaver i denne kolonnen er låst for lukking/åpning', + 'Task creation is not permitted' => 'Kan ikke opprette oppgaver', + 'Closing or opening a task is not permitted' => 'Kan ikke lukke eller åpne oppgaver', + 'New drag and drop restriction for the role "%s"' => 'Ny dra-og-slipp-restriksjon for rollen "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Brukere med denne rollen vil bare kunne flytte oppgaver mellom kilde- og målkolonne.', + 'Remove a column restriction' => 'Fjern kolonnerestriksjon', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Ønsker du virkelig å fjerne kolonnerestriksjonen: "%s" til "%s"?', + 'New column restriction for the role "%s"' => 'Ny kolonnerestriksjon for rollen "%s"', + 'Rule' => 'Regel', + 'Do you really want to remove this column restriction?' => 'Er du sikker på at du vil fjerne denne kolonnerestriksjonen', + 'Custom roles' => 'Egendefinerte roller', + 'New custom project role' => 'Ny egendefinert rolle', + 'Edit custom project role' => 'Endre egendefinert rolle', + 'Remove a custom role' => 'Fjern egendefinert rolle', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Ønsker du virkelig å fjerne den egendefinerte rollen: "%s"? Alle brukere med denne rollen vil bli prosjektmedlemmer.', + 'There is no custom role for this project.' => 'Prosjektet har ingen egendefinerte roller', + 'New project restriction for the role "%s"' => 'Ny prosjektrestriksjon for rollen "%s"', + 'Restriction' => 'Restriksjon', + 'Remove a project restriction' => 'Fjern prosjektrestriksjon', + 'Do you really want to remove this project restriction: "%s"?' => 'Er du sikker på at du vil fjerne restriksjonen "%s"', + 'Duplicate to multiple projects' => 'Dupliser til flere prosjekter', + 'This field is required' => 'Feltet er påkrevet', + 'Moving a task is not permitted' => 'Flytting av oppgaven er ikke tillatt', + 'This value must be in the range %d to %d' => 'Verdien må være i området %d to %d', + 'You are not allowed to move this task.' => 'Du har ikke rettigheter til å flutte denne oppgaven', + 'API User Access' => 'API brukertilgang', + 'Preview' => 'Forhåndsvisning', + 'Write' => 'Skriv', + 'Write your text in Markdown' => 'Du kan skrive teksten din i Markdown hvis du ønsker det.', + 'No personal API access token registered.' => 'Ingen personlig API tilgangsnøkkel er registrert.', + 'Your personal API access token is "%s"' => 'Din personlige API tilgangsnøkkel er "%s"', + 'Remove your token' => 'Fjern din nøkkel', + 'Generate a new token' => 'Lag ny nøkkel', + 'Showing %d-%d of %d' => 'Viser %d-%d of %d', + 'Outgoing Emails' => 'Utgående e-post', + 'Add or change currency rate' => 'Legg til eller endre valuta', + 'Reference currency: %s' => 'Referansevaluta: %s', + 'Add custom filters' => 'Lag egendefinerte filtere', + 'Export' => 'Eksporter', + 'Add link label' => 'Ny etikett', + 'Incompatible Plugins' => 'Ukompatible innstikk', + 'Compatibility' => 'Kompatibilitet', + 'Permissions and ownership' => 'Tillatelser og eierskap', + 'Priorities' => 'Prioriteter', + 'Close this window' => 'Lukk vindu', + 'Unable to upload this file.' => 'Kunne ikke laste opp filen', + 'Import tasks' => 'Importere oppgaver', + 'Choose a project' => 'Velg prosjekt', + 'Profile' => 'Profil', + 'Application role' => 'Rolle', + '%d invitations were sent.' => '%d invitasjoner ble sendt', + '%d invitation was sent.' => '%d invitasjon ble sendt', + 'Unable to create this user.' => 'Kan ikke opprette brukeren.', + 'Kanboard Invitation' => 'Kanboard invitasjon', + 'Visible on dashboard' => 'Synlig på dashbordet', + 'Created at:' => 'Opprettet:', + 'Updated at:' => 'Endret:', + 'There is no custom filter.' => 'Ingen egendefinerte filtre', + 'New User' => 'Ny bruker', + 'Authentication' => 'Autentisering', + 'If checked, this user will use a third-party system for authentication.' => 'Dersom dette er valgt vil brukeren logges inn via et tredjepartssystem', + 'The password is necessary only for local users.' => 'Passord er kun nødvendig for lokale brukere', + 'You have been invited to register on Kanboard.' => 'Du er invitert til å registrere deg på Kanboard', + 'Click here to join your team' => 'Klikk her for å bli med i teamet ditt', + 'Invite people' => 'Inviter personer', + 'Emails' => 'E-poster', + 'Enter one email address by line.' => 'Legg inn en e-postadresse pr. linje', + 'Add these people to this project' => 'Legg til disse personene i prosjektet', + 'Add this person to this project' => 'Legg til denne personen i prosjektet', + 'Sign-up' => 'Registrering', + 'Credentials' => 'Akkreditiver', + 'New user' => 'Ny bruker', + 'This username is already taken' => 'Brukernavn finnes fra før', + 'Your profile must have a valid email address.' => 'Brukerprofilen din må ha en gyldig e-postadresse.', + 'TRL - Turkish Lira' => 'TRL - Tyrkiske lira', + 'The project email is optional and could be used by several plugins.' => 'Du kan angi en e-postadresse for prosjektet, og den kan brukes av ulike innstikksmoduler', + 'The project email must be unique across all projects' => 'Prosjektets e-postadresse må være unik for alle prosjekter', + 'The email configuration has been disabled by the administrator.' => 'Administrator har deaktivert e-postinnstillinger', + 'Close this project' => 'Lukk dette prosjektet', + 'Open this project' => 'Gjenåpne dette prosjektet', + 'Close a project' => 'Lukk et prosekt', + 'Do you really want to close this project: "%s"?' => 'Er du sikker på at vil lukke prosjektet: "%s"?', + 'Reopen a project' => 'Gjenåpne prosjekt', + 'Do you really want to reopen this project: "%s"?' => 'Er du sikker på at du vil gjenåpne prosjektet: "%s"?', + 'This project is open' => 'Dette prosjektet er aktivt', + 'This project is closed' => 'Dette prosjektet er lukket', + 'Unable to upload files, check the permissions of your data folder.' => 'Kunne ikke laste opp filen!', + 'Another category with the same name exists in this project' => 'Kategorien finnes allerede i dette prosjektet', + 'Comment sent by email successfully.' => 'Kommentar sendt med e-post.', + 'Sent by email to "%s" (%s)' => 'Sendt med e-post til "%s" (%s)', + 'Unable to read uploaded file.' => 'Kan ikke lese opplastet fil.', + 'Database uploaded successfully.' => 'Databasen er lastet opp.', + 'Task sent by email successfully.' => 'Oppgaven er sendt med e-post.', + 'There is no category in this project.' => 'Prosjektet har ingen kategorier.', + 'Send by email' => 'Send i e-post', + 'Create and send a comment by email' => 'Opprett og send en kommentar med e-post', + 'Subject' => 'Emne', + 'Upload the database' => 'Last opp databasen', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Du kan laste opp en tidligere nedlastet Sqlite database (Gzip-format).', + 'Database file' => 'Databasefil', + 'Upload' => 'Last opp', + 'Your project must have at least one active swimlane.' => 'Prosjektet må ha minst en aktiv svømmebane', + 'Project: %s' => 'Prosjekt: %s', + 'Automatic action not found: "%s"' => 'Finner ingen automatisk handling: "%s"', + '%d projects' => '%d prosjekter', + '%d project' => '%d prosjekt', + 'There is no project.' => 'Ingen prosjekter', + 'Sort' => 'Sorter', + 'Project ID' => 'Prosjekt-ID', + 'Project name' => 'Prosjektnavn', + 'Public' => 'Felles', + 'Personal' => 'Privat', + '%d tasks' => '%d oppgaver', + '%d task' => '%d oppgave', + 'Task ID' => 'Oppgave-ID', + 'Assign automatically a color when due date is expired' => 'Tilordne farge automatisk når fristen utløper', + 'Total score in this column across all swimlanes' => 'Totalt resultat for denne kolonnen i alle svømmebaner', + 'HRK - Kuna' => 'HRK - Kroatiske kuna', + 'ARS - Argentine Peso' => 'ARS - Argentinske pesos', + 'COP - Colombian Peso' => 'COP - Columbianske pesos', + '%d groups' => '%d grupper', + '%d group' => '%d gruppe', + 'Group ID' => 'Gruppe-ID', + 'External ID' => 'Ekstern ID', + '%d users' => '%d brukere', + '%d user' => '%d bruker', + 'Hide subtasks' => 'Skjul deloppgaver', + 'Show subtasks' => 'Vis deloppgaver', + 'Authentication Parameters' => 'Autensitering', + 'API Access' => 'API-tilgang', + 'No users found.' => 'Finner ingen brukere', + 'User ID' => 'Bruker-ID', + 'Notifications are activated' => 'Varslinger aktivert', + 'Notifications are disabled' => 'Varslinger deaktivert', + 'User disabled' => 'Bruker er deaktivert', + '%d notifications' => '%d varslinger', + '%d notification' => '%d varsling', + 'There is no external integration installed.' => 'Ingen ekstern interasjon er installert.', + 'You are not allowed to update tasks assigned to someone else.' => 'Du kan ikke endre oppgaver hvor en annen bruker er ansvarlig', + 'You are not allowed to change the assignee.' => 'Du har ikke rettigheter til å endre ansvarlig', + 'Task suppression is not permitted' => 'Endring av posisjon er ikke tillatt', + 'Changing assignee is not permitted' => 'Endring av ansvarlig er ikke tillatt', + 'Update only assigned tasks is permitted' => 'Du kan bare endre oppgaver du selv er ansvarlig for', + 'Only for tasks assigned to the current user' => 'Kun for oppgaver tilordnet aktiv bruker', + 'My projects' => 'Mine prosjekter', + 'You are not a member of any project.' => 'Du er ikke med i noen prosjekter', + 'My subtasks' => 'Mine deloppgaver', + '%d subtasks' => '%d deloppgaver', + '%d subtask' => '%d deloppgave', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Aktiv bruker kan kun flytte oppgaver mellom angitte kolonner', + '[DUPLICATE]' => 'DUPLISER', + 'DKK - Danish Krona' => 'DKK - Danske kroner', + 'Remove user from group' => 'Fjern bruker fra gruppen', + 'Assign the task to its creator' => 'Tildel oppgaven til den som har opprettet den', + 'This task was sent by email to "%s" with subject "%s".' => 'Denne oppgaven ble sendt på e-post til "%s" med emne "%s".', + 'Predefined Email Subjects' => 'Forhåndsdefinerte e-post emner', + 'Write one subject by line.' => 'Skriv et emne på hver linje', + 'Create another link' => 'Lag en ny lenke', + 'BRL - Brazilian Real' => 'BRL - Brasilianske real', + 'Add a new Kanboard task' => 'Lag ny oppgave', + 'Subtask not started' => 'Deloppgaven er ikke startet', + 'Subtask currently in progress' => 'Deloppgaver i prosess', + 'Subtask completed' => 'Deloppgave fullført', + 'Subtask added successfully.' => 'Deloppgave er lagt til', + '%d subtasks added successfully.' => '%d deloppgaver er lagt til', + 'Enter one subtask by line.' => 'Legg inn en deloppgave pr. rad', + 'Predefined Contents' => 'Forhåndsdefinert innhold', + 'Predefined contents' => 'Forhåndsdefinert innhold', + 'Predefined Task Description' => 'Forhåndsdefinert oppgavebeskrivelse', + 'Do you really want to remove this template? "%s"' => 'Ønsker du virkelig å slette denne malen? "%s"', + 'Add predefined task description' => 'Ny forhåndsdefinert oppgavebeskrivelse', + 'Predefined Task Descriptions' => 'Forhåndsdefinerte oppgavebeskrivelser', + 'Template created successfully.' => 'Malen er opprettet', + 'Unable to create this template.' => 'Kunne ikke opprette malen', + 'Template updated successfully.' => 'Malen er endret', + 'Unable to update this template.' => 'Kunne ikke endre malen', + 'Template removed successfully.' => 'Malen er slettet', + 'Unable to remove this template.' => 'Kunne ikke slette malen', + 'Template for the task description' => 'Mal for oppgavbeskrivelse', + 'The start date is greater than the end date' => 'Startdato er større en sluttdato', + 'Tags must be separated by a comma' => 'Etiketter må skilles med komma', + 'Only the task title is required' => 'Kun oppgavetittel er påkrevet', + 'Creator Username' => 'Opprettet av', + 'Color Name' => 'Farge', + 'Column Name' => 'Kolonne', + 'Swimlane Name' => 'Svømmebane', + 'Time Estimated' => 'Estimert tid', + 'Time Spent' => 'Medgått tid', + 'External Link' => 'Ekstern lenke', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'OBS Forsiktig: Denne funksjonen aktiverer iCal-strøm, RSS-strøm og offentlig tavlevisning!', + 'Stop the timer of all subtasks when moving a task to another column' => 'Stopp timeren for alle deloppgaver når en oppgave flyttes til en annen kolonne', + 'Subtask Title' => 'Deloppgave', + 'Add a subtask and activate the timer when moving a task to another column' => 'Opprett en deloppgave og aktiver timeren når en oppgave flyttes til en annen kolonne', + 'days' => 'dager', + 'minutes' => 'minutter', + 'seconds' => 'sekunder', + 'Assign automatically a color when preset start date is reached' => 'Tilordne farge automatisk på valgt startdato', + 'Move the task to another column once a predefined start date is reached' => 'Flytt oppgaven til en annen lolonne på valgt startdato', + 'This task is now linked to the task %s with the relation "%s"' => 'Oppgaven er nå lenket til oppgaven %s med relasjonen "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'Lenken med relasjonen "%s" til oppgaven %s er fjernet', + 'Custom Filter:' => 'Egendefinert filter', + 'Unable to find this group.' => 'Finner ikke gruppen', + '%s moved the task #%d to the column "%s"' => '%s flyttet oppgaven #%d til kolonnen "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => '%s flyttet oppgaven #%d til posisjon %d i kolonnen "%s"', + '%s moved the task #%d to the swimlane "%s"' => '%s flyttet oppgaven #%d til svømmebane "%s"', + '%sh spent' => '%st brukt', + '%sh estimated' => '%st estimert', + 'Select All' => 'Velg alle', + 'Unselect All' => 'Fravelg alle', + 'Apply action' => 'Utfør', + 'Move selected tasks to another column or swimlane' => 'Flytt merkede oppgaver til en annen kolonne', + 'Edit tasks in bulk' => 'Masseredigering av oppgaver', + 'Choose the properties that you would like to change for the selected tasks.' => 'Velg egenskapene du vil endre for valgte oppgaver.', + 'Configure this project' => 'Prosjekt', + 'Start now' => 'Start nå', + '%s removed a file from the task #%d' => '%s fjernet en fil fra oppgaven #%d', + 'Attachment removed from task #%d: %s' => 'Vedlegg fjernet fra oppgave #%d: %s', + 'No color' => 'Ingen farge', + 'Attachment removed "%s"' => 'Vedlegg fjernet "%s"', + '%s removed a file from the task %s' => '%s fjernet en fil fra oppgaven %s', + 'Move the task to another swimlane when assigned to a user' => 'Flytt oppgaven til en annen svømmebane når den tildeles en bruker', + 'Destination swimlane' => 'Mål-svømmebane', + 'Assign a category when the task is moved to a specific swimlane' => 'Tildel en kategori når oppgaven flyttes til en bestemt svømmebane', + 'Move the task to another swimlane when the category is changed' => 'Flytt oppgaven til en annen svømmebane når kategorien endres', + 'Reorder this column by priority (ASC)' => 'Sorter denne kolonnen etter prioritet (stigende)', + 'Reorder this column by priority (DESC)' => 'Sorter denne kolonnen etter prioritet (synkende)', + 'Reorder this column by assignee and priority (ASC)' => 'Sorter denne kolonnen etter ansvarlig og prioritet (stigende)', + 'Reorder this column by assignee and priority (DESC)' => 'Sorter denne kolonnen etter ansvarlig og prioritet (synkende)', + 'Reorder this column by assignee (A-Z)' => 'Sorter denne kolonnen etter ansvarlig (A–Å)', + 'Reorder this column by assignee (Z-A)' => 'Sorter denne kolonnen etter ansvarlig (Å–A)', + 'Reorder this column by due date (ASC)' => 'Sorter denne kolonnen etter forfallsdato (stigende)', + 'Reorder this column by due date (DESC)' => 'Sorter denne kolonnen etter forfallsdato (synkende)', + 'Reorder this column by id (ASC)' => 'Sorter denne kolonnen etter ID (stigende)', + 'Reorder this column by id (DESC)' => 'Sorter denne kolonnen etter ID (synkende)', + '%s moved the task #%d "%s" to the project "%s"' => '%s flyttet oppgaven #%d "%s" til prosjektet "%s"', + 'Task #%d "%s" has been moved to the project "%s"' => 'Oppgave #%d "%s" har blitt flyttet til prosjektet "%s"', + 'Move the task to another column when the due date is less than a certain number of days' => 'Flytt oppgaven til en annen kolonne når forfallsdatoen er mindre enn et bestemt antall dager', + 'Automatically update the start date when the task is moved away from a specific column' => 'Oppdater startdatoen automatisk når oppgaven flyttes fra en bestemt kolonne', + 'HTTP Client:' => 'HTTP-klient:', + 'Assigned' => 'Tildelt', + 'Task limits apply to each swimlane individually' => 'Oppgavegrenser gjelder for hver svømmebane individuelt', + 'Column task limits apply to each swimlane individually' => 'Kolonneoppgavegrenser gjelder for hver svømmebane individuelt', + 'Column task limits are applied to each swimlane individually' => 'Kolonneoppgavegrenser brukes på hver svømmebane individuelt', + 'Column task limits are applied across swimlanes' => 'Kolonneoppgavegrenser brukes på tvers av svømmebaner', + 'Task limit: ' => 'Oppgavegrense: ', + 'Change to global tag' => 'Endre til global tag', + 'Do you really want to make the tag "%s" global?' => 'Vil du virkelig gjøre taggen "%s" global?', + 'Enable global tags for this project' => 'Aktiver globale tagger for dette prosjektet', + 'Group membership(s):' => 'Gruppemedlemskap:', + '%s is a member of the following group(s): %s' => '%s er medlem av følgende gruppe(r): %s', + '%d/%d group(s) shown' => '%d/%d gruppe(r) vist', + 'Subtask creation or modification' => 'Opprettelse eller endring av deloppgave', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Tildel oppgaven til en bestemt bruker når den flyttes til en bestemt svømmebane', + 'Comment' => 'Kommentar', + 'Collapse vertically' => 'Skjul vertikalt', + 'Expand vertically' => 'Utvid vertikalt', + 'MXN - Mexican Peso' => 'MXN - Meksikansk peso', + 'Estimated vs actual time per column' => 'Estimert vs faktisk tid per kolonne', + 'HUF - Hungarian Forint' => 'HUF - Ungarsk forint', + 'XBT - Bitcoin' => 'XBT - Bitcoin', + 'You must select a file to upload as your avatar!' => 'Du må velge en fil for å laste opp som din avatar!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'Filen du lastet opp er ikke et gyldig bilde! (Kun *.gif, *.jpg, *.jpeg og *.png er tillatt!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Angi forfallsdato automatisk når oppgaven flyttes fra en bestemt kolonne', + 'No other projects found.' => 'Ingen andre prosjekter funnet.', + 'Tasks copied successfully.' => 'Oppgaver kopiert vellykket.', + 'Unable to copy tasks.' => 'Kan ikke kopiere oppgaver.', + 'Theme' => 'Tema', + 'Theme:' => 'Tema:', + 'Light theme' => 'Lyst tema', + 'Dark theme' => 'Mørkt tema', + 'Automatic theme - Sync with system' => 'Automatisk tema - Synkroniser med systemet', + 'Application managers or more' => 'Applikasjonsadministratorer eller høyere', + 'Administrators' => 'Administratorer', + 'Visibility:' => 'Synlighet:', + 'Standard users' => 'Standardbrukere', + 'Visibility is required' => 'Synlighet er påkrevd', + 'The visibility should be an app role' => 'Synligheten bør være en applikasjonsrolle', + 'Reply' => 'Svar', + '%s wrote: ' => '%s skrev: ', + 'Number of visible tasks in this column and swimlane' => 'Antall synlige oppgaver i denne kolonnen og svømmebanen', + 'Number of tasks in this swimlane' => 'Antall oppgaver i denne svømmebanen', + 'Unable to find another subtask in progress, you can close this window.' => 'Kan ikke finne en annen deloppgave i arbeid, du kan lukke dette vinduet.', + 'This theme is invalid' => 'Dette temaet er ugyldig', + 'This role is invalid' => 'Denne rollen er ugyldig', + 'This timezone is invalid' => 'Denne tidssonen er ugyldig', + 'This language is invalid' => 'Dette språket er ugyldig', + 'This URL is invalid' => 'Denne URL-en er ugyldig', + 'Date format invalid' => 'Ugyldig datoformat', + 'Time format invalid' => 'Ugyldig tidsformat', + 'Invalid Mail transport' => 'Ugyldig e-posttransport', + 'Color invalid' => 'Ugyldig farge', + 'This value must be greater or equal to %d' => 'Denne verdien må være større enn eller lik %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Legg til en BOM i begynnelsen av filen (påkrevd for Microsoft Excel)', + 'Just add these tag(s)' => 'Bare legg til disse taggene', + 'Remove internal link(s)' => 'Fjern interne lenker', + 'Import tasks from another project' => 'Importer oppgaver fra et annet prosjekt', + 'Select the project to copy tasks from' => 'Velg prosjektet du vil kopiere oppgaver fra', + 'The total maximum allowed attachments size is %sB.' => 'Den totale maksimalt tillatte vedleggsstørrelsen er %sB.', + 'Add attachments' => 'Legg til vedlegg', + 'Task #%d "%s" is overdue' => 'Oppgaven #%d "%s" er forsinket', + 'Enable notifications by default for all new users' => 'Aktiver varslinger som standard for alle nye brukere', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Tilordne oppgaven til dens oppretter for bestemte kolonner hvis ingen ansvarlig er satt manuelt', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Tilordne en oppgave til innlogget bruker ved kolonneendring til angitt kolonne hvis ingen bruker er tilordnet', +]; diff --git a/app/Locale/nl_NL/translations.php b/app/Locale/nl_NL/translations.php new file mode 100644 index 0000000..3bce85c --- /dev/null +++ b/app/Locale/nl_NL/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => '.', + 'None' => 'Geen', + 'Edit' => 'Bewerken', + 'Remove' => 'Verwijderen', + 'Yes' => 'Ja', + 'No' => 'Nee', + 'cancel' => 'annuleren', + 'or' => 'of', + 'Yellow' => 'Geel', + 'Blue' => 'Blauw', + 'Green' => 'Groen', + 'Purple' => 'Paars', + 'Red' => 'Rood', + 'Orange' => 'Oranje', + 'Grey' => 'Grijs', + 'Brown' => 'Bruin', + 'Deep Orange' => 'Dieporanje', + 'Dark Grey' => 'Donkergrijs', + 'Pink' => 'Roze', + 'Teal' => 'Groenblauw', + 'Cyan' => 'Cyaan', + 'Lime' => 'Limoen', + 'Light Green' => 'Lichtgroen', + 'Amber' => 'Amber', + '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"?' => 'Wil je deze gebruiker echt verwijderen: "%s" ?', + 'All users' => 'Alle gebruikers', + 'Username' => 'Gebruikersnaam', + 'Password' => 'Wachtwoord', + 'Administrator' => 'Administrator', + 'Sign in' => 'Inloggen', + 'Users' => 'Gebruikers', + 'Forbidden' => 'Geweigerd', + 'Access Forbidden' => 'Toegang geweigerd', + 'Edit user' => 'Gebruiker bewerken', + 'Logout' => 'Uitloggen', + 'Bad username or password' => 'Verkeerde gebruikersnaam of wachtwoord', + 'Edit project' => 'Project bewerken', + 'Name' => 'Naam', + 'Projects' => 'Projecten', + 'No project' => 'Geen project', + 'Project' => 'Project', + 'Status' => 'Status', + 'Tasks' => 'Taken', + 'Board' => 'Bord', + 'Actions' => 'Acties', + 'Inactive' => 'Inactief', + 'Active' => 'Actief', + 'Unable to update this board.' => 'Kan dit bord niet bijwerken.', + 'Disable' => 'Deactiveren', + 'Enable' => 'Activeren', + 'New project' => 'Nieuw project', + 'Do you really want to remove this project: "%s"?' => 'Wil je dit project echt verwijderen: "%s"?', + 'Remove project' => 'Project verwijderen', + 'Edit the board for "%s"' => 'Bord bewerken voor "%s"', + 'Add a new column' => 'Kolom toevoegen', + 'Title' => 'Titel', + 'Assigned to %s' => 'Toegewezen aan %s', + 'Remove a column' => 'Kolom verwijderen', + 'Unable to remove this column.' => 'Kan deze kolom niet verwijderen.', + 'Do you really want to remove this column: "%s"?' => 'Wil je deze kolom echt verwijderen: "%s"?', + 'Settings' => 'Instellingen', + 'Application settings' => 'Applicatie-instellingen', + 'Language' => 'Taal', + 'Webhook token:' => 'Webhook token:', + 'API token:' => 'API token:', + 'Database size:' => 'Database-grootte:', + 'Download the database' => 'Database downloaden', + 'Optimize the database' => 'Database optimaliseren', + '(VACUUM command)' => '(VACUUM commando)', + '(Gzip compressed Sqlite file)' => '(Gzip ingepakt Sqlite-bestand)', + 'Close a task' => 'Taak sluiten', + '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"?' => 'Wil je deze taak echt openen: "%s"?', + 'Back to the board' => 'Terug naar het bord', + 'There is nobody assigned' => 'Er is niemand toegewezen', + 'Column on the board:' => 'Kolom op het bord:', + '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 tekens', + 'The minimum length is %d characters' => 'De minimale lengte is %d tekens', + '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 user id is required' => 'De gebruikers-ID is verplicht', + 'Passwords don\'t match' => 'De wachtwoorden komen niet overeen', + 'The confirmation is required' => 'De bevestiging is verplicht', + 'The project is required' => 'Het project is verplicht', + 'The id is required' => 'De ID is verplicht', + 'The project id is required' => 'De project-ID is verplicht', + 'The project name is required' => 'De projectnaam is verplicht', + 'The title is required' => 'De titel is verplicht', + 'Settings saved successfully.' => 'Instellingen succesvol opgeslagen.', + 'Unable to save your settings.' => 'Kan je instellingen niet opslaan.', + 'Database optimization done.' => 'Database optimaliseren voltooid.', + 'Your project has been created successfully.' => 'Je project is succesvol aangemaakt.', + 'Unable to create your project.' => 'Kan je project niet aanmaken.', + 'Project updated successfully.' => 'Project succesvol bijgewerkt.', + 'Unable to update this project.' => 'Kan dit project niet bijwerken.', + 'Unable to remove this project.' => 'Kan dit project niet verwijderen.', + 'Project removed successfully.' => 'Project succesvol verwijderd.', + 'Project activated successfully.' => 'Project succesvol geactiveerd.', + 'Unable to activate this project.' => 'Kan dit project niet activeren.', + 'Project disabled successfully.' => 'Project uitschakelen succesvol.', + 'Unable to disable this project.' => 'Kan dit project niet uitschakelen.', + 'Unable to open this task.' => 'Kan deze taak niet openen.', + 'Task opened successfully.' => 'Taak succesvol geopend.', + 'Unable to close this task.' => 'Kan deze taak niet sluiten.', + 'Task closed successfully.' => 'Taak succesvol gesloten.', + 'Unable to update your task.' => 'Kan je taak niet bijwerken.', + 'Task updated successfully.' => 'Taak succesvol bijgewerkt.', + 'Unable to create your task.' => 'Kan je taak niet aanmaken.', + 'Task created successfully.' => 'Taak succesvol aangemaakt.', + 'User created successfully.' => 'Gebruiker succesvol aangemaakt.', + 'Unable to create your user.' => 'Kan je gebruiker niet aanmaken.', + 'User updated successfully.' => 'Gebruiker succesvol bijgewerkt', + 'User removed successfully.' => 'Gebruiker succesvol verwijderd.', + 'Unable to remove this user.' => 'Kan deze gebruiker niet verwijderen.', + 'Board updated successfully.' => 'Bord succesvol bijgewerkt.', + 'Ready' => 'Klaar', + 'Backlog' => 'In afwachting', + 'Work in progress' => 'In uitvoering', + 'Done' => 'Afgerond', + 'Application version:' => 'Applicatieversie:', + 'Id' => 'Id', + 'Public link' => 'Publieke link', + 'Timezone' => 'Tijdzone', + 'Sorry, I didn\'t find this information in my database!' => 'Sorry, ik heb deze informatie niet gevonden in de database!', + 'Page not found' => 'Pagina niet gevonden', + 'Complexity' => 'Complexiteit', + 'Task limit' => 'Taaklimiet', + 'Task count' => 'Aantal taken', + 'User' => 'Gebruiker', + 'Comments' => 'Commentaar', + 'Comment is required' => 'Commentaar is verplicht', + 'Comment added successfully.' => 'Commentaar succesvol toegevoegd.', + 'Unable to create your comment.' => 'Kan je commentaar niet aanmaken.', + 'Due Date' => 'Vervaldag', + 'Invalid date' => 'Ongeldige datum', + 'Automatic actions' => 'Geautomatiseerde acties', + 'Your automatic action has been created successfully.' => 'Geautomatiseerde actie succesvol aangemaakt.', + 'Unable to create your automatic action.' => 'Kan je automatische actie niet aanmaken.', + 'Remove an action' => 'Een actie verwijderen', + 'Unable to remove this action.' => 'Kan deze actie niet verwijderen.', + 'Action removed successfully.' => 'Actie succesvol verwijderd.', + 'Automatic actions for the project "%s"' => 'Geautomatiseerde acties voor project "%s"', + 'Add an action' => 'Een actie toevoegen', + 'Event name' => 'Naam gebeurtenis', + 'Action' => 'Actie', + 'Event' => 'Gebeurtenis', + 'When the selected event occurs execute the corresponding action.' => 'Wanneer de geselecteerde gebeurtenis plaatsvindt, voer dan de bijbehorende actie uit.', + 'Next step' => 'Volgende stap', + 'Define action parameters' => 'Bepaal actieparameters', + 'Do you really want to remove this action: "%s"?' => 'Wil je deze actie echt verwijderen?: "%s"?', + 'Remove an automatic action' => 'Een automatische actie verwijderen', + 'Assign the task to a specific user' => 'De taak toewijzen aan een specifieke gebruiker', + 'Assign the task to the person who does the action' => 'De taak toewijzen aan de persoon die de actie uitvoert', + 'Duplicate the task to another project' => 'De taak dupliceren in een ander project', + 'Move a task to another column' => 'Een taak verplaatsen naar een andere kolom', + 'Task modification' => 'Aanpassen taak', + 'Task creation' => 'Aanmaken taak', + 'Closing a task' => 'Een taak sluiten', + 'Assign a color to a specific user' => 'Een kleur toewijzen aan een specifieke gebruiker', + 'Position' => 'Positie', + 'Duplicate to project' => 'Dupliceren in een ander project', + 'Duplicate' => 'Dupliceren', + 'Link' => 'Linken', + 'Comment updated successfully.' => 'Commentaar succesvol bijgewerkt.', + 'Unable to update your comment.' => 'Kan je commentaar niet bijwerken.', + 'Remove a comment' => 'Commentaar verwijderen', + 'Comment removed successfully.' => 'Commentaar succesvol verwijderd.', + 'Unable to remove this comment.' => 'Kan dit commentaar niet verwijderen.', + 'Do you really want to remove this comment?' => 'Wil je dit commentaar echt verwijderen?', + 'Current password for the user "%s"' => 'Huidig wachtwoord voor de gebruiker "%s"', + 'The current password is required' => 'Huidig wachtwoord is verplicht', + 'Wrong password' => 'Verkeerd wachtwoord', + 'Unknown' => 'Onbekend', + 'Last logins' => 'Laatste logins', + 'Login date' => 'Logindatum', + 'Authentication method' => 'Authenticatiemethode', + 'IP address' => 'IP-adres', + 'User agent' => 'User agent', + 'Persistent connections' => 'Persistente connecties', + 'No session.' => 'Geen sessie.', + 'Expiration date' => 'Verloopdatum', + 'Remember Me' => 'Onthoud mij', + 'Creation date' => 'Aanmaakdatum', + 'Everybody' => 'Iedereen', + 'Open' => 'Open', + 'Closed' => 'Gesloten', + 'Search' => 'Zoeken', + 'Nothing found.' => 'Niets gevonden.', + 'Due date' => 'Vervaldatum', + 'Description' => 'Omschrijving', + '%d comments' => '%d commentaren', + '%d comment' => '%d commentaar', + 'Email address invalid' => 'Ongeldig e-mailadres', + 'Your external account is not linked anymore to your profile.' => 'Je externe account is niet meer gekoppeld aan je profiel.', + 'Unable to unlink your external account.' => 'Kan je externe account niet ontkoppelen.', + 'External authentication failed' => 'Externe authenticatie mislukt', + 'Your external account is linked to your profile successfully.' => 'Je externe account is succesvol gekoppeld aan je profiel.', + 'Email' => 'E-mail', + 'Task removed successfully.' => 'Taak succesvol verwijderd.', + 'Unable to remove this task.' => 'Kan deze taak niet verwijderen.', + 'Remove a task' => 'Een taak verwijderen', + 'Do you really want to remove this task: "%s"?' => 'Wil je deze taak echt verwijderen "%s"?', + 'Assign automatically a color based on a category' => 'Automatisch een kleur toewijzen op basis van een categorie', + 'Assign automatically a category based on a color' => 'Automatisch een categorie toewijzen op basis van een kleur', + 'Task creation or modification' => 'Taak aanmaken of wijzigen', + 'Category' => 'Categorie', + 'Category:' => 'Categorie:', + 'Categories' => 'Categorieën', + 'Your category has been created successfully.' => 'Je categorie is succesvol aangemaakt.', + 'This category has been updated successfully.' => 'Je categorie is succesvol bijgewerkt.', + 'Unable to update this category.' => 'Kan deze categorie niet bijwerken.', + 'Remove a category' => 'Categorie verwijderen', + 'Category removed successfully.' => 'Categorie succesvol verwijderd.', + 'Unable to remove this category.' => 'Kan deze categorie niet verwijderen.', + 'Category modification for the project "%s"' => 'Categorie aanpassen voor project "%s"', + 'Category Name' => 'Categorienaam', + 'Add a new category' => 'Categorie toevoegen', + 'Do you really want to remove this category: "%s"?' => 'Wil je deze categorie echt verwijderen: "%s"?', + 'All categories' => 'Alle categorieën', + 'No category' => 'Geen categorie', + 'The name is required' => 'De naam is verplicht', + 'Remove a file' => 'Een bestand verwijderen', + 'Unable to remove this file.' => 'Kan dit bestand niet verwijderen.', + 'File removed successfully.' => 'Bestand succesvol verwijdered.', + 'Attach a document' => 'Document toevoegen', + 'Do you really want to remove this file: "%s"?' => 'Wil je dit bestand echt verwijderen: "%s"?', + 'Attachments' => 'Bijlages', + 'Edit the task' => 'De taak bewerken', + 'Add a comment' => 'Commentaar toevoegen', + 'Edit a comment' => 'Commentaar bewerken', + 'Summary' => 'Samenvatting', + 'Time tracking' => 'Tijdschrijven', + 'Estimate:' => 'Schatting:', + 'Spent:' => 'Besteed:', + 'Do you really want to remove this sub-task?' => 'Wil je deze subtaak echt verwijderen?', + 'Remaining:' => 'Resterend:', + 'hours' => 'uren', + 'estimated' => 'geschat', + 'Sub-Tasks' => 'Subtaken', + 'Add a sub-task' => 'Een subtaak toevoegen', + 'Original estimate' => 'Orginele schatting', + 'Create another sub-task' => 'Nog een subtaak toevoegen', + 'Time spent' => 'Bestede tijd', + 'Edit a sub-task' => 'Subtaak bewerken', + '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.' => 'Kan deze subtaak niet verwijderen.', + 'Sub-task updated successfully.' => 'Subtaak succesvol bijgewerkt.', + 'Unable to update your sub-task.' => 'Kan deze subtaak niet bijwerken.', + 'Unable to create your sub-task.' => 'Kan deze subtaak niet aanmaken.', + 'Maximum size: ' => 'Maximale grootte: ', + 'Display another project' => 'Een ander project weergeven', + 'Created by %s' => 'Aangemaakt door %s', + 'Tasks Export' => 'Taken exporteren', + 'Start Date' => 'Startdatum', + 'Execute' => 'Uitvoeren', + 'Task Id' => 'Taak-ID', + 'Creator' => 'Aangemaakt door', + 'Modification date' => 'Wijzigingsdatum', + 'Completion date' => 'Afgerond op', + 'Clone' => 'Kloon', + 'Project cloned successfully.' => 'Project succesvol gekloond.', + 'Unable to clone this project.' => 'Kan dit project niet klonen.', + 'Enable email notifications' => 'E-mailmeldingen inschakelen', + 'Task position:' => 'Taak positie:', + 'The task #%d has been opened.' => 'Taak #%d is geopend.', + 'The task #%d has been closed.' => 'Taak #%d is gesloten.', + 'Sub-task updated' => 'Subtaak bijgewerkt', + 'Title:' => 'Titel:', + 'Status:' => 'Status:', + 'Assignee:' => 'Toegewezene:', + 'Time tracking:' => 'Tijdschrijven:', + 'New sub-task' => 'Nieuwe subtaak', + 'New attachment added "%s"' => 'Nieuwe bijlage toegevoegd "%s"', + 'New comment posted by %s' => 'Nieuw commentaar geplaatst door "%s"', + 'New comment' => 'Nieuw commentaar', + 'Comment updated' => 'Commentaar bijgewerkt', + 'New subtask' => 'Nieuwe subtaak', + 'I only want to receive notifications for these projects:' => 'Ik wil meldingen ontvangen van de volgende projecten:', + 'view the task on Kanboard' => 'taak bekijken op Kanboard', + 'Public access' => 'Publieke toegang', + 'Disable public access' => 'Publieke toegang uitschakelen', + 'Enable public access' => 'Publieke toegang inschakelen', + 'Public access disabled' => 'Publieke toegang uitgeschakeld', + 'Move the task to another project' => 'Taak verplaatsen naar een ander project', + 'Move to project' => 'Verplaats naar een ander project', + 'Do you really want to duplicate this task?' => 'Wil je deze taak echt dupliceren?', + 'Duplicate a task' => 'Taak dupliceren', + 'External accounts' => 'Externe accounts', + 'Account type' => 'Account type', + 'Local' => 'Lokaal', + 'Remote' => 'Remote', + 'Enabled' => 'Actief', + 'Disabled' => 'Inactief', + 'Login:' => 'Gebruikersnaam:', + 'Full Name:' => 'Naam:', + 'Email:' => 'E-mail:', + 'Notifications:' => 'Meldingen:', + 'Notifications' => 'Meldingen', + 'Account type:' => 'Accounttype:', + 'Edit profile' => 'Profiel aanpassen', + 'Change password' => 'Wachtwoord wijzigen', + 'Password modification' => 'Wijziging wachtwoord', + 'External authentications' => 'Externe authenticatie', + 'Never connected.' => 'Nooit verbonden.', + 'No external authentication enabled.' => 'Geen externe verificatie ingeschakeld.', + 'Password modified successfully.' => 'Wachtwoord succesvol aangepast.', + 'Unable to change the password.' => 'Kan het wachtwoord niet wijzigen.', + 'Change category' => 'Categorie veranderen', + '%s updated the task %s' => '%s heeft taak %s bijgewerkt', + '%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 bijgewerkt 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 bijgewerkt voor taak %s', + '%s commented the task %s' => '%s heeft een commentaar geplaatst voor taak %s', + '%s\'s activity' => 'Activiteiten van %s', + 'RSS feed' => 'RSS-feed', + '%s updated a comment on the task #%d' => '%s heeft een commentaar bijgewerkt 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 bijgewerkt 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 bijgewerkt', + '%s created the task #%d' => '%s heeft taak %d aangemaakt', + '%s closed the task #%d' => '%s heeft taak %d gesloten', + '%s opened the task #%d' => '%s heeft taak %d geopend', + 'Activity' => 'Activiteit', + 'Default values are "%s"' => 'Standaardwaarden zijn "%s"', + 'Default columns for new projects (Comma-separated)' => 'Standaardkolommen voor nieuw projecten (komma-gescheiden)', + 'Task assignee change' => 'Taak toegewezene wijziging', + '%s changed the assignee of the task #%d to %s' => '%s heeft de toegewezene voor taak %d gewijzigd in %s', + '%s changed the assignee of the task %s to %s' => '%s heeft de toegewezene voor taak %s gewijzigd in %s', + 'New password for the user "%s"' => 'Nieuw wachtwoord voor de gebruiker "%s"', + 'Choose an event' => 'Kies een gebeurtenis', + 'Create a task from an external provider' => 'Maak een taak aan vanuit een externe provider', + 'Change the assignee based on an external username' => 'De toewijzing wijzigen op basis van een externe gebruikersnaam', + 'Change the category based on an external label' => 'Verander de categorie op basis van een extern label', + 'Reference' => 'Referentie', + 'Label' => 'Label', + 'Database' => 'Database', + 'About' => 'Over', + 'Database driver:' => 'Database-driver:', + 'Board settings' => 'Bord-instellingen', + 'Webhook settings' => 'Webhook-instellingen', + 'Reset token' => 'Token resetten', + 'API endpoint:' => 'API-endpoint:', + 'Refresh interval for personal board' => 'Verversingsinterval voor persoonlijk bord', + 'Refresh interval for public board' => 'Verversingsinterval voor publiek bord', + '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 nemen dat een taak onlangs is gewijzigd (0 om uit te schakelen, standaard 2 dagen)', + 'Frequency in second (60 seconds by default)' => 'Frequentie in seconden (standaard 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', + 'Token regenerated.' => 'Token opnieuw gegenereerd.', + 'Date format' => 'Datumformaat', + 'ISO format is always accepted, example: "%s" and "%s"' => 'ISO-formaat wordt altijd geaccepteerd, bijvoorbeeld: "%s" en "%s"', + 'New personal project' => 'Nieuw persoonlijk project', + 'This project is personal' => 'Dit project is persoonlijk', + 'Add' => 'Toevoegen', + 'Start date' => 'Startdatum', + 'Time estimated' => 'Geschatte tijd', + 'There is nothing assigned to you.' => 'Er is niets aan jou toegewezen.', + 'My tasks' => 'Mijn taken', + 'Activity stream' => 'Activiteiten', + 'Dashboard' => 'Dashboard', + 'Confirmation' => 'Bevestiging', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Create a comment from an external provider' => 'Commentaar toevoegen van een externe provider', + 'Project management' => 'Projectmanagement', + 'Columns' => 'Kolommen', + 'Task' => 'Taak', + 'Percentage' => 'Percentage', + 'Number of tasks' => 'Aantal taken', + 'Task distribution' => 'Distributie van taken', + 'Analytics' => 'Analytics', + 'Subtask' => 'Subtaak', + 'User repartition' => 'Gebruikersverdeling', + 'Clone this project' => 'Dit project klonen', + 'Column removed successfully.' => 'Kolom succesvol verwijderd.', + 'Not enough data to show the graph.' => 'Niet genoeg data om de grafiek te laten zien.', + 'Previous' => 'Vorige', + 'The id must be an integer' => 'De ID moet een integer zijn', + 'The project id must be an integer' => 'De project-ID moet een integer zijn', + 'The status must be an integer' => 'De status moet een integer zijn', + 'The subtask id is required' => 'De ID van de subtaak is verplicht', + 'The subtask id must be an integer' => 'De ID van de subtaak moet een integer zijn', + 'The task id is required' => 'De ID van de taak is verplicht', + 'The task id must be an integer' => 'De ID van de taak moet een integer zijn', + 'The user id must be an integer' => 'De 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.' => 'Kan deze taak niet aanmaken.', + 'Cumulative flow diagram' => 'Cumulatief stroomdiagram', + 'Daily project summary' => 'Dagelijks projectoverzicht', + 'Daily project summary export' => 'Export van het dagelijkse projectoverzicht', + 'Exports' => 'Exports', + 'This export contains the number of tasks per column grouped per day.' => 'Deze export bevat het aantal taken per kolom, gegroepeerd per dag.', + 'Active swimlanes' => 'Actieve swimlanes', + 'Add a new swimlane' => 'Nieuwe swimlane toevoegen', + 'Default swimlane' => 'Standaard swimlane', + 'Do you really want to remove this swimlane: "%s"?' => 'Wil je deze swimlane echt verwijderen: "%s"?', + 'Inactive swimlanes' => 'Inactieve swimlanes', + 'Remove a swimlane' => 'Een swimlane verwijderen', + 'Swimlane modification for the project "%s"' => 'Swimlane aanpassing voor project "%s"', + 'Swimlane removed successfully.' => 'Swimlane succesvol verwijderd.', + 'Swimlanes' => 'Swimlanes', + 'Swimlane updated successfully.' => 'Swimlane succesvol bijgewerkt.', + 'Unable to remove this swimlane.' => 'Kan deze swimlane niet verwijderen.', + 'Unable to update this swimlane.' => 'Kan deze swimlane niet bijwerken.', + 'Your swimlane has been created successfully.' => 'Swimlane succesvol aangemaakt.', + 'Example: "Bug, Feature Request, Improvement"' => 'Voorbeeld: "Bug, Feature Request, Improvement"', + 'Default categories for new projects (Comma-separated)' => 'Standaardcategorieën voor nieuwe projecten (komma-gescheiden)', + 'Integrations' => 'Integraties', + 'Integration with third-party services' => 'Integratie met derde-partij-services', + 'Subtask Id' => 'Subtaak-ID', + 'Subtasks' => 'Subtaken', + 'Subtasks Export' => 'Subtaken exporteren', + 'Task Title' => 'Taaktitel', + 'Untitled' => 'Geen titel', + 'Application default' => 'Standaard voor applicatie', + 'Language:' => 'Taal:', + 'Timezone:' => 'Tijdzone:', + 'All columns' => 'Alle kolommen', + 'Next' => 'Volgende', + '#%d' => '#%d', + 'All swimlanes' => 'Alle swimlanes', + 'All colors' => 'Alle kleuren', + 'Moved to column %s' => 'Verplaatst naar kolom %s', + 'User dashboard' => 'Gebruikersdashboard', + 'Allow only one subtask in progress at the same time for a user' => 'Een gebruiker mag maar één subtaak tegelijk uitvoeren', + 'Edit column "%s"' => 'Kolom "%s" bewerken', + 'Select the new status of the subtask: "%s"' => 'Selecteer de nieuwe status voor de subtaak: "%s"', + 'Subtask timesheet' => 'Subtaak-timesheet', + 'There is nothing to show.' => 'Er is niets om te laten zien.', + 'Time Tracking' => 'Tijdschrijven', + 'You already have one subtask in progress' => 'Je hebt al een subtaak in behandeling', + 'Which parts of the project do you want to duplicate?' => 'Welke onderdelen van het project wil je dupliceren?', + 'Disallow login form' => 'Inlogformulier blokkeren', + 'Start' => 'Start', + 'End' => 'Eind', + 'Task age in days' => 'Leeftijd van de taak in dagen', + 'Days in this column' => 'Dagen in deze kolom', + '%dd' => '%dd', + 'Add a new link' => 'Nieuwe link toevoegen', + 'Do you really want to remove this link: "%s"?' => 'Wil je deze link echt verwijderen: "%s"?', + 'Do you really want to remove this link with task #%d?' => 'Wil je deze link met taak %d echt verwijderen?', + 'Field required' => 'Veld verplicht', + 'Link added successfully.' => 'Link succesvol toegevoegd.', + 'Link updated successfully.' => 'Link succesvol bijgewerkt.', + 'Link removed successfully.' => 'Link succesvol verwijderd.', + 'Link labels' => 'Linklabels', + 'Link modification' => 'Link-aanpassing', + 'Opposite label' => 'Tegenovergesteld label', + 'Remove a link' => 'Een link verwijderen', + '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.' => 'Kan je link niet aanmaken.', + 'Unable to update your link.' => 'Kan je link niet aanpassen.', + 'Unable to remove this link.' => 'Kan je link niet verwijderen.', + 'relates to' => 'is gerelateerd aan', + 'blocks' => 'blokkeert', + 'is blocked by' => 'wordt geblokkeerd door', + 'duplicates' => 'dupliceert', + 'is duplicated by' => 'wordt gedupliceerd door', + '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' => 'wordt gecorrigeerd door', + 'This task' => 'Deze taak', + '<1h' => '<1u', + '%dh' => '%du', + 'Expand tasks' => 'Taken uitklappen', + 'Collapse tasks' => 'Taken inklappen', + 'Expand/collapse tasks' => 'Taken in/uiklappen', + 'Close dialog box' => 'Venster sluiten', + 'Submit a form' => 'Een formulier indienen', + 'Board view' => 'Bordweergave', + 'Keyboard shortcuts' => 'Sneltoetsen', + 'Open board switcher' => 'Open bordwisselaar', + 'Application' => 'Applicatie', + 'Compact view' => 'Compacte weergave', + 'Horizontal scrolling' => 'Horizontaal scrollen', + 'Compact/wide view' => 'Compacte/breedbeeld-weergave', + 'Currency' => 'Valuta', + 'Personal project' => 'Persoonlijk project', + 'AUD - Australian Dollar' => 'AUD - Australische dollar', + 'CAD - Canadian Dollar' => 'CAD - Canadese dollar', + 'CHF - Swiss Francs' => 'CHF - Zwitserse frank', + 'Custom Stylesheet' => 'Aangepast stylesheet', + 'EUR - Euro' => 'EUR - Euro', + 'GBP - British Pound' => 'GBP - Britse pond', + 'INR - Indian Rupee' => 'INR - Indiaase rupee', + 'JPY - Japanese Yen' => 'JPY - Japanse yen', + 'NZD - New Zealand Dollar' => 'NZD - Nieuw-Zeelandse dollar', + 'PEN - Peruvian Sol' => 'PEN - Peruaanse sol', + 'RSD - Serbian dinar' => 'RSD - Servische dinar', + 'CNY - Chinese Yuan' => 'CNY - Chinese yuan', + 'USD - US Dollar' => 'USD - Amerikaanse dollar', + 'VES - Venezuelan Bolívar' => 'VES - Venezolaanse bolivar', + 'Destination column' => 'Doelkolom', + 'Move the task to another column when assigned to a user' => 'Verplaats de taak naar een andere kolom wanneer deze is toegewezen aan een gebruiker', + 'Move the task to another column when assignee is cleared' => 'Verplaats de taak naar een andere kolom wanneer de toewijzing is verwijderd', + 'Source column' => 'Bronkolom', + 'Transitions' => 'Transities', + 'Executer' => 'Uitvoerder', + 'Time spent in the column' => 'Tijd besteed in de kolom', + 'Task transitions' => 'Taak-transities', + 'Task transitions export' => 'Taak-transities exporteren', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Dit rapport bevat alle kolombewegingen voor elke taak met de datum, de gebruiker en de bestede tijd voor elke overgang.', + 'Currency rates' => 'Wisselkoersen', + 'Rate' => 'Koers', + 'Change reference currency' => 'Referentievaluta wijzigen', + 'Reference currency' => 'Referentievaluta', + 'The currency rate has been added successfully.' => 'De valutakoers is succesvol toegevoegd.', + 'Unable to add this currency rate.' => 'Kan deze valutakoers niet toevoegen.', + 'Webhook URL' => 'Webhook-URL', + '%s removed the assignee of the task %s' => '%s heeft de ontvanger van de taak %s verwijderd', + 'Information' => 'Informatie', + 'Check two factor authentication code' => 'Controleer de twee-factor authenticatiecode', + 'The two factor authentication code is not valid.' => 'De twee-factor authenticatiecode is niet geldig.', + 'The two factor authentication code is valid.' => 'De twee-factor authenticatiecode is geldig.', + 'Code' => 'Code', + 'Two factor authentication' => 'Twee-factor authenticatie', + 'This QR code contains the key URI: ' => 'Deze QR code bevat de sleutel-URI: ', + 'Check my code' => 'Mijn code controleren', + 'Secret key: ' => 'Geheime sleutel', + 'Test your device' => 'Je device testen', + 'Assign a color when the task is moved to a specific column' => 'Een kleur toewijzen wanneer de taak wordt verplaatst naar een specifieke kolom', + '%s via Kanboard' => '%s via Kanboard', + 'Burndown chart' => 'Burndown-grafiek', + 'This chart show the task complexity over the time (Work Remaining).' => 'Deze grafiek toont de complexiteit van de taak over de tijd (Work Remaining).', + 'Screenshot taken %s' => 'Screenshot gemaakt %s', + 'Add a screenshot' => 'Screenshot toevoegen', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Maak een screenshot en toets CTRL+V or ⌘+V om het hier te plakken.', + 'Screenshot uploaded successfully.' => 'Screenshot succesvol geüpload.', + 'SEK - Swedish Krona' => 'SEK - Zweedse kroon', + 'Identifier' => 'Identifier', + 'Disable two factor authentication' => 'Twee-factor authenticatie uitschakelen', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Wil je echt de twee-factor authenticatie uitschakelen voor deze gebruiker: "%s"?', + 'Edit link' => 'Link newerken', + 'Start to type task title...' => 'Begin met typen van de taaktitel...', + 'A task cannot be linked to itself' => 'Een taak kan niet aan zichzelf worden gekoppeld', + 'The exact same link already exists' => 'Dezelfde koppeling bestaat al', + 'Recurrent task is scheduled to be generated' => 'Terugkerende taak is gepland om te worden gegenereerd', + 'Score' => 'Score', + 'The identifier must be unique' => 'De identifier moet uniek zijn', + 'This linked task id doesn\'t exists' => 'Deze gekoppelde taak-ID bestaat niet', + 'This value must be alphanumeric' => 'Deze waarde moet alfanumeriek zijn', + 'Edit recurrence' => 'Herhaling bewerken', + 'Generate recurrent task' => 'Terugkerende taak genereren', + 'Trigger to generate recurrent task' => 'Trigger om terugkerende taak te genereren', + 'Factor to calculate new due date' => 'Factor om nieuwe vervaldatum te berekenen', + 'Timeframe to calculate new due date' => 'Tijdsbestek om nieuwe vervaldatum te berekenen', + 'Base date to calculate new due date' => 'Basisdatum om nieuwe vervaldatum te berekenen', + 'Action date' => 'Actiedatum', + 'Base date to calculate new due date: ' => 'Basisdatum voor het berekenen van de nieuwe vervaldatum: ', + 'This task has created this child task: ' => 'Deze taak heeft deze subtaak aangemaakt: ', + 'Day(s)' => 'Dag(en)', + 'Existing due date' => 'Bestaande vervaldatum', + 'Factor to calculate new due date: ' => 'Factor om nieuwe vervaldatum te berekenen: ', + 'Month(s)' => 'Maand(en)', + 'This task has been created by: ' => 'Deze taak is aangemaakt door: ', + 'Recurrent task has been generated:' => 'Terugkerende taak is gegenereerd:', + 'Timeframe to calculate new due date: ' => 'Tijdsbestek om nieuwe vervaldatum te berekenen: ', + 'Trigger to generate recurrent task: ' => 'Trigger om terugkerende taak te genereren: ', + 'When task is closed' => 'Wanneer taak is gesloten', + 'When task is moved from first column' => 'Wanneer de taak is verplaatst van de eerste kolom', + 'When task is moved to last column' => 'Wanneer de taak is verplaatst naar de laatste kolom', + 'Year(s)' => 'Jaar/Jaren', + 'Project settings' => 'Project instellingen', + 'Automatically update the start date' => 'Automatisch de begindatum bijwerken', + 'iCal feed' => 'iCal-feed', + 'Preferences' => 'Voorkeuren', + 'Security' => 'Beveiliging', + 'Two factor authentication disabled' => 'Twee-factor authenticatie uitgeschakeld', + 'Two factor authentication enabled' => 'Twee-factor authenticatie ingeschakeld', + 'Unable to update this user.' => 'Kan deze gebruiker niet bijwerken.', + 'There is no user management for personal projects.' => 'Er is geen gebruikersbeheer voor persoonlijke projecten.', + 'User that will receive the email' => 'Gebruiker die de e-mail ontvangt', + 'Email subject' => 'Onderwerp e-mail', + 'Date' => 'Datum', + 'Add a comment log when moving the task between columns' => 'Een commentaarlog toevoegen bij het verplaatsen van de taak tussen kolommen', + 'Move the task to another column when the category is changed' => 'Verplaats de taak naar een andere kolom wanneer de categorie wordt gewijzigd', + 'Send a task by email to someone' => 'Een taak per e-mail naar iemand verzenden', + 'Reopen a task' => 'Heropen een taak', + 'Notification' => 'Melding', + '%s moved the task #%d to the first swimlane' => '%s heeft de taak #%d naar de eerste swimlane verplaatst', + 'Swimlane' => 'Swimlane', + '%s moved the task %s to the first swimlane' => '%s heeft de taak %s naar de eerste swimlane verplaatst', + '%s moved the task %s to the swimlane "%s"' => '%s heeft taak %s naar swimlane "%s" verplaatst', + 'This report contains all subtasks information for the given date range.' => 'Dit rapport bevat alle subtakeninformatie voor het gegeven datumbereik.', + 'This report contains all tasks information for the given date range.' => 'Dit rapport bevat alle takeninformatie voor het gegeven datumbereik.', + 'Project activities for %s' => 'Projectactiviteiten voor %s', + 'view the board on Kanboard' => 'het bord bekijken op Kanboard', + 'The task has been moved to the first swimlane' => 'De taak is verplaatst naar de eerste swimlane', + 'The task has been moved to another swimlane:' => 'De taak is verplaatst naar een andere swimlane:', + 'New title: %s' => 'Nieuw titel: %s', + 'The task is not assigned anymore' => 'De taak is niet meer toegewezen', + 'New assignee: %s' => 'Nieuwe toegewezene: %s', + 'There is no category now' => 'Er is nu geen categorie', + 'New category: %s' => 'Nieuwe categorie: %s', + 'New color: %s' => 'Nieuwe kleur: %s', + 'New complexity: %d' => 'Nieuwe complexiteit: %d', + 'The due date has been removed' => 'De vervaldatum is verwijderd', + 'There is no description anymore' => 'Er is geen beschrijving meer', + 'Recurrence settings has been modified' => 'Herhalingsinstellingen zijn gewijzigd', + 'Time spent changed: %sh' => 'Bestede tijd gewijzigd: %su', + 'Time estimated changed: %sh' => 'Geschatte tijd gewijzigd: %su', + 'The field "%s" has been updated' => 'Het veld "%s" is bijgewerkt', + 'The description has been modified:' => 'De beschrijving is gewijzigd:', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Wil je echt de taak "%s" afsluiten, evenals alle subtaken?', + 'I want to receive notifications for:' => 'Ik wil notificaties ontvangen voor:', + 'All tasks' => 'Alle taken', + 'Only for tasks assigned to me' => 'Alleen voor taken die aan mij zijn toegewezen', + 'Only for tasks created by me' => 'Alleen voor taken die door mij zijn aangemaakt', + 'Only for tasks created by me and tasks assigned to me' => 'Alleen voor taken die door mij zijn aangemaakt en taken die aan mij zijn toegewezen', + '%%Y-%%m-%%d' => '%%d-%%m-%%Y', + 'Total for all columns' => 'Totaal voor alle kolommen', + 'You need at least 2 days of data to show the chart.' => 'Je hebt gegevens voor minstens 2 dagen nodig om de grafiek te tonen.', + '<15m' => '<15m', + '<30m' => '<30m', + 'Stop timer' => 'Stop timer', + 'Start timer' => 'Start timer', + 'My activity stream' => 'Mijn activiteiten', + 'Search tasks' => 'Zoek taken', + 'Reset filters' => 'Reset filters', + 'My tasks due tomorrow' => 'Mijn taken voor morgen', + 'Tasks due today' => 'Taken voor vandaag', + 'Tasks due tomorrow' => 'Taken voor morgen', + 'Tasks due yesterday' => 'Taken voor gisteren', + 'Closed tasks' => 'Gesloten taken', + 'Open tasks' => 'Open taken', + 'Not assigned' => 'Niet toegewezen', + 'View advanced search syntax' => 'Geavanceerde zoeksyntax bekijken', + 'Overview' => 'Overzicht', + 'Board/Calendar/List view' => 'Bord/Kalender/Lijst weergave', + 'Switch to the board view' => 'Overschakelen naar de bordweergave', + 'Switch to the list view' => 'Overschakelen naar de lijstweergave', + 'Go to the search/filter box' => 'Ga naar het zoek-/filtervak', + 'There is no activity yet.' => 'Er is nog geen activiteit.', + 'No tasks found.' => 'Geen taken gevonden.', + 'Keyboard shortcut: "%s"' => 'Sneltoets: "%s".', + 'List' => 'Lijst', + 'Filter' => 'Filter', + 'Advanced search' => 'Uitgebreid zoeken', + 'Example of query: ' => 'Voorbeeld van zoekopdracht: ', + 'Search by project: ' => 'Zoek op project', + 'Search by column: ' => 'Zoek op kolom', + 'Search by assignee: ' => 'Zoeken op toegewezene: ', + 'Search by color: ' => 'Zoek op kleur', + 'Search by category: ' => 'Zoek op categorie', + 'Search by description: ' => 'Zoek op omschrijving', + 'Search by due date: ' => 'Zoeken op vervaldatum: ', + 'Average time spent in each column' => 'Gemiddelde tijd besteed in elke kolom', + 'Average time spent' => 'Gemiddelde tijd besteed', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Deze grafiek toont de gemiddelde tijd gespendeerd in elke kolom voor de laatste %d taken.', + 'Average Lead and Cycle time' => 'Gemiddelde doorlooptijd en cyclustijd', + 'Average lead time: ' => 'Gemiddelde doorlooptijd: ', + 'Average cycle time: ' => 'Gemiddelde cyclustijd: ', + 'Cycle Time' => 'Cyclustijd', + 'Lead Time' => 'Doorlooptijd', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Deze grafiek toont de gemiddelde doorlooptijd en cyclustijd voor de laatste %d taken door de tijd.', + 'Average time into each column' => 'Gemiddelde tijd in elke kolom', + 'Lead and cycle time' => 'Doorlooptijd en cyclustijd', + 'Lead time: ' => 'Doorlooptijd: ', + 'Cycle time: ' => 'Cyclustijd: ', + 'Time spent in each column' => 'Tijd besteed in elke kolom', + 'The lead time is the duration between the task creation and the completion.' => 'De doorlooptijd is de duur tussen het aanmaken van de taak en de voltooiing.', + 'The cycle time is the duration between the start date and the completion.' => 'De cyclustijd is de tijd tussen de begindatum en de voltooiing.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Als de taak niet is afgesloten, wordt de huidige tijd gebruikt in plaats van de voltooiingsdatum.', + 'Set the start date automatically' => 'De begindatum automatisch instellen', + 'Edit Authentication' => 'Authenticatie bewerken', + 'Remote user' => 'Externe gebruiker', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Externe gebruikers slaan hun wachtwoord niet op in de Kanboard-database, voorbeelden: LDAP-, Google- en Github-accounts.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Als je het vakje "Inlogformulier uitzetten" aanvinkt, worden inloggegevens die zijn ingevoerd in het inlogformulier genegeerd.', + 'Default task color' => 'Standaard taakkleur', + 'This feature does not work with all browsers.' => 'Deze functie werkt niet met alle browsers.', + 'There is no destination project available.' => 'Er is geen doelproject beschikbaar.', + 'Trigger automatically subtask time tracking' => 'Automatisch bijhouden van subtaaktijd triggeren', + 'Include closed tasks in the cumulative flow diagram' => 'Neem afgesloten taken op in het cumulatieve stroomdiagram', + 'Current swimlane: %s' => 'Huidige swimlane: %s', + 'Current column: %s' => 'Huidige kolom: %s', + 'Current category: %s' => 'Huidige categorie: %s', + 'no category' => 'geen categorie', + 'Current assignee: %s' => 'Huidige toegewezene: %s', + 'not assigned' => 'niet toegewezen', + 'Author:' => 'Auteur:', + 'contributors' => 'bijdragers', + 'License:' => 'Licentie:', + 'License' => 'Licentie', + 'Enter the text below' => 'Voer de tekst hieronder in', + 'Start date:' => 'Startdatum:', + 'Due date:' => 'Vervaldatum:', + 'People who are project managers' => 'Mensen die projectbeheerder zijn', + 'People who are project members' => 'Mensen die projectleden zijn', + 'NOK - Norwegian Krone' => 'NOK - Noorse kroon', + 'Show this column' => 'Toon deze kolom', + 'Hide this column' => 'Verberg deze kolom', + 'End date' => 'Einddatum', + 'Users overview' => 'Overzicht gebruikers', + 'Members' => 'Leden', + 'Shared project' => 'Gedeeld project', + 'Project managers' => 'Projectbeheerders', + 'Projects list' => 'Lijst projecten', + 'End date:' => 'Einddatum:', + 'Change task color when using a specific task link' => 'Verander de kleur van een taak wanneer je een specifieke taaklink gebruikt', + 'Task link creation or modification' => 'Taaklink maken of wijzigen', + 'Milestone' => 'Mijlpaal', + 'Reset the search/filter box' => 'Het zoek/filtervak resetten', + 'Documentation' => 'Documentatie', + 'Author' => 'Auteur', + 'Version' => 'Versie', + 'Plugins' => 'Plugins', + 'There is no plugin loaded.' => 'Er is geen plugin geladen.', + 'My notifications' => 'Mijn meldingen', + 'Custom filters' => 'Aangepaste filters', + 'Your custom filter has been created successfully.' => 'Je aangepaste filter is met succes aangemaakt.', + 'Unable to create your custom filter.' => 'Kan je aangepaste filter niet aanmaken.', + 'Custom filter removed successfully.' => 'Aangepast filter met succes verwijderd.', + 'Unable to remove this custom filter.' => 'Kan dit aangepaste filter niet verwijderen.', + 'Edit custom filter' => 'Aangepast filter bewerken', + 'Your custom filter has been updated successfully.' => 'Je aangepaste filter is met succes bijgewerkt.', + 'Unable to update custom filter.' => 'Kan aangepast filter niet bijwerken.', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Nieuwe bijlage bij taak #%d: %s', + 'New comment on task #%d' => 'Nieuw commentaar op taak #%d', + 'Comment updated on task #%d' => 'Commentaar bijgewerkt op taak #%d', + 'New subtask on task #%d' => 'Nieuwe subtaak op taak #%d', + 'Subtask updated on task #%d' => 'Subtaak bijgewerkt op taak #%d', + 'New task #%d: %s' => 'Nieuwe taak #%d: %s', + 'Task updated #%d' => 'Taak bijgewerkt #%d', + 'Task #%d closed' => 'Taak #%d gesloten', + 'Task #%d opened' => 'Taak #%d geopend', + 'Column changed for task #%d' => 'Kolom gewijzigd voor taak #%d', + 'New position for task #%d' => 'Nieuwe positie voor taak #%d', + 'Swimlane changed for task #%d' => 'Swimlane gewijzigd voor taak #%d', + 'Assignee changed on task #%d' => 'Toegewezene gewijzigd voor taak #%d', + '%d overdue tasks' => '%d achterstallige taken', + 'No notification.' => 'Geen melding.', + 'Mark all as read' => 'Markeer alles als gelezen', + 'Mark as read' => 'Markeer als gelezen', + 'Total number of tasks in this column across all swimlanes' => 'Totaal aantal taken in deze kolom in alle swimlanes', + 'Collapse swimlane' => 'Voeg swimlane samen', + 'Expand swimlane' => 'Breid swimlane uit', + 'Add a new filter' => 'Een nieuw filter toevoegen', + 'Share with all project members' => 'Delen met alle projectleden', + 'Shared' => 'Gedeeld', + 'Owner' => 'Eigenaar', + 'Unread notifications' => 'Ongelezen meldingen', + 'Notification methods:' => 'Kennisgevingsmethoden:', + 'Unable to read your file' => 'Kan je bestand niet lezen', + '%d task(s) have been imported successfully.' => '%d taak (taken) is (zijn) met succes geïmporteerd.', + 'Nothing has been imported!' => 'Er is niets geïmporteerd!', + 'Import users from CSV file' => 'Gebruikers importeren uit CSV-bestand', + '%d user(s) have been imported successfully.' => '%d gebruiker(s) zijn succesvol geïmporteerd.', + 'Comma' => 'Komma', + 'Semi-colon' => 'Punt-komma', + 'Tab' => 'Tab', + 'Vertical bar' => 'Verticale balk', + 'Double Quote' => 'Dubbele aanhalingstekens', + 'Single Quote' => 'Enkele aanhalingstekens', + '%s attached a file to the task #%d' => '%s heeft een bestand toegevoegd aan de taak #%d', + 'There is no column or swimlane activated in your project!' => 'Er is geen kolom of swimlane geactiveerd in je project!', + 'Append filter (instead of replacement)' => 'Voeg filter toe (in plaats van vervangen)', + 'Append/Replace' => 'Toevoegen/vervangen', + 'Append' => 'Toevoegen', + 'Replace' => 'Vervang', + 'Import' => 'Importeer', + 'Change sorting' => 'Sortering wijzigen', + 'Tasks Importation' => 'Taken importeren', + 'Delimiter' => 'Scheidingsteken', + 'Enclosure' => 'Omsluiting', + 'CSV File' => 'CSV bestand', + 'Instructions' => 'Instructies', + 'Your file must use the predefined CSV format' => 'Je bestand moet de vooraf gedefinieerde CSV-indeling gebruiken', + 'Your file must be encoded in UTF-8' => 'Je bestand moet gecodeerd zijn in UTF-8', + 'The first row must be the header' => 'De eerste rij moet de koptekst zijn', + 'Duplicates are not verified for you' => 'Duplicaten worden niet voor je gecontroleerd', + 'The due date must use the ISO format: YYYY-MM-DD' => 'De vervaldatum moet het ISO-formaat gebruiken: JJJJ-MM-DD', + 'Download CSV template' => 'CSV-sjabloon downloaden', + 'No external integration registered.' => 'Geen externe integratie geregistreerd.', + 'Duplicates are not imported' => 'Duplicaten worden niet geïmporteerd', + 'Usernames must be lowercase and unique' => 'Gebruikersnamen moeten kleine letters en uniek zijn', + 'Passwords will be encrypted if present' => 'Wachtwoorden worden gecodeerd indien aanwezig', + '%s attached a new file to the task %s' => '%s heeft een nieuw bestand toegevoegd aan de taak %s', + 'Link type' => 'Type koppeling', + 'Assign automatically a category based on a link' => 'Automatisch een categorie toewijzen op basis van een link', + 'BAM - Konvertible Mark' => 'BAM - converteerbare mark', + 'Assignee Username' => 'Gebruikersnaam ontvanger', + 'Assignee Name' => 'Naam toewijzing', + 'Groups' => 'Groepen', + 'Members of %s' => 'Leden van %s', + 'New group' => 'Nieuwe groep', + 'Group created successfully.' => 'Groep succesvol aangemaakt.', + 'Unable to create your group.' => 'Kan je groep niet aanmaken.', + 'Edit group' => 'Groep bewerken', + 'Group updated successfully.' => 'Groep bijgewerkt.', + 'Unable to update your group.' => 'Kan je groep niet bijwerken.', + 'Add group member to "%s"' => 'Groepslid toevoegen aan "%s"', + 'Group member added successfully.' => 'Groepslid toegevoegd.', + 'Unable to add group member.' => 'Kan geen groepslid toevoegen.', + 'Remove user from group "%s"' => 'Gebruiker verwijderd uit groep "%s"', + 'User removed successfully from this group.' => 'Gebruiker succesvol verwijderd uit deze groep.', + 'Unable to remove this user from the group.' => 'Kan deze gebruiker niet uit de groep verwijderen.', + 'Remove group' => 'Verwijder groep', + 'Group removed successfully.' => 'Groep succesvol verwijderd.', + 'Unable to remove this group.' => 'Kan deze groep niet verwijderen.', + 'Project Permissions' => 'Projectmachtigingen', + 'Manager' => 'Manager', + 'Project Manager' => 'Projectbeheerder', + 'Project Member' => 'Projectlid', + 'Project Viewer' => 'Project-viewer', + 'Your account is locked for %d minutes' => 'Je account is geblokkeerd voor %d minuten', + 'Invalid captcha' => 'Ongeldige captcha', + 'The name must be unique' => 'De naam moet uniek zijn', + 'View all groups' => 'Alle groepen weergeven', + 'There is no user available.' => 'Er is geen gebruiker beschikbaar.', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Wil je echt de gebruiker "%s" verwijderen uit de groep "%s"?', + 'There is no group.' => 'Er is geen groep.', + 'Add group member' => 'Groepslid toevoegen', + 'Do you really want to remove this group: "%s"?' => 'Wil je deze groep echt verwijderen? "%s"?', + 'There is no user in this group.' => 'Er is geen gebruiker in deze groep.', + 'Permissions' => 'Rechten', + 'Allowed Users' => 'Toegestane gebruikers', + 'No specific user has been allowed.' => 'Er is geen specifieke gebruiker toegestaan.', + 'Role' => 'Rol', + 'Enter user name...' => 'Voer gebruikersnaam in...', + 'Allowed Groups' => 'Toegestane groepen', + 'No group has been allowed.' => 'Er is geen groep toegestaan.', + 'Group' => 'Groep', + 'Group Name' => 'Naam groep', + 'Enter group name...' => 'Naam van groep invoeren...', + 'Role:' => 'Rol:', + 'Project members' => 'Projectleden', + '%s mentioned you in the task #%d' => '%s noemde jou in de taak #%d', + '%s mentioned you in a comment on the task #%d' => '%s noemde jou in een commentaar op de taak #%d', + 'You were mentioned in the task #%d' => 'Je werd genoemd in de taak #%d', + 'You were mentioned in a comment on the task #%d' => 'Je werd genoemd in een commentaar op de taak #%d', + 'Estimated hours: ' => 'Geschatte uren: ', + 'Actual hours: ' => 'Werkelijke uren: ', + 'Hours Spent' => 'Bestede uren', + 'Hours Estimated' => 'Geschatte uren', + 'Estimated Time' => 'Geschatte tijd', + 'Actual Time' => 'Werkelijke tijd', + 'Estimated vs actual time' => 'Geschatte vs. werkelijke tijd', + 'RUB - Russian Ruble' => 'RUB - Russische roebel', + 'Assign the task to the person who does the action when the column is changed' => 'Wijs de taak toe aan de persoon die de actie uitvoert wanneer de kolom wordt gewijzigd', + 'Close a task in a specific column' => 'Sluit een taak in een specifieke kolom', + 'Time-based One-time Password Algorithm' => 'Tijdgebaseerd algoritme voor eenmalig wachtwoord', + 'Two-Factor Provider: ' => 'Provider met twee factoren: ', + 'Disable two-factor authentication' => 'Twee-factor authenticatie uitschakelen', + 'Enable two-factor authentication' => 'Twee-factor authenticatie inschakelen', + 'There is no integration registered at the moment.' => 'Er is momenteel geen integratie geregistreerd.', + 'Password Reset for Kanboard' => 'Wachtwoord opnieuw instellen voor Kanboard', + 'Forgot password?' => 'Wachtwoord vergeten?', + 'Enable "Forget Password"' => '"Wachtwoord vergeten" inschakelen', + 'Password Reset' => 'Wachtwoord opnieuw instellen', + 'New password' => 'Nieuw wachtwoord', + 'Change Password' => 'Wijzig wachtwoord', + 'To reset your password click on this link:' => 'Klik op deze link om je wachtwoord opnieuw in te stellen:', + 'Last Password Reset' => 'Laatste wachtwoordverandering', + 'The password has never been reinitialized.' => 'Het wachtwoord is nooit opnieuw geïnitialiseerd.', + 'Creation' => 'Aanmaak', + 'Expiration' => 'Vervaldatum', + 'Password reset history' => 'Geschiedenis wachtwoordwijzigingen', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Alle taken van de kolom "%s" en de swimlane "%s" zijn met succes afgesloten.', + 'Do you really want to close all tasks of this column?' => 'Wil je echt alle taken van deze kolom sluiten?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d taak (taken) in de kolom "%s" en de swimlane "%s" zullen worden gesloten.', + 'Close all tasks in this column and this swimlane' => 'Sluit alle taken in deze kolom en deze swimlane', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Geen enkele plugin heeft een projectnotificatiemethode geregistreerd. Je kunt nog steeds individuele meldingen configureren in je gebruikersprofiel.', + 'My dashboard' => 'Mijn dashboard', + 'My profile' => 'Mijn profiel', + 'Project owner: ' => 'Project eigenaar: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'De projectidentificatie is optioneel en moet alfanumeriek zijn, bijvoorbeeld: MYPROJECT.', + 'Project owner' => 'Project eigenaar', + 'Personal projects do not have users and groups management.' => 'Persoonlijke projecten hebben geen gebruikers- en groepenbeheer.', + 'There is no project member.' => 'Er is geen projectlid.', + 'Priority' => 'Prioriteit', + 'Task priority' => 'Taak prioriteit', + 'General' => 'Algemeen', + 'Dates' => 'Datums', + 'Default priority' => 'Standaard prioriteit', + 'Lowest priority' => 'Laagste prioriteit', + 'Highest priority' => 'Hoogste prioriteit', + 'Close a task when there is no activity' => 'Een taak sluiten als er geen activiteit is', + 'Duration in days' => 'Duur in dagen', + 'Send email when there is no activity on a task' => 'Een e-mail sturen wanneer er geen activiteit is voor een taak', + 'Unable to fetch link information.' => 'Kan geen linkinformatie ophalen.', + 'Daily background job for tasks' => 'Dagelijkse achtergrondtaak voor taken', + 'Auto' => 'Auto', + 'Related' => 'Gerelateerd', + 'Attachment' => 'Bijlage', + 'Web Link' => 'Weblink', + 'External links' => 'Externe links', + 'Add external link' => 'Externe link toevoegen', + 'Type' => 'Type', + 'Dependency' => 'Afhankelijkheid', + 'Add internal link' => 'Interne link toevoegen', + 'Add a new external link' => 'Nieuwe externe link toevoegen', + 'Edit external link' => 'Externe link bewerken', + 'External link' => 'Externe link', + 'Copy and paste your link here...' => 'Kopieer en plak je link hier...', + 'URL' => 'URL', + 'Internal links' => 'Interne koppelingen', + 'Assign to me' => 'Aan mij toewijzen', + 'Me' => 'Mij', + 'Do not duplicate anything' => 'Niets dupliceren', + 'Projects management' => 'Projecten beheren', + 'Users management' => 'Gebruikers beheren', + 'Groups management' => 'Groepen beheren', + 'Create from another project' => 'Aanmaken vanuit een ander project', + 'open' => 'open', + 'closed' => 'gesloten', + 'Priority:' => 'Prioriteit:', + 'Reference:' => 'Referentie:', + 'Complexity:' => 'Complexiteit:', + 'Swimlane:' => 'Swimlane:', + 'Column:' => 'Kolom:', + 'Position:' => 'Positie:', + 'Creator:' => 'Aanmaker:', + 'Time estimated:' => 'Ingeschatte tijd:', + '%s hours' => '%s uur', + 'Time spent:' => 'Tijd besteed:', + 'Created:' => 'Aangemaakt:', + 'Modified:' => 'Gewijzigd:', + 'Completed:' => 'Afgerond:', + 'Started:' => 'Begonnen:', + 'Moved:' => 'Verplaatst:', + 'Task #%d' => 'Taak #%d', + 'Time format' => 'Tijd formaat', + 'Start date: ' => 'Begindatum: ', + 'End date: ' => 'Einddatum: ', + 'New due date: ' => 'Nieuwe einddatum: ', + 'Start date changed: ' => 'Startdatum gewijzigd: ', + 'Disable personal projects' => 'Persoonlijke projecten uitschakelen', + 'Do you really want to remove this custom filter: "%s"?' => 'Wil je deze aangepaste filter echt verwijderen: "%s"?', + 'Remove a custom filter' => 'Een aangepast filter verwijderen', + 'User activated successfully.' => 'Gebruiker succesvol geactiveerd.', + 'Unable to enable this user.' => 'Kan deze gebruiker niet inschakelen.', + 'User disabled successfully.' => 'Gebruiker uitgeschakeld.', + 'Unable to disable this user.' => 'Kan deze gebruiker niet uitschakelen.', + 'All files have been uploaded successfully.' => 'Alle bestanden zijn succesvol geüpload.', + 'The maximum allowed file size is %sB.' => 'De maximaal toegestane bestandsgrootte is %sB.', + 'Drag and drop your files here' => 'Sleep je bestanden hierheen', + 'choose files' => 'kies bestanden', + 'View profile' => 'Profiel bekijken', + 'Two Factor' => 'Twee-factor', + 'Disable user' => 'Gebruiker uitschakelen', + 'Do you really want to disable this user: "%s"?' => 'Wil je deze gebruiker echt uitschakelen: "%s"?', + 'Enable user' => 'Gebruiker inschakelen', + 'Do you really want to enable this user: "%s"?' => 'Wil je deze gebruiker echt inschakelen: "%s"?', + 'Download' => 'Downloaden', + 'Uploaded: %s' => 'Geüpload: %s', + 'Size: %s' => 'Grootte: %s', + 'Uploaded by %s' => 'Geüpload door %s', + 'Filename' => 'Bestandsnaam', + 'Size' => 'Grootte', + 'Column created successfully.' => 'Kolom succesvol aangemaakt.', + 'Another column with the same name exists in the project' => 'Een andere kolom met dezelfde naam bestaat in het project', + 'Default filters' => 'Standaard filters', + 'Your board doesn\'t have any columns!' => 'Je bord heeft geen kolommen!', + 'Change column position' => 'Kolompositie wijzigen', + 'Switch to the project overview' => 'Ga naar het projectoverzicht', + 'User filters' => 'Gebruikersfilters', + 'Category filters' => 'Categoriefilters', + 'Upload a file' => 'Een bestand uploaden', + 'View file' => 'Bestand bekijken', + 'Last activity' => 'Laatste activiteit', + 'Change subtask position' => 'Positie van subtaak wijzigen', + 'This value must be greater than %d' => 'Deze waarde moet groter zijn dan %d', + 'Another swimlane with the same name exists in the project' => 'Er bestaat een andere swimlane met dezelfde naam in het project', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Voorbeeld: https://example.kanboard.org/ (gebruikt om absolute URL\'s te genereren)', + 'Actions duplicated successfully.' => 'Acties succesvol gedupliceerd.', + 'Unable to duplicate actions.' => 'Kan acties niet dupliceren.', + 'Add a new action' => 'Voeg een nieuwe actie toe', + 'Import from another project' => 'Importeren vanuit een ander project', + 'There is no action at the moment.' => 'Er is op dit moment geen actie.', + 'Import actions from another project' => 'Importeer acties van een ander project', + 'There is no available project.' => 'Er is geen project beschikbaar.', + 'Local File' => 'Lokaal bestand', + 'Configuration' => 'Configuratie', + 'PHP version:' => 'PHP versie:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'OS versie:', + 'Database version:' => 'Database versie:', + 'Browser:' => 'Browser:', + 'Task view' => 'Taak bekijken', + 'Edit task' => 'Taak bewerken', + 'Edit description' => 'Beschrijving bewerken', + 'New internal link' => 'Nieuwe interne link', + 'Display list of keyboard shortcuts' => 'Lijst met sneltoetsen weergeven', + 'Avatar' => 'Avatar', + 'Upload my avatar image' => 'Mijn avatar uploaden', + 'Remove my image' => 'Mijn afbeelding verwijderen', + 'The OAuth2 state parameter is invalid' => 'De OAuth2-statusparameter is ongeldig', + 'User not found.' => 'Gebruiker niet gevonden.', + 'Search in activity stream' => 'Zoeken in activiteitenstroom', + 'My activities' => 'Mijn activiteiten', + 'Activity until yesterday' => 'Activiteit tot gisteren', + 'Activity until today' => 'Activiteit tot vandaag', + 'Search by creator: ' => 'Zoeken op maker: ', + 'Search by creation date: ' => 'Zoeken op aanmaakdatum: ', + 'Search by task status: ' => 'Zoeken op taakstatus: ', + 'Search by task title: ' => 'Zoeken op taaktitel: ', + 'Activity stream search' => 'Zoeken op activiteitenstroom', + 'Projects where "%s" is manager' => 'Projecten waar "%s" manager is', + 'Projects where "%s" is member' => 'Projecten waar "%s" lid van is', + 'Open tasks assigned to "%s"' => 'Open taken toegewezen aan "%s', + 'Closed tasks assigned to "%s"' => 'Gesloten taken toegewezen aan "%s', + 'Assign automatically a color based on a priority' => 'Automatisch een kleur toewijzen op basis van prioriteit', + 'Overdue tasks for the project(s) "%s"' => 'Achterstallige taken voor het project (de projecten) "%s".', + 'Upload files' => 'Bestanden uploaden', + 'Installed Plugins' => 'Geïnstalleerde plugins', + 'Plugin Directory' => 'Plugin map', + 'Plugin installed successfully.' => 'Plugin met succes geïnstalleerd.', + 'Plugin updated successfully.' => 'Plugin bijgewerkt.', + 'Plugin removed successfully.' => 'Plugin met succes verwijderd.', + 'Subtask converted to task successfully.' => 'Subtaak met succes geconverteerd naar taak.', + 'Unable to convert the subtask.' => 'Kan de subtaak niet converteren.', + 'Unable to extract plugin archive.' => 'Plugin-archief kan niet worden uitgepakt.', + 'Plugin not found.' => 'Plugin niet gevonden.', + 'You don\'t have the permission to remove this plugin.' => 'Je hebt geen toestemming om deze plugin te verwijderen.', + 'Unable to download plugin archive.' => 'Kan het plugin-archief niet downloaden.', + 'Unable to write temporary file for plugin.' => 'Kan geen tijdelijk bestand voor plugin schrijven.', + 'Unable to open plugin archive.' => 'Kan het plugin archief niet openen.', + 'There is no file in the plugin archive.' => 'Er is geen bestand in het plugin archief.', + 'Create tasks in bulk' => 'Taken in bulk aanmaken', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Je Kanboard-instantie is niet geconfigureerd om plugins te installeren vanuit de gebruikersinterface.', + 'There is no plugin available.' => 'Er is geen plugin beschikbaar.', + 'Install' => 'Installeer', + 'Update' => 'Bijwerken', + 'Up to date' => 'Up to date', + 'Not available' => 'Niet beschikbaar', + 'Remove plugin' => 'Plugin verwijderen', + 'Do you really want to remove this plugin: "%s"?' => 'Wil je deze plugin echt verwijderen: "%s"?', + 'Uninstall' => 'Verwijder', + 'Listing' => 'Lijst', + 'Metadata' => 'Metagegevens', + 'Manage projects' => 'Projecten beheren', + 'Convert to task' => 'Omzetten naar taak', + 'Convert sub-task to task' => 'Subtaak omzetten naar taak', + 'Do you really want to convert this sub-task to a task?' => 'Wil je deze subtaak echt omzetten naar een taak?', + 'My task title' => 'Mijn taak titel', + 'Enter one task by line.' => 'Per regel een taak invoeren.', + 'Number of failed login:' => 'Aantal mislukte aanmeldingen:', + 'Account locked until:' => 'Account vergrendeld tot:', + 'Email settings' => 'E-mailinstellingen', + 'Email sender address' => 'E-mailadres afzender', + 'Email transport' => 'E-mail transport', + 'Webhook token' => 'Webhook token', + 'Project tags management' => 'Project tags beheer', + 'Tag created successfully.' => 'Tag met succes aangemaakt.', + 'Unable to create this tag.' => 'Kan deze tag niet aanmaken.', + 'Tag updated successfully.' => 'Tag met succes bijgewerkt.', + 'Unable to update this tag.' => 'Kan deze tag niet bijwerken.', + 'Tag removed successfully.' => 'Tag met succes verwijderd.', + 'Unable to remove this tag.' => 'Kan deze tag niet verwijderen.', + 'Global tags management' => 'Beheer globale tags', + 'Tags' => 'Tags', + 'Tags management' => 'Beheer tags', + 'Add new tag' => 'Voeg een nieuwe tag toe', + 'Edit a tag' => 'Een tag bewerken', + 'Project tags' => 'Projecttags', + 'There is no specific tag for this project at the moment.' => 'Er zijn geen tags gedefinieerd voor dit project.', + 'Tag' => 'Tag', + 'Remove a tag' => 'Een tag verwijderen', + 'Do you really want to remove this tag: "%s"?' => 'Wil je deze tag echt verwijderen: "%s"?', + 'Global tags' => 'Globale tags', + 'There is no global tag at the moment.' => 'Er is momenteel geen globale tag.', + 'This field cannot be empty' => 'Dit veld kan niet leeg zijn', + 'Close a task when there is no activity in a specific column' => 'Een taak sluiten wanneer er geen activiteit is in een specifieke kolom', + '%s removed a subtask for the task #%d' => '%s verwijderde een subtaak voor de taak #%d', + '%s removed a comment on the task #%d' => '%s heeft een commentaar bij taak #%d verwijderd', + 'Comment removed on task #%d' => 'Commentaar bij taak #%d verwijderd', + 'Subtask removed on task #%d' => 'Subtaak verwijderd voor taak #%d', + 'Hide tasks in this column in the dashboard' => 'Verberg taken in deze kolom in het dashboard', + '%s removed a comment on the task %s' => '%s heeft een commentaar bij taak %s verwijderd', + '%s removed a subtask for the task %s' => '%s heeft een subtaak verwijderd voor de taak %s', + 'Comment removed' => 'Commentaar verwijderd', + 'Subtask removed' => 'Subtaak verwijderd', + '%s set a new internal link for the task #%d' => '%s heeft een nieuwe interne link geplaatst voor de taak #%d', + '%s removed an internal link for the task #%d' => '%s verwijderde een interne link voor de taak #%d', + 'A new internal link for the task #%d has been defined' => 'Een nieuwe interne link voor de taak #%d is gedefinieerd.', + 'Internal link removed for the task #%d' => 'Interne link verwijderd voor de taak #%d', + '%s set a new internal link for the task %s' => '%s heeft een nieuwe interne link ingesteld voor de taak %s', + '%s removed an internal link for the task %s' => '%s heeft een interne link verwijderd voor de taak %s', + 'Automatically set the due date on task creation' => 'Stel automatisch de vervaldatum in bij het aanmaken van een taak', + 'Move the task to another column when closed' => 'Verplaats de taak naar een andere kolom wanneer deze is gesloten', + 'Move the task to another column when not moved during a given period' => 'Verplaats de taak naar een andere kolom wanneer deze niet is verplaatst gedurende een bepaalde periode', + 'Dashboard for %s' => 'Dashboard voor %s', + 'Tasks overview for %s' => 'Taken overzicht voor %s', + 'Subtasks overview for %s' => 'Subtaken overzicht voor %s', + 'Projects overview for %s' => 'Projecten overzicht voor %s', + 'Activity stream for %s' => 'Activiteitenstroom voor %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Wijs een kleur toe wanneer de taak wordt verplaatst naar een specifieke swimlane', + 'Assign a priority when the task is moved to a specific swimlane' => 'Prioriteit toekennen wanneer de taak wordt verplaatst naar een specifieke swimlane', + 'User unlocked successfully.' => 'Gebruiker succesvol ontgrendeld.', + 'Unable to unlock the user.' => 'Kan de gebruiker niet ontgrendelen.', + 'Move a task to another swimlane' => 'Een taak naar een andere swimlane verplaatsen', + 'Creator Name' => 'Naam aanmaker', + 'Time spent and estimated' => 'Tijd besteed en geschat', + 'Move position' => 'Positie verplaatsen', + 'Move task to another position on the board' => 'Verplaats taak naar een andere positie op het bord', + 'Insert before this task' => 'Invoegen vóór deze taak', + 'Insert after this task' => 'Invoegen na deze taak', + 'Unlock this user' => 'Ontgrendel deze gebruiker', + 'Custom Project Roles' => 'Aangepaste projectrollen', + 'Add a new custom role' => 'Een nieuwe aangepaste rol toevoegen', + 'Restrictions for the role "%s"' => 'Beperkingen voor de rol "%s".', + 'Add a new project restriction' => 'Een nieuwe projectbeperking toevoegen', + 'Add a new drag and drop restriction' => 'Een nieuwe slepen-en-neerzetten beperking toevoegen', + 'Add a new column restriction' => 'Een nieuwe kolombeperking toevoegen', + 'Edit this role' => 'Deze rol bewerken', + 'Remove this role' => 'Deze role verwijderen', + 'There is no restriction for this role.' => 'Er is geen beperking voor deze rol.', + 'Only moving task between those columns is permitted' => 'Alleen het verplaatsen van taken tussen deze kolommen is toegestaan', + 'Close a task in a specific column when not moved during a given period' => 'Sluit een taak in een specifieke kolom als deze niet is verplaatst gedurende een bepaalde periode', + 'Edit columns' => 'Kolommen berwerken', + 'The column restriction has been created successfully.' => 'De kolombeperking is succesvol aangemaakt.', + 'Unable to create this column restriction.' => 'Kan deze kolombeperking niet aanmaken.', + 'Column restriction removed successfully.' => 'Kolombeperking succesvol verwijderd.', + 'Unable to remove this restriction.' => 'Kan deze beperking niet verwijderen.', + 'Your custom project role has been created successfully.' => 'Je aangepaste projectrol is succesvol aangemaakt.', + 'Unable to create custom project role.' => 'Kan aangepaste projectrol niet aanmaken.', + 'Your custom project role has been updated successfully.' => 'Je aangepaste projectrol is succesvol bijgewerkt.', + 'Unable to update custom project role.' => 'Kan aangepaste projectrol niet bijwerken.', + 'Custom project role removed successfully.' => 'Aangepaste projectrol succesvol verwijderd.', + 'Unable to remove this project role.' => 'Kan deze projectrol niet verwijderen.', + 'The project restriction has been created successfully.' => 'De projectrestrictie is succesvol aangemaakt.', + 'Unable to create this project restriction.' => 'Kan deze projectrestrictie niet aanmaken.', + 'Project restriction removed successfully.' => 'Projectbeperking succesvol verwijderd.', + 'You cannot create tasks in this column.' => 'Je kunt geen taken aanmaken in deze kolom.', + 'Task creation is permitted for this column' => 'Taken aanmaken is toegestaan voor deze kolom.', + 'Closing or opening a task is permitted for this column' => 'Een taak sluiten of openen is toegestaan voor deze kolom.', + 'Task creation is blocked for this column' => 'Taak aanmaken is geblokkeerd voor deze kolom', + 'Closing or opening a task is blocked for this column' => 'Een taak sluiten of openen is geblokkeerd voor deze kolom', + 'Task creation is not permitted' => 'Taken aanmaken is niet toegestaan', + 'Closing or opening a task is not permitted' => 'Een taak sluiten of openen is niet toegestaan', + 'New drag and drop restriction for the role "%s"' => 'Nieuwe slepen en neerzetten beperking voor de rol "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Mensen die behoren tot deze rol kunnen alleen taken verplaatsen tussen de bron- en bestemmingskolom.', + 'Remove a column restriction' => 'Een kolombeperking verwijderen', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Wil je deze kolombeperking echt verwijderen? "%s" naar "%s"?', + 'New column restriction for the role "%s"' => 'Nieuwe kolombeperking voor de rol "%s".', + 'Rule' => 'Regel', + 'Do you really want to remove this column restriction?' => 'Wil je deze kolombeperking echt verwijderen?', + 'Custom roles' => 'Aangepaste rollen', + 'New custom project role' => 'Nieuwe aangepaste projectrol', + 'Edit custom project role' => 'Aangepaste projectrol bewerken', + 'Remove a custom role' => 'Een aangepaste rol verwijderen', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Wil je deze aangepaste rol echt verwijderen: "%s"? Alle mensen die aan deze rol zijn toegewezen worden projectlid.', + 'There is no custom role for this project.' => 'Er is geen aangepaste rol voor dit project.', + 'New project restriction for the role "%s"' => 'Nieuwe projectbeperking voor de rol "%s".', + 'Restriction' => 'Beperking', + 'Remove a project restriction' => 'Een projectbeperking verwijderen', + 'Do you really want to remove this project restriction: "%s"?' => 'Wil je deze projectrestrictie echt verwijderen: "%s"?', + 'Duplicate to multiple projects' => 'Dupliceren naar meerdere projecten', + 'This field is required' => 'Dit veld is verplicht', + 'Moving a task is not permitted' => 'Een taak verplaatsen is niet toegestaan', + 'This value must be in the range %d to %d' => 'Deze waarde moet in het bereik %d tot %d liggen', + 'You are not allowed to move this task.' => 'Je mag deze taak niet verplaatsen.', + 'API User Access' => 'API-gebruikerstoegang', + 'Preview' => 'Voorbeeld', + 'Write' => 'Schrijven', + 'Write your text in Markdown' => 'Schrijf je tekst in Markdown', + 'No personal API access token registered.' => 'Geen persoonlijk API toegangstoken geregistreerd.', + 'Your personal API access token is "%s"' => 'Je persoonlijke API toegang token is "%s".', + 'Remove your token' => 'Verwijder je token', + 'Generate a new token' => 'Genereer een nieuw token', + 'Showing %d-%d of %d' => 'Weergave %d-%d van %d', + 'Outgoing Emails' => 'Uitgaande e-mails', + 'Add or change currency rate' => 'Valutakoers toevoegen of wijzigen', + 'Reference currency: %s' => 'Referentievaluta: %s', + 'Add custom filters' => 'Nieuw aangepast filter toevoegen', + 'Export' => 'Exporteren', + 'Add link label' => 'Linklabel toevoegen', + 'Incompatible Plugins' => 'Onverenigbare plugins', + 'Compatibility' => 'Compatibiliteit', + 'Permissions and ownership' => 'Rechten en eigendom', + 'Priorities' => 'Prioriteiten', + 'Close this window' => 'Dit scherm sluiten', + 'Unable to upload this file.' => 'Kan dit bestand niet uploaden.', + 'Import tasks' => 'Taken importeren', + 'Choose a project' => 'Kies een project', + 'Profile' => 'Profiel', + 'Application role' => 'Rol toepassing', + '%d invitations were sent.' => '%d uitnodigingen zijn verzonden.', + '%d invitation was sent.' => '%d uitnodigingen verzonden.', + 'Unable to create this user.' => 'Kan deze gebruiker niet aanmaken.', + 'Kanboard Invitation' => 'Kanboard uitnodiging', + 'Visible on dashboard' => 'Zichtbaar op dashboard', + 'Created at:' => 'Aangemaakt op:', + 'Updated at:' => 'Bijgewerkt op:', + 'There is no custom filter.' => 'Er is geen aangepaste filter.', + 'New User' => 'Nieuwe gebruiker', + 'Authentication' => 'Authenticatie', + 'If checked, this user will use a third-party system for authentication.' => 'Als deze optie is aangevinkt, zal deze gebruiker een systeem van derden gebruiken voor verificatie.', + 'The password is necessary only for local users.' => 'Het wachtwoord is alleen nodig voor lokale gebruikers.', + 'You have been invited to register on Kanboard.' => 'Je bent uitgenodigd om je te registreren op Kanboard.', + 'Click here to join your team' => 'Klik hier om lid te worden van je team', + 'Invite people' => 'Nodig personen uit', + 'Emails' => 'E-mails', + 'Enter one email address by line.' => 'Voer één e-mailadres per regel in.', + 'Add these people to this project' => 'Voeg deze mensen toe aan dit project', + 'Add this person to this project' => 'Voeg deze persoon toe aan dit project', + 'Sign-up' => 'Aanmelden', + 'Credentials' => 'Gegevens', + 'New user' => 'Nieuwe gebruiker', + 'This username is already taken' => 'Deze gebruikersnaam is al gebruikt', + 'Your profile must have a valid email address.' => 'Je profiel moet een geldig e-mailadres hebben.', + 'TRL - Turkish Lira' => 'TRL - Turkse lire', + 'The project email is optional and could be used by several plugins.' => 'Het e-mailadres van het project is optioneel en kan door verschillende plugins worden gebruikt.', + 'The project email must be unique across all projects' => 'Het project e-mailadres moet uniek zijn voor alle projecten', + 'The email configuration has been disabled by the administrator.' => 'De e-mailconfiguratie is uitgeschakeld door de beheerder.', + 'Close this project' => 'Dit project sluiten', + 'Open this project' => 'Dit project openen', + 'Close a project' => 'Sluit een project', + 'Do you really want to close this project: "%s"?' => 'Wil je dit project echt sluiten: "%s"?', + 'Reopen a project' => 'Heropen dit project', + 'Do you really want to reopen this project: "%s"?' => 'Wil je dit project echt heropenen: "%s"?', + 'This project is open' => 'Dit project is open', + 'This project is closed' => 'Dit project is gesloten', + 'Unable to upload files, check the permissions of your data folder.' => 'Kan geen bestanden uploaden, controleer de rechten van je gegevensmap.', + 'Another category with the same name exists in this project' => 'Er bestaat een andere categorie met dezelfde naam in dit project', + 'Comment sent by email successfully.' => 'Commentaar succesvol verzonden per e-mail.', + 'Sent by email to "%s" (%s)' => 'Per e-mail verzonden naar "%s" (%s)', + 'Unable to read uploaded file.' => 'Kan geüpload bestand niet lezen.', + 'Database uploaded successfully.' => 'Database succesvol geüpload.', + 'Task sent by email successfully.' => 'Taak succesvol verzonden per e-mail.', + 'There is no category in this project.' => 'Er is geen categorie in dit project.', + 'Send by email' => 'Per e-mail verzenden', + 'Create and send a comment by email' => 'Een opmerking maken en verzenden per e-mail', + 'Subject' => 'Onderwerp', + 'Upload the database' => 'De database uploaden', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Je kunt de eerder gedownloade Sqlite-database uploaden (Gzip-formaat).', + 'Database file' => 'Bestand database', + 'Upload' => 'uploaden', + 'Your project must have at least one active swimlane.' => 'Je project moet ten minste één actieve swimlane hebben.', + 'Project: %s' => 'Project: %s', + 'Automatic action not found: "%s"' => 'Automatische actie niet gevonden: "%s"', + '%d projects' => '%d projecten', + '%d project' => '%d project', + 'There is no project.' => 'Er is geen project.', + 'Sort' => 'Sorteren', + 'Project ID' => 'Project-ID', + 'Project name' => 'Naam project', + 'Public' => 'Publiek', + 'Personal' => 'Persoonlijk', + '%d tasks' => '%d taken', + '%d task' => '%d taak', + 'Task ID' => 'Taak-ID', + 'Assign automatically a color when due date is expired' => 'Wijs automatisch een kleur toe als de deadline is verstreken', + 'Total score in this column across all swimlanes' => 'Totale score in deze kolom over alle swimlanes', + 'HRK - Kuna' => 'HRK - kuna', + 'ARS - Argentine Peso' => 'ARS - Argentijnse peso', + 'COP - Colombian Peso' => 'COP - Colombiaanse peso', + '%d groups' => '%d groepen', + '%d group' => '%d groep', + 'Group ID' => 'Groep ID', + 'External ID' => 'Externe ID', + '%d users' => '%d gebruikers', + '%d user' => '%d gebruiker', + 'Hide subtasks' => 'Subtaken verbergen', + 'Show subtasks' => 'Subtaken weergeven', + 'Authentication Parameters' => 'Verificatieparameters', + 'API Access' => 'API toegang', + 'No users found.' => 'Geen gebruikers gevonden', + 'User ID' => 'Gebruikers-ID', + 'Notifications are activated' => 'Meldingen zijn geactiveerd', + 'Notifications are disabled' => 'Meldingen zijn uitgeschakeld', + 'User disabled' => 'Gebruiker uitgeschakeld', + '%d notifications' => '%d meldingen', + '%d notification' => '%d melding', + 'There is no external integration installed.' => 'Er is geen externe integratie geïnstalleerd.', + 'You are not allowed to update tasks assigned to someone else.' => 'Je mag taken die aan iemand anders zijn toegewezen niet bijwerken.', + 'You are not allowed to change the assignee.' => 'Je mag de toegewezene niet wijzigen.', + 'Task suppression is not permitted' => 'Taakonderdrukking is niet toegestaan', + 'Changing assignee is not permitted' => 'Het wijzigen van de toegewezene is niet toegestaan', + 'Update only assigned tasks is permitted' => 'Alleen toegewezen taken bijwerken is toegestaan', + 'Only for tasks assigned to the current user' => 'Alleen voor taken die zijn toegewezen aan de huidige gebruiker', + 'My projects' => 'Mijn projecten', + 'You are not a member of any project.' => 'Je bent geen lid van een project.', + 'My subtasks' => 'Mijn subtaken', + '%d subtasks' => '%d subtaken', + '%d subtask' => '%d subtaken', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Alleen het verplaatsen van taken tussen deze kolommen is toegestaan voor taken die zijn toegewezen aan de huidige gebruiker.', + '[DUPLICATE]' => '[DUPLICEREN]', + 'DKK - Danish Krona' => 'DKK - Deense kroon', + 'Remove user from group' => 'Verwijder gebruiker uit groep', + 'Assign the task to its creator' => 'Wijs de taak toe aan de maker', + 'This task was sent by email to "%s" with subject "%s".' => 'Deze taak is per e-mail verzonden naar "%s" met als onderwerp "%s".', + 'Predefined Email Subjects' => 'Vooraf gedefinieerde e-mailonderwerpen', + 'Write one subject by line.' => 'Schrijf een onderwerp per regel.', + 'Create another link' => 'Maak een andere link', + 'BRL - Brazilian Real' => 'BRL - Braziliaanse real', + 'Add a new Kanboard task' => 'Een nieuwe Kanboard-taak toevoegen', + 'Subtask not started' => 'Subtaak niet gestart', + 'Subtask currently in progress' => 'Subtaak momenteel bezig', + 'Subtask completed' => 'Subtaak voltooid', + 'Subtask added successfully.' => 'Subtaak succesvol toegevoegd.', + '%d subtasks added successfully.' => '%d subtaken succesvol toegevoegd.', + 'Enter one subtask by line.' => 'Voer één subtaak per regel in.', + 'Predefined Contents' => 'Vooraf gedefinieerde inhoud', + 'Predefined contents' => 'Vooraf gedefinieerde inhoud', + 'Predefined Task Description' => 'Vooraf gedefinieerde taakbeschrijving', + 'Do you really want to remove this template? "%s"' => 'Wil je deze sjabloon echt verwijderen? "%s"', + 'Add predefined task description' => 'Vooraf gedefinieerde taakbeschrijving toevoegen', + 'Predefined Task Descriptions' => 'Vooraf gedefinieerde taakbeschrijvingen', + 'Template created successfully.' => 'Sjabloon succesvol aangemaakt.', + 'Unable to create this template.' => 'Kan deze sjabloon niet aanmaken.', + 'Template updated successfully.' => 'Sjabloon bijgewerkt.', + 'Unable to update this template.' => 'Kan deze sjabloon niet bijwerken.', + 'Template removed successfully.' => 'Sjabloon met succes verwijderd.', + 'Unable to remove this template.' => 'Kan deze sjabloon niet verwijderen.', + 'Template for the task description' => 'Sjabloon voor de taakbeschrijving', + 'The start date is greater than the end date' => 'De begindatum is groter dan de einddatum', + 'Tags must be separated by a comma' => 'Tags moeten worden gescheiden door een komma', + 'Only the task title is required' => 'Alleen de taaktitel is verplicht', + 'Creator Username' => 'Bedenker Gebruikersnaam', + 'Color Name' => 'Kleur Naam', + 'Column Name' => 'Kolom Naam', + 'Swimlane Name' => 'Naam swimlane', + 'Time Estimated' => 'Geschatte tijd', + 'Time Spent' => 'Bestede tijd', + 'External Link' => 'Externe link', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Deze functie schakelt de iCal feed, RSS feed en de openbare bordweergave in.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Stop de timer van alle subtaken wanneer je een taak naar een andere kolom verplaatst', + 'Subtask Title' => 'Subtaak Titel', + 'Add a subtask and activate the timer when moving a task to another column' => 'Voeg een subtaak toe en activeer de timer bij het verplaatsen van een taak naar een andere kolom', + 'days' => 'dagen', + 'minutes' => 'minuten', + 'seconds' => 'seconden', + 'Assign automatically a color when preset start date is reached' => 'Automatisch een kleur toewijzen wanneer een vooraf ingestelde begindatum is bereikt', + 'Move the task to another column once a predefined start date is reached' => 'Verplaats de taak naar een andere kolom wanneer een vooraf ingestelde begindatum is bereikt', + 'This task is now linked to the task %s with the relation "%s"' => 'Deze taak is nu gekoppeld aan de taak %s met de relatie "%s".', + 'The link with the relation "%s" to the task %s has been removed' => 'De link met de relatie "%s" naar de taak %s is verwijderd', + 'Custom Filter:' => 'Aangepast filter:', + 'Unable to find this group.' => 'Deze groep kan niet worden gevonden', + '%s moved the task #%d to the column "%s"' => '%s heeft de taak #%d naar de kolom "%s" verplaatst', + '%s moved the task #%d to the position %d in the column "%s"' => '%s verplaatste de taak #%d naar de positie %d in de kolom "%s"', + '%s moved the task #%d to the swimlane "%s"' => '%s heeft de taak #%d verplaatst naar de swimlane "%s".', + '%sh spent' => '%sh besteed', + '%sh estimated' => '%sh geschat', + 'Select All' => 'Alles selecteren', + 'Unselect All' => 'Alles deselecteren', + 'Apply action' => 'Actie toepassen', + 'Move selected tasks to another column or swimlane' => 'Verplaats geselecteerde taken naar een andere kolom of swimlane', + 'Edit tasks in bulk' => 'Taken in bulk bewerken', + 'Choose the properties that you would like to change for the selected tasks.' => 'Kies de eigenschappen die je wilt wijzigen voor de geselecteerde taken.', + 'Configure this project' => 'Configureer dit project', + 'Start now' => 'Start nu', + '%s removed a file from the task #%d' => '%s heeft een bestand verwijderd uit de taak #%d', + 'Attachment removed from task #%d: %s' => 'Bijlage verwijderd uit taak #%d: %s', + 'No color' => 'Geen kleur', + 'Attachment removed "%s"' => 'Bijlage verwijderd "%s".', + '%s removed a file from the task %s' => '%s heeft een bestand van de taak %s verwijderd', + 'Move the task to another swimlane when assigned to a user' => 'Verplaats de taak naar een andere swimlane wanneer deze is toegewezen aan een gebruiker', + 'Destination swimlane' => 'Bestemming swimlane', + 'Assign a category when the task is moved to a specific swimlane' => 'Een categorie toewijzen wanneer de taak wordt verplaatst naar een specifieke swimlane', + 'Move the task to another swimlane when the category is changed' => 'Verplaats de taak naar een ander swimlane wanneer de categorie is gewijzigd', + 'Reorder this column by priority (ASC)' => 'Herschik deze kolom op prioriteit (oplopend)', + 'Reorder this column by priority (DESC)' => 'Herschik deze kolom op prioriteit (aflopend)', + 'Reorder this column by assignee and priority (ASC)' => 'Herschik deze kolom op ontvanger en prioriteit (oplopend)', + 'Reorder this column by assignee and priority (DESC)' => 'Sorteer deze kolom opnieuw op geadresseerde en prioriteit (aflopend)', + 'Reorder this column by assignee (A-Z)' => 'Sorteer deze kolom opnieuw op geadresseerde (A-Z)', + 'Reorder this column by assignee (Z-A)' => 'Sorteer deze kolom opnieuw op geadresseerde (Z-A)', + 'Reorder this column by due date (ASC)' => 'Sorteer deze kolom opnieuw op vervaldatum (oplopend)', + 'Reorder this column by due date (DESC)' => 'Sorteer deze kolom opnieuw op vervaldatum (aflopend)', + 'Reorder this column by id (ASC)' => 'Sorteer deze kolom opnieuw op id (oplopend)', + 'Reorder this column by id (DESC)' => 'Sorteer deze kolom opnieuw op id (aflopend)', + '%s moved the task #%d "%s" to the project "%s"' => '%s heeft de taak #%d "%s" verplaatst naar het project "%s".', + 'Task #%d "%s" has been moved to the project "%s"' => 'Taak #%d "%s" is verplaatst naar het project "%s".', + 'Move the task to another column when the due date is less than a certain number of days' => 'Verplaats de taak naar een andere kolom wanneer de vervaldatum minder is dan een bepaald aantal dagen', + 'Automatically update the start date when the task is moved away from a specific column' => 'Automatisch de begindatum bijwerken wanneer de taak wordt verplaatst uit een specifieke kolom', + 'HTTP Client:' => 'HTTP-client', + 'Assigned' => 'Toegekend', + 'Task limits apply to each swimlane individually' => 'Taaklimieten gelden voor elke swimlane afzonderlijk', + 'Column task limits apply to each swimlane individually' => 'Kolom taaklimieten zijn van toepassing op elke swimlane afzonderlijk', + 'Column task limits are applied to each swimlane individually' => 'Kolomtaaklimieten worden toegepast op elke swimlane afzonderlijk', + 'Column task limits are applied across swimlanes' => 'Kolom taaklimieten worden toegepast op alle swimlanes', + 'Task limit: ' => 'Taaklimiet', + 'Change to global tag' => 'Globale tag wijzigen', + 'Do you really want to make the tag "%s" global?' => 'Wil je de tag "%s" echt globaal maken?', + 'Enable global tags for this project' => 'Globale tags inschakelen voor dit project', + 'Group membership(s):' => 'Groep lidmaatschap(pen)', + '%s is a member of the following group(s): %s' => '%s is lid van de volgende groep(en): %s', + '%d/%d group(s) shown' => '%d/%d groep(en) getoond', + 'Subtask creation or modification' => 'Subtaak aanmaken of wijzigen', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Wijs de taak toe aan een specifieke gebruiker wanneer de taak wordt verplaatst naar een specifieke swimlane', + 'Comment' => 'Commentaar', + 'Collapse vertically' => 'Verticaal samenvouwen', + 'Expand vertically' => 'Verticaal uitvouwen', + 'MXN - Mexican Peso' => 'MXN - Mexicaanse peso', + 'Estimated vs actual time per column' => 'Geschatte vs werkelijke tijd per kolom', + 'HUF - Hungarian Forint' => 'HUF - Hongaarse forint', + 'XBT - Bitcoin' => 'XBT - Bitcoin', + 'You must select a file to upload as your avatar!' => 'U moet een bestand selecteren om als uw avatar te uploaden!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'Het bestand dat u heeft geüpload is geen geldige afbeelding! (Alleen *.gif, *.jpg, *.jpeg en *.png zijn toegestaan!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Stel automatisch de vervaldatum in wanneer de taak uit een specifieke kolom wordt verplaatst', + 'No other projects found.' => 'Geen andere projecten gevonden.', + 'Tasks copied successfully.' => 'Taken succesvol gekopieerd.', + 'Unable to copy tasks.' => 'Kan taken niet kopiëren.', + 'Theme' => 'Thema', + 'Theme:' => 'Thema:', + 'Light theme' => 'Licht thema', + 'Dark theme' => 'Donker thema', + 'Automatic theme - Sync with system' => 'Automatisch thema - Synchroniseren met systeem', + 'Application managers or more' => 'Applicatiebeheerders of meer', + 'Administrators' => 'Beheerders', + 'Visibility:' => 'Zichtbaarheid:', + 'Standard users' => 'Standaardgebruikers', + 'Visibility is required' => 'Zichtbaarheid is vereist', + 'The visibility should be an app role' => 'De zichtbaarheid moet een app-rol zijn', + 'Reply' => 'Beantwoorden', + '%s wrote: ' => '%s schreef: ', + 'Number of visible tasks in this column and swimlane' => 'Aantal zichtbare taken in deze kolom en swimlane', + 'Number of tasks in this swimlane' => 'Aantal taken in deze swimlane', + 'Unable to find another subtask in progress, you can close this window.' => 'Kan geen andere subtaak in uitvoering vinden, u kunt dit venster sluiten.', + 'This theme is invalid' => 'Dit thema is ongeldig', + 'This role is invalid' => 'Deze rol is ongeldig', + 'This timezone is invalid' => 'Deze tijdzone is ongeldig', + 'This language is invalid' => 'Deze taal is ongeldig', + 'This URL is invalid' => 'Deze URL is ongeldig', + 'Date format invalid' => 'Ongeldig datumnotatie', + 'Time format invalid' => 'Ongeldige tijdnotatie', + 'Invalid Mail transport' => 'Ongeldige mailtransport', + 'Color invalid' => 'Ongeldige kleur', + 'This value must be greater or equal to %d' => 'Deze waarde moet groter of gelijk zijn aan %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Voeg een BOM toe aan het begin van het bestand (vereist voor Microsoft Excel)', + 'Just add these tag(s)' => 'Voeg alleen deze tags toe', + 'Remove internal link(s)' => 'Verwijder interne links', + 'Import tasks from another project' => 'Importeer taken uit een ander project', + 'Select the project to copy tasks from' => 'Selecteer het project waarvan u taken wilt kopiëren', + 'The total maximum allowed attachments size is %sB.' => 'De maximaal toegestane totale grootte voor bijlagen is %sB.', + 'Add attachments' => 'Bijlagen toevoegen', + 'Task #%d "%s" is overdue' => 'Taak #%d "%s" is te laat', + 'Enable notifications by default for all new users' => 'Standaard meldingen inschakelen voor alle nieuwe gebruikers', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Wijs de taak toe aan de maker voor specifieke kolommen als er niet handmatig een gebruiker is toegewezen', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Wijs een taak toe aan de ingelogde gebruiker bij het wijzigen van kolom naar de opgegeven kolom als er geen gebruiker is toegewezen', +]; diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php new file mode 100644 index 0000000..c66fe26 --- /dev/null +++ b/app/Locale/pl_PL/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => ' ', + 'None' => 'Brak', + 'Edit' => 'Edytuj', + 'Remove' => 'Usuń', + 'Yes' => 'Tak', + 'No' => 'Nie', + 'cancel' => 'anuluj', + 'or' => 'lub', + 'Yellow' => 'Żółty', + 'Blue' => 'Niebieski', + 'Green' => 'Zielony', + 'Purple' => 'Fioletowy', + 'Red' => 'Czerwony', + 'Orange' => 'Pomarańczowy', + 'Grey' => 'Szary', + 'Brown' => 'Brąz', + 'Deep Orange' => 'Ciemnopomarańczowy', + 'Dark Grey' => 'Ciemnoszary', + 'Pink' => 'Różowy', + 'Teal' => 'Turkusowy', + 'Cyan' => 'Cyjan', + 'Lime' => 'Limonkowy', + 'Light Green' => 'Jasnozielony', + 'Amber' => 'Bursztynowy', + 'Save' => 'Zapisz', + 'Login' => 'Login', + 'Official website:' => 'Oficjalna strona:', + 'Unassigned' => 'Nieprzypisany', + 'View this task' => 'Zobacz zadanie', + 'Remove user' => 'Usuń użytkownika', + 'Do you really want to remove this user: "%s"?' => 'Na pewno chcesz usunąć użytkownika: "%s"?', + 'All users' => 'Wszyscy użytkownicy', + 'Username' => 'Nazwa użytkownika', + 'Password' => 'Hasło', + 'Administrator' => 'Administrator', + 'Sign in' => 'Zaloguj', + 'Users' => 'Użytkownicy', + 'Forbidden' => 'Zabroniony', + 'Access Forbidden' => 'Dostęp zabroniony', + 'Edit user' => 'Edytuj użytkownika', + 'Logout' => 'Wyloguj', + 'Bad username or password' => 'Zła nazwa użytkownika lub hasło', + 'Edit project' => 'Edytuj projekt', + 'Name' => 'Nazwa', + 'Projects' => 'Projekty', + 'No project' => 'Brak projektów', + 'Project' => 'Projekt', + 'Status' => 'Status', + 'Tasks' => 'Zadania', + 'Board' => 'Tablica', + 'Actions' => 'Akcje', + 'Inactive' => 'Nieaktywny', + 'Active' => 'Aktywny', + 'Unable to update this board.' => 'Nie można zaktualizować tablicy.', + 'Disable' => 'Wyłącz', + 'Enable' => 'Włącz', + 'New project' => 'Nowy projekt', + 'Do you really want to remove this project: "%s"?' => 'Na pewno chcesz usunąć projekt: "%s"?', + 'Remove project' => 'Usuń projekt', + 'Edit the board for "%s"' => 'Edytuj tablicę dla "%s"', + 'Add a new column' => 'Dodaj nową kolumnę', + 'Title' => 'Nazwa', + 'Assigned to %s' => 'Przypisane do %s', + 'Remove a column' => 'Usuń kolumnę', + 'Unable to remove this column.' => 'Nie udało się usunąć kolumny.', + 'Do you really want to remove this column: "%s"?' => 'Na pewno chcesz usunąć kolumnę: "%s"?', + 'Settings' => 'Ustawienia', + 'Application settings' => 'Ustawienia aplikacji', + 'Language' => 'Język', + 'Webhook token:' => 'Token :', + 'API token:' => 'Token dla API', + 'Database size:' => 'Rozmiar bazy danych :', + 'Download the database' => 'Pobierz bazę danych', + 'Optimize the database' => 'Optymalizuj bazę danych', + '(VACUUM command)' => '(komenda VACUUM)', + '(Gzip compressed Sqlite file)' => '(skompresowany gzip-em plik bazy danych sqlite)', + 'Close a task' => 'Zakończ zadanie', + 'Column' => 'Kolumna', + 'Color' => 'Kolor', + 'Assignee' => 'Odpowiedzialny', + 'Create another task' => 'Dodaj kolejne zadanie', + 'New task' => 'Nowe zadanie', + 'Open a task' => 'Otwórz zadanie', + 'Do you really want to open this task: "%s"?' => 'Na pewno chcesz otworzyć zadanie: "%s"?', + 'Back to the board' => 'Powrót do tablicy', + 'There is nobody assigned' => 'Nikt nie jest przypisany', + 'Column on the board:' => 'Kolumna na tablicy:', + 'Close this task' => 'Zamknij zadanie', + 'Open this task' => 'Otwórz zadanie', + 'There is no description.' => 'Brak opisu.', + 'Add a new task' => 'Dodaj zadanie', + 'The username is required' => 'Nazwa użytkownika jest wymagana', + 'The maximum length is %d characters' => 'Maksymalna długość wynosi %d znaków', + 'The minimum length is %d characters' => 'Minimalna długość wynosi %d znaków', + 'The password is required' => 'Hasło jest wymagane', + 'This value must be an integer' => 'Wartość musi być liczbą całkowitą', + 'The username must be unique' => 'Nazwa użytkownika musi być unikalna', + 'The user id is required' => 'ID użytkownika jest wymagane', + 'Passwords don\'t match' => 'Hasła nie pasują do siebie', + 'The confirmation is required' => 'Wymagane jest potwierdzenie', + 'The project is required' => 'Projekt jest wymagany', + 'The id is required' => 'ID jest wymagane', + 'The project id is required' => 'ID projektu jest wymagane', + 'The project name is required' => 'Nazwa projektu jest wymagana', + 'The title is required' => 'Tytuł jest wymagany', + 'Settings saved successfully.' => 'Ustawienia zapisane.', + 'Unable to save your settings.' => 'Nie udało się zapisać ustawień.', + 'Database optimization done.' => 'Optymalizacja bazy danych zakończona.', + 'Your project has been created successfully.' => 'Projekt został pomyślnie utworzony.', + 'Unable to create your project.' => 'Nie udało się stworzyć projektu.', + 'Project updated successfully.' => 'Projekt zaktualizowany.', + 'Unable to update this project.' => 'Nie można zaktualizować projektu.', + 'Unable to remove this project.' => 'Nie można usunąć projektu.', + 'Project removed successfully.' => 'Projekt usunięty.', + 'Project activated successfully.' => 'Projekt aktywowany.', + 'Unable to activate this project.' => 'Nie można aktywować projektu.', + 'Project disabled successfully.' => 'Projekt wyłączony.', + 'Unable to disable this project.' => 'Nie można wyłączyć projektu.', + 'Unable to open this task.' => 'Nie można otworzyć tego zadania.', + 'Task opened successfully.' => 'Zadanie otwarte.', + 'Unable to close this task.' => 'Nie można zamknąć tego zadania.', + 'Task closed successfully.' => 'Zadanie zamknięte.', + 'Unable to update your task.' => 'Nie można zaktualizować tego zadania.', + 'Task updated successfully.' => 'Zadanie zaktualizowane.', + 'Unable to create your task.' => 'Nie można dodać zadania.', + 'Task created successfully.' => 'Zadanie zostało utworzone.', + 'User created successfully.' => 'Użytkownik dodany', + 'Unable to create your user.' => 'Nie udało się dodać użytkownika.', + 'User updated successfully.' => 'Profil użytkownika został zaaktualizowany.', + 'User removed successfully.' => 'Użytkownik usunięty.', + 'Unable to remove this user.' => 'Nie udało się usunąć użytkownika.', + 'Board updated successfully.' => 'Tablica została zaktualizowana.', + 'Ready' => 'Gotowe', + 'Backlog' => 'Log', + 'Work in progress' => 'W trakcie', + 'Done' => 'Zakończone', + 'Application version:' => 'Wersja aplikacji:', + 'Id' => 'Id', + 'Public link' => 'Link publiczny', + 'Timezone' => 'Strefa czasowa', + 'Sorry, I didn\'t find this information in my database!' => 'Niestety nie znaleziono tej informacji w bazie danych', + 'Page not found' => 'Strona nie istnieje', + 'Complexity' => 'Poziom trudności', + 'Task limit' => 'Limit zadań', + 'Task count' => 'Liczba zadań', + 'User' => 'Użytkownik', + 'Comments' => 'Komentarze', + 'Comment is required' => 'Komentarz jest wymagany', + 'Comment added successfully.' => 'Komentarz dodany', + 'Unable to create your comment.' => 'Nie udało się dodać komentarza', + 'Due Date' => 'Termin', + 'Invalid date' => 'Błędna data', + 'Automatic actions' => 'Akcje automatyczne', + 'Your automatic action has been created successfully.' => 'Twoja akcja została dodana', + 'Unable to create your automatic action.' => 'Nie udało się utworzyć akcji', + 'Remove an action' => 'Usuń akcję', + 'Unable to remove this action.' => 'Nie można usunąć akcji', + 'Action removed successfully.' => 'Akcja usunięta', + 'Automatic actions for the project "%s"' => 'Akcje automatyczne dla projektu "%s"', + 'Add an action' => 'Nowa akcja', + 'Event name' => 'Nazwa zdarzenia', + 'Action' => 'Akcja', + 'Event' => 'Zdarzenie', + 'When the selected event occurs execute the corresponding action.' => 'Gdy następuje wybrane zdarzenie, uruchom odpowiednią akcję', + 'Next step' => 'Następny krok', + 'Define action parameters' => 'Zdefiniuj parametry akcji', + 'Do you really want to remove this action: "%s"?' => 'Na pewno chcesz usunąć akcję "%s"?', + 'Remove an automatic action' => 'Usuń akcję automatyczną', + 'Assign the task to a specific user' => 'Przypisz zadanie do wybranego użytkownika', + 'Assign the task to the person who does the action' => 'Przypisz zadanie do osoby wykonującej akcję', + 'Duplicate the task to another project' => 'Kopiuj zadanie do innego projektu', + 'Move a task to another column' => 'Przeniesienie zadania do innej kolumny', + 'Task modification' => 'Modyfikacja zadania', + 'Task creation' => 'Tworzenie zadania', + 'Closing a task' => 'Zamknięcie zadania', + 'Assign a color to a specific user' => 'Przypisz kolor do wybranego użytkownika', + 'Position' => 'Pozycja', + 'Duplicate to project' => 'Skopiuj do innego projektu', + 'Duplicate' => 'Utwórz kopię', + 'Link' => 'Link', + 'Comment updated successfully.' => 'Komentarz został zapisany.', + 'Unable to update your comment.' => 'Nie udało się zapisać komentarza.', + 'Remove a comment' => 'Usuń komentarz', + 'Comment removed successfully.' => 'Komentarz został usunięty.', + 'Unable to remove this comment.' => 'Nie udało się usunąć komentarza.', + 'Do you really want to remove this comment?' => 'Czy na pewno usunąć ten komentarz?', + 'Current password for the user "%s"' => 'Aktualne hasło dla użytkownika "%s"', + 'The current password is required' => 'Wymanage jest aktualne hasło', + 'Wrong password' => 'Błędne hasło', + 'Unknown' => 'Nieznany', + 'Last logins' => 'Ostatnie logowania', + 'Login date' => 'Data logowania', + 'Authentication method' => 'Sposób uwierzytelnienia', + 'IP address' => 'Adres IP', + 'User agent' => 'Przeglądarka', + 'Persistent connections' => 'Stałe połączenia', + 'No session.' => 'Brak sesji.', + 'Expiration date' => 'Data zakończenia', + 'Remember Me' => 'Pamiętaj mnie', + 'Creation date' => 'Data utworzenia', + 'Everybody' => 'Wszyscy', + 'Open' => 'Otwarto', + 'Closed' => 'Zamknięto', + 'Search' => 'Szukaj', + 'Nothing found.' => 'Nic nie znaleziono', + 'Due date' => 'Termin', + 'Description' => 'Opis', + '%d comments' => '%d Komentarzy', + '%d comment' => '%d Komentarz', + 'Email address invalid' => 'Błędny adres email', + 'Your external account is not linked anymore to your profile.' => 'Twoje zewnętrzne konto nie jest już połączone z profilem', + 'Unable to unlink your external account.' => 'Nie można odłączyć zewnętrznego konta', + 'External authentication failed' => 'Uwierzytelnianie zewnętrzne zakończyło się niepowodzeniem', + 'Your external account is linked to your profile successfully.' => 'Twoje zewnętrzne konto zostało pomyślnie połączone z profilem', + 'Email' => 'Email', + 'Task removed successfully.' => 'Zadanie usunięto pomyślnie.', + 'Unable to remove this task.' => 'Nie można usunąć tego zadania.', + 'Remove a task' => 'Usuń zadanie', + 'Do you really want to remove this task: "%s"?' => 'Czy na pewno chcesz usunąć zadanie "%s"?', + 'Assign automatically a color based on a category' => 'Przypisz kolor automatycznie na podstawie kategori', + 'Assign automatically a category based on a color' => 'Przypisz kategorię automatycznie na podstawie koloru', + 'Task creation or modification' => 'Tworzenie lub usuwanie zadania', + 'Category' => 'Kategoria', + 'Category:' => 'Kategoria:', + 'Categories' => 'Kategorie', + 'Your category has been created successfully.' => 'Pomyślnie utworzono kategorię.', + 'This category has been updated successfully.' => 'Pomyślnie zaktualizowano kategorię', + 'Unable to update this category.' => 'Nie można zaktualizować kategorii', + 'Remove a category' => 'Usuń kategorię', + 'Category removed successfully.' => 'Pomyślnie usunięto kategorię.', + 'Unable to remove this category.' => 'Nie można usunąć tej kategorii.', + 'Category modification for the project "%s"' => 'Zmiana kategorii projektu "%s"', + 'Category Name' => 'Nazwa kategorii', + 'Add a new category' => 'Utwórz nową kategorię', + 'Do you really want to remove this category: "%s"?' => 'Czy na pewno chcesz usunąć kategorię: "%s"?', + 'All categories' => 'Wszystkie kategorie', + 'No category' => 'Brak kategorii', + 'The name is required' => 'Nazwa jest wymagana', + 'Remove a file' => 'Usuń plik', + 'Unable to remove this file.' => 'Nie można usunąć tego pliku.', + 'File removed successfully.' => 'Plik Usunięty pomyślnie.', + 'Attach a document' => 'Dołącz plik', + 'Do you really want to remove this file: "%s"?' => 'Czy na pewno chcesz usunąć plik: "%s"?', + 'Attachments' => 'Załączniki', + 'Edit the task' => 'Edytuj zadanie', + 'Add a comment' => 'Dodaj komentarz', + 'Edit a comment' => 'Edytuj komentarz', + 'Summary' => 'Podsumowanie', + 'Time tracking' => 'Śledzenie czasu', + 'Estimate:' => 'Szacowany:', + 'Spent:' => 'Przeznaczony:', + 'Do you really want to remove this sub-task?' => 'Czy na pewno chcesz usunąć to pod-zadanie?', + 'Remaining:' => 'Pozostało:', + 'hours' => 'godzin(y)', + 'estimated' => 'szacowany', + 'Sub-Tasks' => 'Pod-zadania', + 'Add a sub-task' => 'Dodaj pod-zadanie', + 'Original estimate' => 'Szacowanie początkowe', + 'Create another sub-task' => 'Dodaj kolejne pod-zadanie', + 'Time spent' => 'Spędzony czas', + 'Edit a sub-task' => 'Edytuj pod-zadanie', + 'Remove a sub-task' => 'Usuń pod-zadanie', + 'The time must be a numeric value' => 'Czas musi być wartością liczbową', + 'Todo' => 'Do zrobienia', + 'In progress' => 'W trakcie', + 'Sub-task removed successfully.' => 'Pod-zadanie usunięte pomyślnie.', + 'Unable to remove this sub-task.' => 'Nie można usunąć tego pod-zadania.', + 'Sub-task updated successfully.' => 'Pod-zadanie zaktualizowane pomyślnie.', + 'Unable to update your sub-task.' => 'Nie można zaktualizować tego pod-zadania.', + 'Unable to create your sub-task.' => 'Nie można utworzyć tego pod-zadania.', + 'Maximum size: ' => 'Maksymalny rozmiar: ', + 'Display another project' => 'Wyświetl inny projekt', + 'Created by %s' => 'Utworzone przez %s', + 'Tasks Export' => 'Eksport zadań', + 'Start Date' => 'Data początkowa', + 'Execute' => 'Wykonaj', + 'Task Id' => 'Identyfikator Zadania', + 'Creator' => 'Autor', + 'Modification date' => 'Data modyfikacji', + 'Completion date' => 'Data ukończenia', + 'Clone' => 'Sklonuj', + 'Project cloned successfully.' => 'Projekt sklonowany pomyślnie.', + 'Unable to clone this project.' => 'Nie można sklonować projektu.', + 'Enable email notifications' => 'Włącz powiadomienia email', + 'Task position:' => 'Pozycja zadania:', + 'The task #%d has been opened.' => 'Zadania #%d zostały otwarte.', + 'The task #%d has been closed.' => 'Zadania #%d zostały zamknięte.', + 'Sub-task updated' => 'Pod-zadanie zaktualizowane', + 'Title:' => 'Nazwa:', + 'Status:' => 'Status:', + 'Assignee:' => 'Przypisano do:', + 'Time tracking:' => 'Śledzenie czasu: ', + 'New sub-task' => 'Nowe Pod-zadanie', + 'New attachment added "%s"' => 'Nowy załącznik dodany "%s"', + 'New comment posted by %s' => 'Nowy komentarz dodany przez %s', + 'New comment' => 'Nowy Komentarz', + 'Comment updated' => 'Komentarz zaktualizowany', + 'New subtask' => 'Nowe pod-zadanie', + 'I only want to receive notifications for these projects:' => 'Chcę otrzymywać powiadomienia tylko dla poniższych projektów:', + 'view the task on Kanboard' => 'Zobacz zadanie', + 'Public access' => 'Dostęp publiczny', + 'Disable public access' => 'Zablokuj dostęp publiczny', + 'Enable public access' => 'Odblokuj dostęp publiczny', + 'Public access disabled' => 'Dostęp publiczny zablokowany', + 'Move the task to another project' => 'Przenieś zadanie do innego projektu', + 'Move to project' => 'Przenieś do innego projektu', + 'Do you really want to duplicate this task?' => 'Czy na pewno chcesz zduplikować to zadanie?', + 'Duplicate a task' => 'Zduplikuj zadanie', + 'External accounts' => 'Konta zewnętrzne', + 'Account type' => 'Typ konta', + 'Local' => 'Lokalne', + 'Remote' => 'Zdalne', + 'Enabled' => 'Odblokowane', + 'Disabled' => 'Zablokowane', + 'Login:' => 'Nazwa Użytkownika (login):', + 'Full Name:' => 'Imię i Nazwisko', + 'Email:' => 'Email: ', + 'Notifications:' => 'Powiadomienia: ', + 'Notifications' => 'Powiadomienia', + 'Account type:' => 'Typ konta:', + 'Edit profile' => 'Edytuj profil', + 'Change password' => 'Zmień hasło', + 'Password modification' => 'Zmiana hasła', + 'External authentications' => 'Uwierzytelnienia zewnętrzne', + 'Never connected.' => 'Nigdy nie połączone.', + 'No external authentication enabled.' => 'Brak włączonych uwierzytelnień zewnętrznych.', + 'Password modified successfully.' => 'Hasło zmienione pomyślne.', + 'Unable to change the password.' => 'Nie można zmienić hasła.', + 'Change category' => 'Zmień kategorię', + '%s updated the task %s' => '%s zaktualizował zadanie %s', + '%s opened the task %s' => '%s otworzył zadanie %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s przeniósł zadanie %s na pozycję #%d w kolumnie "%s"', + '%s moved the task %s to the column "%s"' => '%s przeniósł zadanie %s do kolumny "%s"', + '%s created the task %s' => '%s utworzył zadanie %s', + '%s closed the task %s' => '%s zamknął zadanie %s', + '%s created a subtask for the task %s' => '%s utworzył pod-zadanie dla zadania %s', + '%s updated a subtask for the task %s' => '%s zaktualizował pod-zadanie dla zadania %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Przypisano do %s z szacowanym czasem wykonania %s/%sh', + 'Not assigned, estimate of %sh' => 'Nie przypisane, szacowany czas wykonania %sh', + '%s updated a comment on the task %s' => '%s zaktualizował komentarz do zadania %s', + '%s commented the task %s' => '%s skomentował zadanie %s', + '%s\'s activity' => 'Aktywność %s', + 'RSS feed' => 'Kanał RSS', + '%s updated a comment on the task #%d' => '%s zaktualizował komentarz do zadania #%d', + '%s commented on the task #%d' => '%s skomentował zadanie #%d', + '%s updated a subtask for the task #%d' => '%s zaktualizował pod-zadanie dla zadania #%d', + '%s created a subtask for the task #%d' => '%s utworzył pod-zadanie dla zadania #%d', + '%s updated the task #%d' => '%s zaktualizował zadanie #%d', + '%s created the task #%d' => '%s utworzył zadanie #%d', + '%s closed the task #%d' => '%s zamknął zadanie #%d', + '%s opened the task #%d' => '%s otworzył zadanie #%d', + 'Activity' => 'Aktywność', + 'Default values are "%s"' => 'Domyślne wartości: "%s"', + 'Default columns for new projects (Comma-separated)' => 'Domyślne kolumny dla nowych projektów (oddzielone przecinkiem)', + 'Task assignee change' => 'Zmień osobę odpowiedzialną', + '%s changed the assignee of the task #%d to %s' => '%s zmienił osobę odpowiedzialną za zadanie #%d na %s', + '%s changed the assignee of the task %s to %s' => '%s zmienił osobę odpowiedzialną za zadanie %s na %s', + 'New password for the user "%s"' => 'Nowe hasło użytkownika "%s"', + 'Choose an event' => 'Wybierz zdarzenie', + 'Create a task from an external provider' => 'Utwórz zadanie z dostawcy zewnętrznego', + '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ętrznej etykiety', + 'Reference' => 'Odniesienie', + 'Label' => 'Etykieta', + 'Database' => 'Baza danych', + 'About' => 'Informacje', + 'Database driver:' => 'Silnik bazy danych:', + 'Board settings' => 'Ustawienia tablicy', + 'Webhook settings' => 'Ustawienia webhook', + 'Reset token' => 'Resetuj token', + 'API endpoint:' => 'Endpoint API', + 'Refresh interval for personal board' => 'Częstotliwość odświeżania dla tablicy prywatnej', + 'Refresh interval for public board' => 'Częstotliwość odświeżania dla tablicy publicznej', + 'Task highlight period' => 'Okres wyróżniania zadań', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Okres (w sekundach) wymagany do uznania zadania za niedawno zmienione (0 ab zablokować, domyślnie 2 dni)', + 'Frequency in second (60 seconds by default)' => 'Częstotliwość w sekundach (domyślnie 60 sekund)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Częstotliwość w sekundach (0 aby zablokować, domyślnie 10 sekund)', + 'Application URL' => 'Adres URL aplikacji', + 'Token regenerated.' => 'Token wygenerowany ponownie.', + 'Date format' => 'Format daty', + 'ISO format is always accepted, example: "%s" and "%s"' => 'Format ISO jest zawsze akceptowany, przykłady: "%s", "%s"', + 'New personal project' => 'Nowy projekt prywatny', + 'This project is personal' => 'Ten projekt jest prywatny', + 'Add' => 'Dodaj', + 'Start date' => 'Data rozpoczęcia', + 'Time estimated' => 'Szacowany czas', + 'There is nothing assigned to you.' => 'Nie ma przypisanych zadań', + 'My tasks' => 'Moje zadania', + 'Activity stream' => 'Strumień aktywności', + 'Dashboard' => 'Dashboard', + 'Confirmation' => 'Potwierdzenie', + 'Webhooks' => 'Webhooki', + 'API' => 'API', + 'Create a comment from an external provider' => 'Utwórz komentarz od zewnętrznego dostawcy', + 'Project management' => 'Menadżer projektu', + 'Columns' => 'Kolumny', + 'Task' => 'Zadanie', + 'Percentage' => 'Procent', + 'Number of tasks' => 'Liczba zadań', + 'Task distribution' => 'Rozmieszczenie zadań', + 'Analytics' => 'Analizy', + 'Subtask' => 'Pod-zadanie', + 'User repartition' => 'Przydział użytkownika', + 'Clone this project' => 'Sklonuj ten projekt', + 'Column removed successfully.' => 'Kolumna usunięta pomyślnie.', + 'Not enough data to show the graph.' => 'Za mało danych do utworzenia wykresu.', + 'Previous' => 'Poprzedni', + '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-zadanie 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 zadania musi być liczbą całkowitą', + 'The user id must be an integer' => 'ID użytkownika musi być liczbą całkowitą', + 'This value is required' => 'Wymagana wartość', + 'This value must be numeric' => 'Wartość musi być liczbą', + 'Unable to create this task.' => 'Nie można tworzyć zadania.', + 'Cumulative flow diagram' => 'Zbiorowy diagram przepływu', + 'Daily project summary' => 'Dzienne raport z projektu', + 'Daily project summary export' => 'Eksport dziennego podsumowania projektu', + 'Exports' => 'Eksporty', + 'This export contains the number of tasks per column grouped per day.' => 'Ten eksport zawiera ilość zadań zgrupowanych w kolumnach na dzień', + 'Active swimlanes' => 'Aktywne tory', + 'Add a new swimlane' => 'Dodaj tor', + 'Default swimlane' => 'Domyślny tor', + 'Do you really want to remove this swimlane: "%s"?' => 'Czy na pewno chcesz usunąć tor: "%s"?', + 'Inactive swimlanes' => 'Nieaktywne tory', + 'Remove a swimlane' => 'Usuń tor', + 'Swimlane modification for the project "%s"' => 'Edycja torów dla projektu "%s"', + 'Swimlane removed successfully.' => 'Tor usunięty pomyślnie.', + 'Swimlanes' => 'Tory', + 'Swimlane updated successfully.' => 'Zaktualizowano tor.', + 'Unable to remove this swimlane.' => 'Nie można usunąć toru.', + 'Unable to update this swimlane.' => 'Nie można zaktualizować toru.', + 'Your swimlane has been created successfully.' => 'Tor utworzony pomyślnie.', + 'Example: "Bug, Feature Request, Improvement"' => 'Przykład: "Błąd, Żądanie Funkcjonalności, Udoskonalenia"', + 'Default categories for new projects (Comma-separated)' => 'Domyślne kategorie dla nowych projektów (oddzielone przecinkiem)', + 'Integrations' => 'Integracje', + 'Integration with third-party services' => 'Integracja z usługami firm trzecich', + 'Subtask Id' => 'ID pod-zadania', + 'Subtasks' => 'Pod-zadania', + 'Subtasks Export' => 'Eksport pod-zadań', + 'Task Title' => 'Nazwa zadania', + 'Untitled' => 'Bez nazwy', + 'Application default' => 'Domyślne dla aplikacji', + 'Language:' => 'Język:', + 'Timezone:' => 'Strefa czasowa:', + 'All columns' => 'Wszystkie kolumny', + 'Next' => 'Następny', + '#%d' => 'nr %d', + 'All swimlanes' => 'Wszystkie tory', + 'All colors' => 'Wszystkie kolory', + 'Moved to column %s' => 'Przeniosiono do kolumny %s', + 'User dashboard' => 'Panel użytkownika', + 'Allow only one subtask in progress at the same time for a user' => 'Zezwalaj na tylko jedno pod-zadanie o statusie "w trakcie" jednocześnie', + 'Edit column "%s"' => 'Zmień kolumnę "%s"', + 'Select the new status of the subtask: "%s"' => 'Wybierz nowy status dla pod-zadania: "%s"', + 'Subtask timesheet' => 'Oś czasu pod-zadania', + 'There is nothing to show.' => 'Nie nic do wyświetlenia', + 'Time Tracking' => 'Śledzenie czasu', + 'You already have one subtask in progress' => 'Masz już zadanie o statusie "w trakcie"', + 'Which parts of the project do you want to duplicate?' => 'Które elementy projektu chcesz zduplikować?', + 'Disallow login form' => 'Zablokuj możliwość logowania', + 'Start' => 'Początek', + 'End' => 'Koniec', + 'Task age in days' => 'Wiek zadania w dniach', + 'Days in this column' => 'Dni w tej kolumnie', + '%dd' => '%d dni', + 'Add a new link' => 'Dodaj nowy link', + 'Do you really want to remove this link: "%s"?' => 'Czy na pewno chcesz usunąć ten link: "%s"?', + 'Do you really want to remove this link with task #%d?' => 'Czy na pewno chcesz usunąć ten link razem z zadaniem nr %d?', + 'Field required' => 'Pole wymagane', + 'Link added successfully.' => 'Link dodany', + 'Link updated successfully.' => 'Link zaktualizowany', + 'Link removed successfully.' => 'Link usunięty', + 'Link labels' => 'Etykiety linku', + 'Link modification' => 'Modyfikuj link', + 'Opposite label' => 'Etykieta odwrotna', + 'Remove a link' => 'Usuń link', + 'The labels must be different' => 'Etykiety muszą być różne', + 'There is no link.' => 'Brak linku', + 'This label must be unique' => 'Etykieta musi być unikatowa', + 'Unable to create your link.' => 'Nie można utworzyć linku.', + 'Unable to update your link.' => 'Nie można zaktualizować linku,', + 'Unable to remove this link.' => 'Nie można usunąć linku,', + 'relates to' => 'odnosi się do', + 'blocks' => 'blokuje', + 'is blocked by' => 'jest blokowane przez', + 'duplicates' => 'duplikuje', + 'is duplicated by' => 'jest duplikowane przez', + 'is a child of' => 'jest dzieckiem', + 'is a parent of' => 'jest rodzicem', + 'targets milestone' => 'oznacza krok milowy', + 'is a milestone of' => 'jest krokiem milowym', + 'fixes' => 'naprawia', + 'is fixed by' => 'zostało naprawione przez', + 'This task' => 'To zadanie', + '<1h' => '<1h', + '%dh' => '%dh', + 'Expand tasks' => 'Rozwiń zadania', + 'Collapse tasks' => 'Zwiń zadania', + 'Expand/collapse tasks' => 'Zwiń/Rozwiń zadania', + 'Close dialog box' => 'Zamknij okno', + 'Submit a form' => 'Wyślij formularz', + 'Board view' => 'Widok tablicy', + 'Keyboard shortcuts' => 'Skróty klawiszowe', + 'Open board switcher' => 'Przełącz tablice', + 'Application' => 'Aplikacja', + 'Compact view' => 'Widok kompaktowy', + 'Horizontal scrolling' => 'Przewijanie poziome', + 'Compact/wide view' => 'Pełny/Kompaktowy widok', + 'Currency' => 'Waluta', + 'Personal project' => 'Projekt prywatny', + 'AUD - Australian Dollar' => 'AUD - Dolar australijski', + 'CAD - Canadian Dollar' => 'CAD - Dolar kanadyjski', + 'CHF - Swiss Francs' => 'CHF - Frank szwajcarski', + 'Custom Stylesheet' => 'Niestandardowy arkusz stylów', + 'EUR - Euro' => 'EUR - Euro', + 'GBP - British Pound' => 'GBP - Funt brytyjski', + 'INR - Indian Rupee' => 'INR - Rupia indyjska', + 'JPY - Japanese Yen' => 'JPY - Jen japoński', + 'NZD - New Zealand Dollar' => 'NZD - Dolar nowozelandzki', + 'PEN - Peruvian Sol' => 'PEN - Sol peruwiański', + 'RSD - Serbian dinar' => 'RSD - Dinar serbski', + 'CNY - Chinese Yuan' => 'CNY - Juan chiński', + 'USD - US Dollar' => 'USD - Dolar amerykański', + 'VES - Venezuelan Bolívar' => 'VES - Boliwar wenezuelski', + 'Destination column' => 'Kolumna docelowa', + 'Move the task to another column when assigned to a user' => 'Przenieś zadanie do innej kolumny gdy zostanie przypisane do osoby', + 'Move the task to another column when assignee is cleared' => 'Przenieś zadanie do innej kolumny gdy osoba odpowiedzialna zostanie usunięta', + 'Source column' => 'Kolumna źródłowa', + 'Transitions' => 'Przeniesienia', + 'Executer' => 'Wykonał', + 'Time spent in the column' => 'Czas spędzony w tej kolumnie', + 'Task transitions' => 'Przeniesienia zadań', + 'Task transitions export' => 'Wygeneruj raport z przeniesień zadań', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Ten raport zawiera wszystkie przeniesienia pomiędzy kolumnami wraz z datą oraz osobą odpowiedzialną', + 'Currency rates' => 'Kursy walut', + 'Rate' => 'Kurs', + 'Change reference currency' => 'Zmień walutę referencyjną', + 'Reference currency' => 'Waluta referencyjna', + 'The currency rate has been added successfully.' => 'Dodano kurs waluty', + 'Unable to add this currency rate.' => 'Nie można dodać kursu waluty', + 'Webhook URL' => 'Adres webhooka', + '%s removed the assignee of the task %s' => '%s usunął osobę przypisaną do zadania %s', + 'Information' => 'Informacje', + 'Check two factor authentication code' => 'Sprawdź kod weryfikujący', + 'The two factor authentication code is not valid.' => 'Kod weryfikujący niepoprawny', + 'The two factor authentication code is valid.' => 'Kod weryfikujący poprawny', + 'Code' => 'Kod', + 'Two factor authentication' => 'Dwustopniowe uwierzytelnianie', + 'This QR code contains the key URI: ' => 'Ten kod QR zawiera URI klucza: ', + 'Check my code' => 'Sprawdź kod', + 'Secret key: ' => 'Tajny kod: ', + 'Test your device' => 'Przetestuj urządzenie', + 'Assign a color when the task is moved to a specific column' => 'Przypisz kolor gdy zadanie jest przeniesione do danej kolumny', + '%s via Kanboard' => '%s poprzez Kanboard', + 'Burndown chart' => 'Wykres Burndown', + 'This chart show the task complexity over the time (Work Remaining).' => 'Ten wykres pokazuje złożoność zadania na przestrzeni czasu (pozostała praca).', + 'Screenshot taken %s' => 'Zrzut ekranu zapisany %s', + 'Add a screenshot' => 'Dołącz zrzut ekranu', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Zrób zrzut ekranu i wciśnij CTRL+V by dodać go tutaj.', + 'Screenshot uploaded successfully.' => 'Zrzut ekranu dodany.', + 'SEK - Swedish Krona' => 'SEK - Korona szwedzka', + 'Identifier' => 'Identyfikator', + 'Disable two factor authentication' => 'Wyłącz dwustopniowe uwierzytelnianie', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Czy na pewno chcesz wyłączyć dwustopniowe uwierzytelnianie dla tego użytkownika: "%s"?', + 'Edit link' => 'Edytuj link', + 'Start to type task title...' => 'Rozpocznij wpisywanie tytułu zadania...', + 'A task cannot be linked to itself' => 'Link do zadania nie może wskazywać na samego siebie', + 'The exact same link already exists' => 'Taki link już istnieje', + 'Recurrent task is scheduled to be generated' => 'Zaplanowano zadanie cykliczne', + 'Score' => 'Wynik', + 'The identifier must be unique' => 'Identyfikator musi być unikatowy', + 'This linked task id doesn\'t exists' => 'Id zadania nie istnieje', + 'This value must be alphanumeric' => 'Ta wartość musi być alfanumeryczna', + 'Edit recurrence' => 'Edytuj rekurencje', + 'Generate recurrent task' => 'Włącz rekurencje', + 'Trigger to generate recurrent task' => 'Wyzwalacz tworzący zadanie cykliczne', + 'Factor to calculate new due date' => 'Czynnik wyliczający nowy termin', + 'Timeframe to calculate new due date' => 'Ramy czasowe do wyliczenia nowego terminu', + 'Base date to calculate new due date' => 'Data bazowa do wyliczenia nowego terminu', + 'Action date' => 'Data akcji', + 'Base date to calculate new due date: ' => 'Data bazowa do wyliczenia nowego terminu: ', + 'This task has created this child task: ' => 'Zadanie utworzyło zadanie pokrewne: ', + 'Day(s)' => 'Dni', + 'Existing due date' => 'Istniejący termin', + 'Factor to calculate new due date: ' => 'Czynnik wyliczający nowy termin: ', + 'Month(s)' => 'Miesięcy', + 'This task has been created by: ' => 'Zadanie utworzone przez: ', + 'Recurrent task has been generated:' => 'Zadanie cykliczne zostało utworzone: ', + 'Timeframe to calculate new due date: ' => 'Ramy czasowe do wyliczenia nowego terminu: ', + 'Trigger to generate recurrent task: ' => 'Wyzwalacz tworzący zadanie cykliczne: ', + 'When task is closed' => 'Zamknięcie zadania', + 'When task is moved from first column' => 'Przeniesienie zadania z pierwszej kolumny', + 'When task is moved to last column' => 'Przeniesienie zadania do ostatniej kolumny', + 'Year(s)' => 'Lat', + 'Project settings' => 'Ustawienia Projektu', + 'Automatically update the start date' => 'Automatycznie aktualizuj datę rozpoczęcia', + 'iCal feed' => 'kanał iCal', + 'Preferences' => 'Ustawienia', + 'Security' => 'Zabezpieczenia', + 'Two factor authentication disabled' => 'Dwustopniowe uwierzytelnianie wyłączone', + 'Two factor authentication enabled' => 'Dwustopniowe uwierzytelnianie włączone', + 'Unable to update this user.' => 'Nie można zaktualizować tego użytkownika', + 'There is no user management for personal projects.' => 'Projekty prywatne nie wspierają zarządzania użytkownikami. Projekt prywatny ma tylko jednego użytkownika.', + 'User that will receive the email' => 'Adresat', + 'Email subject' => 'Temat', + 'Date' => 'Data', + 'Add a comment log when moving the task between columns' => 'Wygeneruj komentarz pod zadaniem podczas przenoszenia między kolumnami', + 'Move the task to another column when the category is changed' => 'Przenieś zadanie do innej kolumny gdy kategoria ulegnie zmianie', + 'Send a task by email to someone' => 'Wyślij zadanie emailem do kogoś', + 'Reopen a task' => 'Otwórz ponownie zadanie', + 'Notification' => 'Powiadomienie', + '%s moved the task #%d to the first swimlane' => '%s przeniosł zadanie #%d na pierwszy tor', + 'Swimlane' => 'Tor', + '%s moved the task %s to the first swimlane' => '%s przeniosł zadanie %s na pierwszy tor', + '%s moved the task %s to the swimlane "%s"' => '%s przeniosł zadanie %s na tor "%s"', + 'This report contains all subtasks information for the given date range.' => 'Niniejszy raport zawiera wszystkie informacje o pod-zadaniach dla podanego zakresu dat.', + 'This report contains all tasks information for the given date range.' => 'Niniejszy raport zawiera wszystkie informacje o zadaniach dla podanego zakresu dat.', + 'Project activities for %s' => 'Aktywności w ramach projektu dla %s', + 'view the board on Kanboard' => 'Zobacz tablice', + 'The task has been moved to the first swimlane' => 'Zadanie zostało przeniesione do piewszego toru', + 'The task has been moved to another swimlane:' => 'Zadanie zostało przeniesione do innego toru:', + 'New title: %s' => 'Nowy tytuł: %s', + 'The task is not assigned anymore' => 'Brak osoby odpowiedzialnej za zadanie', + 'New assignee: %s' => 'Nowy odpowiedzialny: %s', + 'There is no category now' => 'Aktualnie zadanie nie posiada kategorii', + 'New category: %s' => 'Nowa kategoria: %s', + 'New color: %s' => 'Nowy kolor: %s', + 'New complexity: %d' => 'Nowa złożoność: %d', + 'The due date has been removed' => 'Termin został usunięty', + 'There is no description anymore' => 'Nie ma już opisu', + 'Recurrence settings has been modified' => 'Ustawienia cyklu zostały zmienione', + 'Time spent changed: %sh' => 'Spędzony czas uległ zmianie: %sh', + 'Time estimated changed: %sh' => 'Szacowany czas uległ zmianie: %sh', + 'The field "%s" has been updated' => 'Pole "%s" zostało zaktualizowane', + 'The description has been modified:' => 'Opis został zmodyfikowany:', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Naprawdę chcesz zamknąć zadanie "%s" wraz z wszystkimi pod-zadaniami?', + 'I want to receive notifications for:' => 'Wysyłaj powiadomienia dla:', + 'All tasks' => 'Wszystkich zadań', + 'Only for tasks assigned to me' => 'Tylko zadań przypisanych do mnie', + 'Only for tasks created by me' => 'Tylko zadań utworzonych przeze mnie', + 'Only for tasks created by me and tasks assigned to me' => 'Tylko zadań przypisanych lub utworzonych przeze mnie', + '%%Y-%%m-%%d' => '%%Y-%%m-%%d', + 'Total for all columns' => 'Ogółem dla wszystkich kolumn', + 'You need at least 2 days of data to show the chart.' => 'Potrzebujesz przynajmniej 2 dni by wyświetlić wykres', + '<15m' => '<15m', + '<30m' => '<30m', + 'Stop timer' => 'Zatrzymaj pomiar czasu', + 'Start timer' => 'Uruchom pomiar czasu', + 'My activity stream' => 'Moja aktywność', + 'Search tasks' => 'Szukaj zadań', + 'Reset filters' => 'Resetuj zastosowane filtry', + 'My tasks due tomorrow' => 'Moje zadania do jutra', + 'Tasks due today' => 'Zadania do dzisiaj', + 'Tasks due tomorrow' => 'Zadania do jutra', + 'Tasks due yesterday' => 'Zadania na wczoraj', + 'Closed tasks' => 'Zamknięte zadania', + 'Open tasks' => 'Otwarte zadania', + 'Not assigned' => 'Nieprzypisane zadania', + 'View advanced search syntax' => 'Pomoc dotycząca budowania filtrów', + 'Overview' => 'Podsumowanie', + 'Board/Calendar/List view' => 'Widok: Tablica/Kalendarz/Lista', + 'Switch to the board view' => 'Przełącz na tablicę', + 'Switch to the list view' => 'Przełącz na listę', + 'Go to the search/filter box' => 'Użyj pola wyszukiwania/filtrów', + 'There is no activity yet.' => 'Brak powiadomień', + 'No tasks found.' => 'Nie znaleziono zadań', + 'Keyboard shortcut: "%s"' => 'Skrót klawiaturowy: "%s"', + 'List' => 'Lista', + 'Filter' => 'Filtr', + 'Advanced search' => 'Zaawansowane wyszukiwanie', + 'Example of query: ' => 'Przykładowe zapytanie:', + 'Search by project: ' => 'Szukaj wg projektów:', + 'Search by column: ' => 'Szukaj wg kolumn:', + 'Search by assignee: ' => 'Szukaj wg użytkownika:', + 'Search by color: ' => 'Szukaj wg koloru:', + 'Search by category: ' => 'Szukaj wg kategorii:', + 'Search by description: ' => 'Szukaj wg opisu:', + 'Search by due date: ' => 'Szukaj wg terminu:', + 'Average time spent in each column' => 'Średni czas spędzony w każdej z kolumn', + 'Average time spent' => 'Średni spędzony czas', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Niniejszy wykres pokazuje średni czas spędzony w każdej z kolumn dla ostatnich %d zadań.', + 'Average Lead and Cycle time' => 'Średni czas cyklu i realizacji', + 'Average lead time: ' => 'Średni czas realizacji:', + 'Average cycle time: ' => 'Średni czas cyklu:', + 'Cycle Time' => 'Czas cyklu', + 'Lead Time' => 'Czas realizacji', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Niniejszy wykres pokazuje średni czas cyklu i realizacji dla ostatnich %d zadań na przestrzeni czasu.', + 'Average time into each column' => 'Średni czas dla każdej kolumny', + 'Lead and cycle time' => 'Czas cyklu i realizacji', + 'Lead time: ' => 'Czas realizacji:', + 'Cycle time: ' => 'Czas cyklu:', + 'Time spent in each column' => 'Czas spędzony przez zadanie w każdej z kolumn', + 'The lead time is the duration between the task creation and the completion.' => 'Czas realizacji pomiędzy utworzeniem a ukończeniem zadania.', + 'The cycle time is the duration between the start date and the completion.' => 'Czas cyklu pomiędzy datą rozpoczęcia a ukończeniem zadania.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Jeśli zadanie nie jest zamknięte, bieżący czas zostaje użyty zamiast daty ukończenia.', + 'Set the start date automatically' => 'Ustaw automatycznie datę rozpoczęcia', + 'Edit Authentication' => 'Edycja uwierzytelnienia', + 'Remote user' => 'Zdalny użytkownik', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Zdalni użykownicy nie przechowują swojego hasła w bazie danych Kanboard, przykłady: konta LDAP, Google and Github.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Jeśli zaznaczysz "Zablokuj możliwość logowania", dane podane przy logowaniu zostaną zignorowane.', + 'Default task color' => 'Domyślny kolor zadań', + 'This feature does not work with all browsers.' => 'Ta funkcja może nie działać z każdą przeglądarką.', + 'There is no destination project available.' => 'Żaden docelowy projekt nie jest aktualnie dostępny.', + 'Trigger automatically subtask time tracking' => 'Ustaw automatyczne śledzenie czasu dla pod-zadań', + 'Include closed tasks in the cumulative flow diagram' => 'Obejmuj zamknięte zadania w zbiorczym diagramie przepływu', + 'Current swimlane: %s' => 'Bieżący tor: %s', + 'Current column: %s' => 'Bieżąca kolumna: %s', + 'Current category: %s' => 'Bieżąca kategoria: %s', + 'no category' => 'brak kategorii', + 'Current assignee: %s' => 'Aktualnie odpowiedzialna osoba: %s', + 'not assigned' => 'Brak osoby odpowiedzialnej', + 'Author:' => 'Autor', + 'contributors' => 'współautorzy', + 'License:' => 'Licencja:', + 'License' => 'Licencja', + 'Enter the text below' => 'Wpisz tekst poniżej', + 'Start date:' => 'Data rozpoczęcia:', + 'Due date:' => 'Termin', + 'People who are project managers' => 'Użytkownicy będący menedżerami projektu', + 'People who are project members' => 'Użytkownicy będący uczestnikami projektu', + 'NOK - Norwegian Krone' => 'NOK - Korona norweska', + 'Show this column' => 'Pokaż tą kolumnę', + 'Hide this column' => 'Ukryj tą kolumnę', + 'End date' => 'Data zakończenia', + 'Users overview' => 'Przegląd użytkowników', + 'Members' => 'Członkowie', + 'Shared project' => 'Projekt udostępniony', + 'Project managers' => 'Menedżerowie projektu', + 'Projects list' => 'Lista projektów', + 'End date:' => 'Data zakończenia:', + 'Change task color when using a specific task link' => 'Zmień kolor zadania używając specjalnego adresu URL', + 'Task link creation or modification' => 'Adres URL do utworzenia zadania lub modyfikacji', + 'Milestone' => 'Kamień milowy', + 'Reset the search/filter box' => 'Zresetuj pole wyszukiwania/filtrowania', + 'Documentation' => 'Dokumentacja', + 'Author' => 'Autor', + 'Version' => 'Wersja', + 'Plugins' => 'Wtyczki', + 'There is no plugin loaded.' => 'Nie wykryto żadnych wtyczek.', + 'My notifications' => 'Powiadomienia', + 'Custom filters' => 'Dostosuj filtry', + 'Your custom filter has been created successfully.' => 'Niestandardowy filtr został utworzony.', + 'Unable to create your custom filter.' => 'Nie można utworzyć niestandardowego filtra.', + 'Custom filter removed successfully.' => 'Niestandardowy filtr został usunięty.', + 'Unable to remove this custom filter.' => 'Nie można usunąć niestandardowego filtra.', + 'Edit custom filter' => 'Edytuj niestandardowy filtr', + 'Your custom filter has been updated successfully.' => 'Niestandardowy filtr został zaktualizowany', + 'Unable to update custom filter.' => 'Nie można zaktualizować niestandardowego filtra.', + 'Web' => 'Sieć', + 'New attachment on task #%d: %s' => 'Nowy załącznik do zadania #%d: %s', + 'New comment on task #%d' => 'Nowy komentarz do zadania #%d', + 'Comment updated on task #%d' => 'Aktualizacja komentarza do zadania #%d', + 'New subtask on task #%d' => 'Nowe pod-zadanie dla zadania #%d', + 'Subtask updated on task #%d' => 'Aktualizacja pod-zadania w zadaniu #%d', + 'New task #%d: %s' => 'Nowe zadanie #%d: %s', + 'Task updated #%d' => 'Aktualizacja zadania #%d', + 'Task #%d closed' => 'Zamknięto zadanie #%d', + 'Task #%d opened' => 'Otwarto zadanie #%d', + 'Column changed for task #%d' => 'Zmieniono kolumnę zadania #%d', + 'New position for task #%d' => 'Ustalono nową pozycję zadania #%d', + 'Swimlane changed for task #%d' => 'Zmieniono tor dla zadania #%d', + 'Assignee changed on task #%d' => 'Zmieniono osobę odpowiedzialną dla zadania #%d', + '%d overdue tasks' => '%d zaległych zadań', + 'No notification.' => 'Brak nowych powiadomień.', + 'Mark all as read' => 'Oznacz wszystkie jako przeczytane', + 'Mark as read' => 'Oznacz jako przeczytane', + 'Total number of tasks in this column across all swimlanes' => 'Całkowita liczba zadań z tej kolumny z wszystkich torów', + 'Collapse swimlane' => 'Zwiń tor', + 'Expand swimlane' => 'Rozwiń tor', + 'Add a new filter' => 'Dodaj nowy filtr', + 'Share with all project members' => 'Udostępnij wszystkim uczestnikom projektu', + 'Shared' => 'Udostępnione', + 'Owner' => 'Właściciel', + 'Unread notifications' => 'Nieprzeczytane powiadomienia', + 'Notification methods:' => 'Metody powiadomień:', + 'Unable to read your file' => 'Nie można odczytać pliku', + '%d task(s) have been imported successfully.' => '%d zadań zostało zaimportowanych.', + 'Nothing has been imported!' => 'Nic nie zostało zaimportowane!', + 'Import users from CSV file' => 'Importuj użytkowników z pliku CSV', + '%d user(s) have been imported successfully.' => '%d użytkowników zostało zaimportowanych.', + 'Comma' => 'Przecinek', + 'Semi-colon' => 'Średnik', + 'Tab' => 'Tabulacja', + 'Vertical bar' => 'Kreska pionowa', + 'Double Quote' => 'Cudzysłów', + 'Single Quote' => 'Apostrof', + '%s attached a file to the task #%d' => '%s dołączył(a) plik do zadania #%d', + 'There is no column or swimlane activated in your project!' => 'Żaden tor badź kolumna nie została aktywowana!', + 'Append filter (instead of replacement)' => 'Dołączaj filtr do zastosowanego filtru(zamiast przełączać)', + 'Append/Replace' => 'Dołącz/Zastąp', + 'Append' => 'Dołącz', + 'Replace' => 'Zastąp', + 'Import' => 'Importuj', + 'Change sorting' => 'odwróć sortowanie', + 'Tasks Importation' => 'Import zadań', + 'Delimiter' => 'Separator pola', + 'Enclosure' => 'Separator tekstu', + 'CSV File' => 'Plik CSV', + 'Instructions' => 'Instrukcje', + 'Your file must use the predefined CSV format' => 'Twój plik musi być zgodny z predefiniowanym formatem CSV (pobierz szablon)', + 'Your file must be encoded in UTF-8' => 'Twój plik musi być kodowany w UTF-8', + 'The first row must be the header' => 'Pierwszy wiersz pliku musi definiować nagłówki', + 'Duplicates are not verified for you' => 'Duplikaty nie będą weryfikowane', + 'The due date must use the ISO format: YYYY-MM-DD' => 'Data musi być w formacie ISO: YYYY-MM-DD', + 'Download CSV template' => 'Pobierz szablon pliku CSV', + 'No external integration registered.' => 'Żadna zewnętrzna integracja nie została zarejestrowana.', + 'Duplicates are not imported' => 'Duplikaty nie zostaną zaimportowane', + 'Usernames must be lowercase and unique' => 'Nazwy użytkowników muszą być unikalne i składać się z małych liter', + 'Passwords will be encrypted if present' => 'Hasła zostaną zaszyfrowane jeśli występują', + '%s attached a new file to the task %s' => '%s załączył nowy plik do zadania %s', + 'Link type' => 'Rodzaj link\'u', + 'Assign automatically a category based on a link' => 'Przypisz kategorię automatycznie na podstawie linku', + 'BAM - Konvertible Mark' => 'BAM - Bośnia i Hercegowina Cabrio Marka', + 'Assignee Username' => 'Przypisz nazwę użytkownika', + 'Assignee Name' => 'Przypisz imię', + 'Groups' => 'Grupy', + 'Members of %s' => 'Członkowie %s', + 'New group' => 'Nowa grupa', + 'Group created successfully.' => 'Grupa została utworzona.', + 'Unable to create your group.' => 'Nie można utworzyć grupy.', + 'Edit group' => 'Edytuj grupę', + 'Group updated successfully.' => 'Grupa została zaaktualizowana.', + 'Unable to update your group.' => 'Nie można zaaktualizować grupy.', + 'Add group member to "%s"' => 'Dodaj członka do grupy "%s"', + 'Group member added successfully.' => 'Użytkownik został dodany do grupy.', + 'Unable to add group member.' => 'Nie można dodać użytkownika do grupy.', + 'Remove user from group "%s"' => 'Usuń użytkownika z grupy "%s"', + 'User removed successfully from this group.' => 'Użytkownik został usunięty z grupy.', + 'Unable to remove this user from the group.' => 'Nie można usunąć użytkownika z grupy.', + 'Remove group' => 'Usuń grupę', + 'Group removed successfully.' => 'Grupa została usunięta.', + 'Unable to remove this group.' => 'Nie można usunąć grupy.', + 'Project Permissions' => 'Prawa dostępowe projektu', + 'Manager' => 'Menedżer', + 'Project Manager' => 'Menedżer projektu', + 'Project Member' => 'Uczestnik projektu', + 'Project Viewer' => 'Obserwator projektu', + 'Your account is locked for %d minutes' => 'Twoje konto zostało zablokowane na %d minut', + 'Invalid captcha' => 'Błędny kod z obrazka (captcha)', + 'The name must be unique' => 'Nazwa musi być unikatowa', + 'View all groups' => 'Wyświetl wszystkie grupy', + 'There is no user available.' => 'Żaden użytkownik nie jest dostępny.', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Czy na pewno chcesz usunąć użytkownika "%s" z grupy "%s"?', + 'There is no group.' => 'Nie utworzono jeszcze żadnej grupy.', + 'Add group member' => 'Dodaj członka grupy', + 'Do you really want to remove this group: "%s"?' => 'Czy na pewno chcesz usunąć grupę "%s"?', + 'There is no user in this group.' => 'Wybrana grupa nie posiada członków.', + 'Permissions' => 'Prawa dostępu', + 'Allowed Users' => 'Użytkownicy, którzy mają dostęp', + 'No specific user has been allowed.' => 'Żaden użytkownik nie ma przyznanego dostępu.', + 'Role' => 'Rola', + 'Enter user name...' => 'Wprowadź nazwę użytkownika...', + 'Allowed Groups' => 'Grupy, które mają dostęp', + 'No group has been allowed.' => 'Żadna grupa nie ma przyznanego dostępu.', + 'Group' => 'Grupa', + 'Group Name' => 'Nazwa grupy', + 'Enter group name...' => 'Wprowadź nazwę grupy...', + 'Role:' => 'Rola:', + 'Project members' => 'Uczestnicy projektu', + '%s mentioned you in the task #%d' => '%s wspomiał o Tobie w zadaniu #%d', + '%s mentioned you in a comment on the task #%d' => '%s wspomiał o Tobie w komentarzu do zadania #%d', + 'You were mentioned in the task #%d' => 'Wspomiano o Tobie w zadaniu #%d', + 'You were mentioned in a comment on the task #%d' => 'Wspomiano o Tobie w komentarzu do zadania #%d', + 'Estimated hours: ' => 'Szacowane godziny: ', + 'Actual hours: ' => 'Rzeczywiste godziny: ', + 'Hours Spent' => 'Spędzone godziny', + 'Hours Estimated' => 'Szacowane godziny', + 'Estimated Time' => 'Szacowany czas', + 'Actual Time' => 'Rzeczywisty czas', + 'Estimated vs actual time' => 'Szacowany vs rzeczywisty czas', + 'RUB - Russian Ruble' => 'RUB - Rosyjskie Ruble', + 'Assign the task to the person who does the action when the column is changed' => 'Przypisz zadanie do osoby wykonującej akcję gdy kolumna zostanie zmodyfikowana', + 'Close a task in a specific column' => 'Zamknij zadanie w określonej kolumnie', + 'Time-based One-time Password Algorithm' => 'Algorytm hasła jednorazowego bazującego na czasie', + 'Two-Factor Provider: ' => 'Dostawca: ', + 'Disable two-factor authentication' => 'Wyłącz dwustopniowe uwierzytelnianie', + 'Enable two-factor authentication' => 'Włącz dwustopniowe uwierzytelnianie', + 'There is no integration registered at the moment.' => 'W chwili obecnej nie ma zarejestrowanej żadnej integracji.', + 'Password Reset for Kanboard' => 'Resetuj hasło do Kanboarda', + 'Forgot password?' => 'Nie pamiętasz hasła?', + 'Enable "Forget Password"' => 'Włącz "Nie pamiętasz hasła?"', + 'Password Reset' => 'Resetuj hasło', + 'New password' => 'Nowe hasło', + 'Change Password' => 'Zmień hasło', + 'To reset your password click on this link:' => 'Kliknij w poniższy link, aby zresetować hasło:', + 'Last Password Reset' => 'Ostatnie odzyskiwanie hasła', + 'The password has never been reinitialized.' => 'Hasło nigdy nie było odzyskiwane.', + 'Creation' => 'Utworzenie', + 'Expiration' => 'Wygaśnięcie', + 'Password reset history' => 'Historia resetowania hasła', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Wszystkie zadania z kolumny "%s" i toru "%s" zostały zamknięte.', + 'Do you really want to close all tasks of this column?' => 'Na pewno chcesz zamknąć wszystkie zadania z tej kolumny?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d zadania z kolumny "%s" i toru "%s" zostaną zamknięte.', + 'Close all tasks in this column and this swimlane' => 'Zamknij wszystkie zadania w tej kolumnie', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Wtyczki obsługujące dodatkowe powiadomienia nie zostały zainstalowane. Dalej jednak możesz korzystać z standardowych powiadomień (sprawdź w ustawieniach Twojego profilu).', + 'My dashboard' => 'Mój dashboard', + 'My profile' => 'Mój profil', + 'Project owner: ' => 'Właściciel projektu: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'Identyfikator projektu jest opcjonalny i musi być alfanumeryczny, przykład: MYPROJECT.', + 'Project owner' => 'Właściciel projektu', + 'Personal projects do not have users and groups management.' => 'Projekty prywatne nie wspierają obsługi użytkowników i grup.', + 'There is no project member.' => 'Projekt nie ma uczestników.', + 'Priority' => 'Priorytet', + 'Task priority' => 'Priorytety zadań', + 'General' => 'Ogólne', + 'Dates' => 'Czas życia projektu', + 'Default priority' => 'Domyślny priorytet', + 'Lowest priority' => 'Najniższy priorytet', + 'Highest priority' => 'Najwyższy priorytet', + 'Close a task when there is no activity' => 'Zamknij zadanie gdy nie jest aktywne', + 'Duration in days' => 'Czas trwania w dniach', + 'Send email when there is no activity on a task' => 'Wyślij email gdy zadanie nie jest aktywne', + 'Unable to fetch link information.' => 'Nie można pobrać informacji o połączeniach.', + 'Daily background job for tasks' => 'Codzienne zadanie w tle', + 'Auto' => 'Automatyczny', + 'Related' => 'Powiązanie', + 'Attachment' => 'Załącznik', + 'Web Link' => 'Link URL', + 'External links' => 'Linki zewnętrzne', + 'Add external link' => 'Dodaj link zewnętrzny', + 'Type' => 'Typ', + 'Dependency' => 'Zależność', + 'Add internal link' => 'Dodaj link do innego zadania', + 'Add a new external link' => 'Dodaj nowy link zewnętrzny', + 'Edit external link' => 'Edytuj link zewnętrzny', + 'External link' => 'Link zewnętrzny', + 'Copy and paste your link here...' => 'Skopiuj i wklej link tutaj ...', + 'URL' => 'URL', + 'Internal links' => 'Linki do innych zadań', + 'Assign to me' => 'Przypisz do mnie', + 'Me' => 'Ja', + 'Do not duplicate anything' => 'Nie kopiuj żadnego projektu', + 'Projects management' => 'Zarządzanie projektami', + 'Users management' => 'Zarządzanie użytkownikami', + 'Groups management' => 'Zarządzanie grupami', + 'Create from another project' => 'Utwórz na podstawie innego projektu', + 'open' => 'otwarty', + 'closed' => 'zamknięty', + 'Priority:' => 'Priorytet:', + 'Reference:' => 'Odnośnik:', + 'Complexity:' => 'Złożoność:', + 'Swimlane:' => 'Proces:', + 'Column:' => 'Kolumna:', + 'Position:' => 'Pozycja:', + 'Creator:' => 'Utworzył:', + 'Time estimated:' => 'Szacowany czas:', + '%s hours' => '%s godzin', + 'Time spent:' => 'Spędzony czas:', + 'Created:' => 'Utworzone:', + 'Modified:' => 'Zmodyfikowane:', + 'Completed:' => 'Ukończone:', + 'Started:' => 'Rozpoczęte:', + 'Moved:' => 'Przeniesione:', + 'Task #%d' => 'Zadanie #%d', + 'Time format' => 'Format czasu', + 'Start date: ' => 'Data rozpoczęcia: ', + 'End date: ' => 'Data zakończenia: ', + 'New due date: ' => 'Nowy termin: ', + 'Start date changed: ' => 'Data rozpoczęcia została zmieniona: ', + 'Disable personal projects' => 'Wyłącz prywatne projekty', + 'Do you really want to remove this custom filter: "%s"?' => 'Na pewno usunąć niestandardowy filtr: "%s"?', + 'Remove a custom filter' => 'Usuń niestandardowy filtr', + 'User activated successfully.' => 'Użytkownik został aktywowany.', + 'Unable to enable this user.' => 'Nie można włączyć użytkownika.', + 'User disabled successfully.' => 'Użytkownik został wyłączony.', + 'Unable to disable this user.' => 'Nie można wyłączyć użytkownika.', + 'All files have been uploaded successfully.' => 'Wszystkie pliki zostały pomyślnie przesłane.', + 'The maximum allowed file size is %sB.' => 'Maksymalny rozmiar pliku to %sB.', + 'Drag and drop your files here' => 'Przeciągnij i upuść pliki tutaj', + 'choose files' => 'wybierz pliki', + 'View profile' => 'Zobacz profil', + 'Two Factor' => 'Dwustopniowe', + 'Disable user' => 'Wyłącz użytkownika', + 'Do you really want to disable this user: "%s"?' => 'Na pewno wyłączyć użytkownika: "%s"?', + 'Enable user' => 'Włącz użytkownika', + 'Do you really want to enable this user: "%s"?' => 'Na pewno włączyć użytkownika: "%s"?', + 'Download' => 'Pobierz', + 'Uploaded: %s' => 'Data załączenia: %s', + 'Size: %s' => 'Rozmiar: %s', + 'Uploaded by %s' => 'Załadowany przez %s', + 'Filename' => 'Nazwa pliku', + 'Size' => 'Rozmiar', + 'Column created successfully.' => 'Utworzono kolumnę.', + 'Another column with the same name exists in the project' => 'Inna kolumna o tej samej nazwie już istnieje w projekcie', + 'Default filters' => 'Domyślne filtry', + 'Your board doesn\'t have any columns!' => 'Twoja tablica nie ma żadnej kolumny!', + 'Change column position' => 'Zmień pozycję kolumny', + 'Switch to the project overview' => 'Przełącz do podsumowania projektu', + 'User filters' => 'Filtry użytkownika', + 'Category filters' => 'Filtry kategorii', + 'Upload a file' => 'Prześlij plik', + 'View file' => 'Wyświetl plik', + 'Last activity' => 'Ostatnia aktywność', + 'Change subtask position' => 'Zmień pozycję pod-zadania', + 'This value must be greater than %d' => 'Wartość musi być większa niż %d', + 'Another swimlane with the same name exists in the project' => 'Inny tor o tej samej nazwie już istnieje w projekcie', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Przykład: https://example.kanboard.org/ (użyty do wygenerowania bezwzględnych adresów URL)', + 'Actions duplicated successfully.' => 'Pomyślnie zduplikowano akcje.', + 'Unable to duplicate actions.' => 'Nie można zduplikować akcji.', + 'Add a new action' => 'Dodaj nową akcję', + 'Import from another project' => 'Importuj z innego projektu', + 'There is no action at the moment.' => 'W chwili obecnej nie dodano żadnych akcji.', + 'Import actions from another project' => 'Importuj akcje z innego projektu', + 'There is no available project.' => 'Brak dostępnego projektu.', + 'Local File' => 'Plik lokalny', + 'Configuration' => 'Konfiguracja', + 'PHP version:' => 'Wersja PHP:', + 'PHP SAPI:' => 'API serwera PHP:', + 'OS version:' => 'Wersja OS:', + 'Database version:' => 'Wersja bazy danych:', + 'Browser:' => 'Przeglądarka', + 'Task view' => 'Widok zadań', + 'Edit task' => 'Edytuj zadanie', + 'Edit description' => 'Edytuj opis', + 'New internal link' => 'Nowy wewnętrzny link', + 'Display list of keyboard shortcuts' => 'Wyświetl skróty klawiszowe', + 'Avatar' => 'Avatar', + 'Upload my avatar image' => 'Prześlij avatar', + 'Remove my image' => 'Usuń', + 'The OAuth2 state parameter is invalid' => 'Parametr stanu OAuth2 jest niepoprawny', + 'User not found.' => 'Nie znaleziono użytkownika', + 'Search in activity stream' => 'Szukaj w strumieniu aktywności', + 'My activities' => 'Moje aktywności', + 'Activity until yesterday' => 'Aktywności do wczoraj', + 'Activity until today' => 'Aktywności do dzisiaj', + 'Search by creator: ' => 'Wyszukaj według twórcy: ', + 'Search by creation date: ' => 'Wyszukaj według daty utworzenia: ', + 'Search by task status: ' => 'Wyszukaj według statusu zadania: ', + 'Search by task title: ' => 'Wyszukaj po tytule zadania: ', + 'Activity stream search' => 'Wyszukaj w strumieniu aktywności', + 'Projects where "%s" is manager' => 'Projekty gdzie "%s" jest menedżerem', + 'Projects where "%s" is member' => 'Projekty gdzie "%s" jest uczestnikiem', + 'Open tasks assigned to "%s"' => 'Otwarte zadania przypisane do "%s"', + 'Closed tasks assigned to "%s"' => 'Zamknięte zadania przypisane do "%s"', + 'Assign automatically a color based on a priority' => 'Przypisz kolor automatycznie, w zależności od priorytetu', + 'Overdue tasks for the project(s) "%s"' => 'Zaległe zadania dla projektu/projektów "%s"', + 'Upload files' => 'Wgraj pliki', + 'Installed Plugins' => 'Zainstalowane wtyczki', + 'Plugin Directory' => 'Folder z wtyczkami', + 'Plugin installed successfully.' => 'Wtyczka zainstalowana poprawnie.', + 'Plugin updated successfully.' => 'Wtyczka wgrana poprawnie.', + 'Plugin removed successfully.' => 'Wtyczka usunięta poprawnie.', + 'Subtask converted to task successfully.' => 'Zadanie podrzędne skonwertowane poprawnie do zadania.', + 'Unable to convert the subtask.' => 'Skonwertowanie do zadania podrzędnego niemożliwe.', + 'Unable to extract plugin archive.' => 'Rozpakowanie archiwum wtyczki niemożliwe.', + 'Plugin not found.' => 'Wtyczka nie znaleziona.', + 'You don\'t have the permission to remove this plugin.' => 'Nie masz uprawnień do usunięcia wtyczki.', + 'Unable to download plugin archive.' => 'Pobranie wtyczki niemożliwe.', + 'Unable to write temporary file for plugin.' => 'Zapisanie plików tymczasowych dla wtyczki niemożliwe.', + 'Unable to open plugin archive.' => 'Rozpakowanie archiwum wtyczki niemożliwe.', + 'There is no file in the plugin archive.' => 'Brak plików w archiwum wtyczki.', + 'Create tasks in bulk' => 'Utwórz zadania zbiorczo', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Ta instancja Kanboarda nie jest sknfigurowana do instalowania wtyczek z poziomu interfejsu użytkownika.', + 'There is no plugin available.' => 'Brak dostępnej wtyczki.', + 'Install' => 'Instaluj', + 'Update' => 'Aktualizuj', + 'Up to date' => 'Aktualny', + 'Not available' => 'Niedostępny', + 'Remove plugin' => 'Usuń wtyczkę', + 'Do you really want to remove this plugin: "%s"?' => 'Na pewno chcesz usunąć tę wtyczkę: "%s"?', + 'Uninstall' => 'Usuń', + 'Listing' => 'Lista', + 'Metadata' => 'Metadane', + 'Manage projects' => 'Zarządzaj projektami', + 'Convert to task' => 'Konwertuj do zadania', + 'Convert sub-task to task' => 'Konwertuj zadanie podrzędne do zadania', + 'Do you really want to convert this sub-task to a task?' => 'Na pewno chcesz skonwertować zadanie podrzędne do zadania?', + 'My task title' => 'Tytuł mojego zadania', + 'Enter one task by line.' => 'Wprowadź każde zadanie w osobnej linii.', + 'Number of failed login:' => 'Ilość nieudanych logowań:', + 'Account locked until:' => 'Konto zablokowane do:', + 'Email settings' => 'Ustawienia e-maila', + 'Email sender address' => 'Adres e-mail nadawcy', + 'Email transport' => 'Protokół transportowy e-mail', + 'Webhook token' => 'Token', + 'Project tags management' => 'Zarządzanie tagami w projekcie', + 'Tag created successfully.' => 'Tag utworzony poprawnie.', + 'Unable to create this tag.' => 'Utworzenie taga niemożliwe.', + 'Tag updated successfully.' => 'Tag zaktualizowany poprawnie', + 'Unable to update this tag.' => 'Aktualizacja taga niemożliwa.', + 'Tag removed successfully.' => 'Tag usunięty poprawnie.', + 'Unable to remove this tag.' => 'Usunięcie taga niemożliwe.', + 'Global tags management' => 'Globalne zarządzanie tagami', + 'Tags' => 'Tagi', + 'Tags management' => 'Zarządzanie tagami', + 'Add new tag' => 'Utwórz taga', + 'Edit a tag' => 'Edytuj taga', + 'Project tags' => 'Tagi w projekcie', + 'There is no specific tag for this project at the moment.' => 'Brak wskazanego taga w projekcie.', + 'Tag' => 'Tag', + 'Remove a tag' => 'Usuń taga', + 'Do you really want to remove this tag: "%s"?' => 'Na pewno chcesz usunąć taga: "%s"?', + 'Global tags' => 'Globalne tagi', + 'There is no global tag at the moment.' => 'Brak globalnych tagów.', + 'This field cannot be empty' => 'To pole nie może być puste', + 'Close a task when there is no activity in a specific column' => 'Zamknij zadanie, jeżeli nie ma aktywności we wskazanej kolumnie', + '%s removed a subtask for the task #%d' => '%s usunął zadanie podrzędne do zadania nr %d', + '%s removed a comment on the task #%d' => '%s usunął komentarz do zadania nr %d', + 'Comment removed on task #%d' => 'Komentarz do zadania nr %d usunięty', + 'Subtask removed on task #%d' => 'Zadanie podrzędne do zadania nr %d usunięte', + 'Hide tasks in this column in the dashboard' => 'Ukryj zadania dla wskazanej kolumny na tablicy', + '%s removed a comment on the task %s' => '%s usunął komentarz do zadania %s', + '%s removed a subtask for the task %s' => '%s usunął zadanie podrzędne do zadania %s', + 'Comment removed' => 'Komentarz usunięty', + 'Subtask removed' => 'Zadanie podrzędne usunięte', + '%s set a new internal link for the task #%d' => '%s dodał odnośnik do zadania nr %d', + '%s removed an internal link for the task #%d' => '%s usunął odnośnik do zadania nr %d', + 'A new internal link for the task #%d has been defined' => 'Nowy odnośnik do zadania nr %d został zdefiniowany', + 'Internal link removed for the task #%d' => 'Odnośnik do zadania nr %d został usunięty', + '%s set a new internal link for the task %s' => '%s dodał odnośnik do zadania %s', + '%s removed an internal link for the task %s' => '%s usunął odnośnik do zadania %s', + 'Automatically set the due date on task creation' => 'Automatycznie ustaw datę zakończenia przy tworzeniu zadania', + 'Move the task to another column when closed' => 'Przy zamknięciu zadania przenieś je do innej kolumny', + 'Move the task to another column when not moved during a given period' => 'Jeżeli zadanie nie było przenoszone przez określony interwał, to przenieś je do innej kolumny', + 'Dashboard for %s' => 'Tablica dla %s', + 'Tasks overview for %s' => 'Podgląd zadań dla %s', + 'Subtasks overview for %s' => 'Podgląd zadań podrzędnych dla %s', + 'Projects overview for %s' => 'Podgląd projektów dla %s', + 'Activity stream for %s' => 'Strumień aktywności dla %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Ustaw kolor kiedy zadanie jest przenoszone do swimlane', + 'Assign a priority when the task is moved to a specific swimlane' => 'Ustaw priorytet kiedy zadanie jest przenoszone do swimlane', + 'User unlocked successfully.' => 'Użytkownik odblokowany poprawnie.', + 'Unable to unlock the user.' => 'Odblokowanie użytkownika niemożliwe.', + 'Move a task to another swimlane' => 'Przenieś zadanie do innego swimlane', + 'Creator Name' => 'Autor', + 'Time spent and estimated' => 'Czas spędzony i planowany', + 'Move position' => 'Przenieś', + 'Move task to another position on the board' => 'Przenieś zadanie w inne miejsce', + 'Insert before this task' => 'Wstaw przed tym zadaniem', + 'Insert after this task' => 'Wstaw za tym zadaniem', + 'Unlock this user' => 'Odblokuj użytkownika', + 'Custom Project Roles' => 'Role w projekcie', + 'Add a new custom role' => 'Dodaj rolę', + 'Restrictions for the role "%s"' => 'Ograniczenia dla roli "%s"', + 'Add a new project restriction' => 'Dodaj ograniczenie w projekcie', + 'Add a new drag and drop restriction' => 'Dodaj ograniczenie drag & drop', + 'Add a new column restriction' => 'Dodaj ograniczenie kolumny', + 'Edit this role' => 'Edytuj rolę', + 'Remove this role' => 'Usuń rolę', + 'There is no restriction for this role.' => 'Brak ograniczeń dla roli', + 'Only moving task between those columns is permitted' => 'Jedynie przenoszenie zadań pomiędzy kolumnami jest dozwolone', + 'Close a task in a specific column when not moved during a given period' => 'Zamknij zadanie w kolumnie, jeżeli nie było przeniesione przez określony czas', + 'Edit columns' => 'Edytuj kolumny', + 'The column restriction has been created successfully.' => 'Ograniczenie dla kolumny zostało utworzone poprawnie.', + 'Unable to create this column restriction.' => 'Utworzenie ograniczenia dla kolumny niemożliwe.', + 'Column restriction removed successfully.' => 'Ograniczenie dla kolumny zostało usunięte poprawnie.', + 'Unable to remove this restriction.' => 'Usunięcie ograniczenia niemożliwe.', + 'Your custom project role has been created successfully.' => 'Rola w projekcie została utworzona.', + 'Unable to create custom project role.' => 'Utworzenie roli w projekcie niemożliwe.', + 'Your custom project role has been updated successfully.' => 'Rola w projekcie została zaktualizowana.', + 'Unable to update custom project role.' => 'Aktualizacja roli w projekcie niemożliwa.', + 'Custom project role removed successfully.' => 'Rola w projekcie została usunięta.', + 'Unable to remove this project role.' => 'Usunięcie roli w projekcie niemożliwe.', + 'The project restriction has been created successfully.' => 'Ograniczenie w projekcie zostało utworzone.', + 'Unable to create this project restriction.' => 'Usunięcie ograniczenia w projekcie niemożliwe.', + 'Project restriction removed successfully.' => 'Organiczenie w projekcie zostało usunięte.', + 'You cannot create tasks in this column.' => 'Nie możesz utworzyć zadania w tej kolumnie.', + 'Task creation is permitted for this column' => 'Dodawanie zadania w tej kolumnie jest niedozwolone', + 'Closing or opening a task is permitted for this column' => 'Otwieranie lub zamykanie zadania w tej kolumnie jest dozwolone', + 'Task creation is blocked for this column' => 'Tworzenie zadania w tej kolumnie jest zablokowane', + 'Closing or opening a task is blocked for this column' => 'Otwieranie lub zamykanie zadania w tej kolumnie jest zablokowane', + 'Task creation is not permitted' => 'Tworzenie zadania jest niedozwolone', + 'Closing or opening a task is not permitted' => 'Otwierania lub zamykanie zadania jest niedozwolone', + 'New drag and drop restriction for the role "%s"' => 'Now ograniczenie drag & drop dla roli "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Osoby przypisane do tej roli będą mogły przemieszczać zadania jedynie pomiędzy kolumną źródłową i docelową.', + 'Remove a column restriction' => 'Usuń ograniczenie dla kolumny', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Na pewno chcesz usunąć organiczenie "%s", dla kolumny "%s"?', + 'New column restriction for the role "%s"' => 'Nowe organiczenie dla kolumny, dla roli "%s"', + 'Rule' => 'Reguła', + 'Do you really want to remove this column restriction?' => 'Na pewno chcesz usunąć organiczenie dla kolumny?', + 'Custom roles' => 'Własne role', + 'New custom project role' => 'Nowa rola w projekcie', + 'Edit custom project role' => 'Edytuj rolę w projekcie', + 'Remove a custom role' => 'Usuń rolę', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Na pewno chcesz usunąć rolę: "%s"? Wszyscy użytkownicy przypisani do tej roli zostaną członkami projektu.', + 'There is no custom role for this project.' => 'Brak wskazanej roli w projekcie.', + 'New project restriction for the role "%s"' => 'Nowe ograniczenie w projekcie dla roli "%s"', + 'Restriction' => 'Organiczenie', + 'Remove a project restriction' => 'Usuń organiczenie projektu', + 'Do you really want to remove this project restriction: "%s"?' => 'Na pewno chcesz usunąć organiczenie projektu: "%s"?', + 'Duplicate to multiple projects' => 'Zduplikuj do innych projektów', + 'This field is required' => 'To pole jest wymagane', + 'Moving a task is not permitted' => 'Przenoszenie zadań jest niedozwolone', + 'This value must be in the range %d to %d' => 'Ta wartosć musi być w zakresie od %d do %d', + 'You are not allowed to move this task.' => 'Nie masz uprawnień do przeniesienia zadania.', + 'API User Access' => 'Dostęp użytkownika po API', + 'Preview' => 'Podgląd', + 'Write' => 'Zapisz', + 'Write your text in Markdown' => 'Zapisz tekst jako znaczniki Markdown', + 'No personal API access token registered.' => 'Brak zarejestrowanego tokenu osobistego API.', + 'Your personal API access token is "%s"' => 'Osobisty token API to "%s"', + 'Remove your token' => 'Usuń token', + 'Generate a new token' => 'Wygeneruj token', + 'Showing %d-%d of %d' => 'Pokazuję %d-%d z %d', + 'Outgoing Emails' => 'Wychodzące e-maile', + 'Add or change currency rate' => 'Dodaj kursy wymiany walut', + 'Reference currency: %s' => 'Waluta referencyjna: %s', + 'Add custom filters' => 'Dodaj filtr', + 'Export' => 'Eksport', + 'Add link label' => 'Dodaj etykietę linku', + 'Incompatible Plugins' => 'Niekompatybilne wtyczki', + 'Compatibility' => 'Kompatybilność', + 'Permissions and ownership' => 'Zezwolenia i własności', + 'Priorities' => 'Priorytety', + 'Close this window' => 'Zamknij okno', + 'Unable to upload this file.' => 'Wgranie pliku niemożliwe.', + 'Import tasks' => 'Importuj zadania', + 'Choose a project' => 'Wybierz projekt', + 'Profile' => 'Profil', + 'Application role' => 'Rola w aplikacji', + '%d invitations were sent.' => '%d zaproszeń zostało wysłanych.', + '%d invitation was sent.' => '%d zaproszenie zostało wysłane.', + 'Unable to create this user.' => 'Utworzenie użytkownika niemożliwe.', + 'Kanboard Invitation' => 'Zaproszenie do Kanboarda', + 'Visible on dashboard' => 'Widoczny na tablicy', + 'Created at:' => 'Utworzono:', + 'Updated at:' => 'Zaktualizowano:', + 'There is no custom filter.' => 'Brak filtra.', + 'New User' => 'Nowy użytkownik', + 'Authentication' => 'AUtentykacja', + 'If checked, this user will use a third-party system for authentication.' => 'Użytkownik będzie używał zewnętrznego systemu dla autentykacji.', + 'The password is necessary only for local users.' => 'Hasło jest wymagane jedynie dla lokalnych użytkowników.', + 'You have been invited to register on Kanboard.' => 'Zostałeś zaproszony do rejestracji w Kanboardzie.', + 'Click here to join your team' => 'Kliknij, aby dołączyć do zespołu', + 'Invite people' => 'Zaproś osoby', + 'Emails' => 'E-maile', + 'Enter one email address by line.' => 'Wprowadź jeden adres e-mail w każdej linii.', + 'Add these people to this project' => 'Dodaj ludzi do projektu', + 'Add this person to this project' => 'Dodaj osobę do projektu', + 'Sign-up' => 'Zaloguj', + 'Credentials' => 'Dane logowania', + 'New user' => 'Nowy użytkownik', + 'This username is already taken' => 'Ta nazwa użytkownika jest już zajęta', + 'Your profile must have a valid email address.' => 'Profil musi mieć przypisany prawidłowy adres e-mail.', + 'TRL - Turkish Lira' => 'TRL - Lira turecka', + 'The project email is optional and could be used by several plugins.' => 'Adres e-mail projektu jest opcjonalny, ale może być używany przez niektóre wtyczki.', + 'The project email must be unique across all projects' => 'Adres e-mail projektu musi być unikalny pośród wszystkich projektów', + 'The email configuration has been disabled by the administrator.' => 'Konfiguracja e-maila została zablokowana przez administratora.', + 'Close this project' => 'Zamknij projekt', + 'Open this project' => 'Otwórz projekt', + 'Close a project' => 'Zamknij projekt', + 'Do you really want to close this project: "%s"?' => 'Na pewno chcesz zamknąć projekt: "%s"?', + 'Reopen a project' => 'Otwórz ponownie projekt', + 'Do you really want to reopen this project: "%s"?' => 'Na pewno chcesz otworzyć ponownie projekt: "%s"?', + 'This project is open' => 'Ten projekt jest otwarty', + 'This project is closed' => 'Ten projekt jest zamknięty', + 'Unable to upload files, check the permissions of your data folder.' => 'Wgranie plików niemożliwe, sprawdź uprawnienia katalogu z danymi.', + 'Another category with the same name exists in this project' => 'Istnieje już taka kategoria w tym projekcie', + 'Comment sent by email successfully.' => 'Komentarz został wysłany poprzez e-maila.', + 'Sent by email to "%s" (%s)' => 'Wysłano poprzez e-maila do "%s" (%s)', + 'Unable to read uploaded file.' => 'Odczytanie wgranego pliku niemożliwe.', + 'Database uploaded successfully.' => 'Baza danych wgrana poprawnie.', + 'Task sent by email successfully.' => 'Zadanie zostało wysłane poprzez e-maila.', + 'There is no category in this project.' => 'Brak kategorii w projekcie.', + 'Send by email' => 'Wysłano poprzez e-maila', + 'Create and send a comment by email' => 'Utwórz komentarz i wyślij poprze e-maila', + 'Subject' => 'Temat', + 'Upload the database' => 'Wgraj bazę danych', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Możesz wgrać poprzednio wyeksportowaną bazę danych sqlite (w formacie gzip).', + 'Database file' => 'Plik bazy danych', + 'Upload' => 'Wgraj', + 'Your project must have at least one active swimlane.' => 'Projekt musi mieć chociaż jednego aktywnego swimlane', + 'Project: %s' => 'Projekt: %s', + 'Automatic action not found: "%s"' => 'Automatyczna akcja nie znaleziona: "%s"', + '%d projects' => '%d projektów', + '%d project' => '%d projekt', + 'There is no project.' => 'Brak projektu.', + 'Sort' => 'Sortuj', + 'Project ID' => 'ID projektu', + 'Project name' => 'Nazwa projektu', + 'Public' => 'Publiczny', + 'Personal' => 'Prywatny', + '%d tasks' => '%d zadań', + '%d task' => '%d zadanie', + 'Task ID' => 'ID zadania', + 'Assign automatically a color when due date is expired' => 'Automatycznie przydziel kolor, jeżeli data zakończenia została przekroczona', + 'Total score in this column across all swimlanes' => 'Sumaryczna ilość punktów w kolumnie, poprzez wszystkie swimlane', + 'HRK - Kuna' => 'HRK - Kuna chorwacka', + 'ARS - Argentine Peso' => 'ARS - Peso argentyńskie', + 'COP - Colombian Peso' => 'COP - Peso kolumbijskie', + '%d groups' => '%d grup', + '%d group' => '%d grupa', + 'Group ID' => 'ID grupy', + 'External ID' => 'Zewnętrzne ID', + '%d users' => '%d użytkowników', + '%d user' => '%d użytkownik', + 'Hide subtasks' => 'Ukryj zadania podrzędne', + 'Show subtasks' => 'Pokaż zadania podrzędne', + 'Authentication Parameters' => 'Parametry autentykacji', + 'API Access' => 'Dostęp po API', + 'No users found.' => 'Nie znaleziono użytkowników.', + 'User ID' => 'ID użytkownika', + 'Notifications are activated' => 'Powiadomienia są aktywne', + 'Notifications are disabled' => 'Powiadomienia są nieaktywne', + 'User disabled' => 'Użytkownik zablokowany', + '%d notifications' => '%d powiadomień', + '%d notification' => '%d powiadomienie', + 'There is no external integration installed.' => 'Brak zainstalowanych zewnętrznych integracji', + 'You are not allowed to update tasks assigned to someone else.' => 'Nie masz uprawnień do uaktualnienia zadania przypisanego do innego użytkownika.', + 'You are not allowed to change the assignee.' => 'Nie masz uprawnień do zmiany użytkownika przypisanego do zadania.', + 'Task suppression is not permitted' => 'Zablokowanie zadania jest niedozwolone', + 'Changing assignee is not permitted' => 'Zmiana osoby przypisanej do zadania jest niedozwolona', + 'Update only assigned tasks is permitted' => 'Dozwolone jest jedynie uaktualnienie już przypisanego zadania', + 'Only for tasks assigned to the current user' => 'Tylko dla zadań przypisanych dla aktualnego użytkownika', + 'My projects' => 'Moje projekty', + 'You are not a member of any project.' => 'Nie jesteś członkiem żadnego projektu.', + 'My subtasks' => 'Moje zadania podrzędne', + '%d subtasks' => '%d zadań podrzędnych', + '%d subtask' => '%d zadanie podrzędne', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Przesuwanie zadań pomiędzy kolumnami jest dozwolone jedynie dla użytkownika przypisanego do tych zadań', + '[DUPLICATE]' => '[DUPLIKAT]', + 'DKK - Danish Krona' => 'DKK - Korona duńska', + 'Remove user from group' => 'Usuń użytkownika z grupy', + 'Assign the task to its creator' => 'Przypisz zadanie do autora', + 'This task was sent by email to "%s" with subject "%s".' => 'Zadanie zostało wysłane poprzez e-maila do "%s" z tematem "%s".', + 'Predefined Email Subjects' => 'Predefiniowane tematy e-maila', + 'Write one subject by line.' => 'Wpisz jeden temat na linię.', + 'Create another link' => 'Utwórz kolejnego linka', + 'BRL - Brazilian Real' => 'BRL - Real brazylijski', + 'Add a new Kanboard task' => 'Dodaj nowe zadanie Kanboard', + 'Subtask not started' => 'Zadanie podrzędne nie rozpoczęte', + 'Subtask currently in progress' => 'Zadanie podrzędne w trakcie', + 'Subtask completed' => 'Zadanie podrzędne zakończone', + 'Subtask added successfully.' => 'Zadanie podrzędne dodane', + '%d subtasks added successfully.' => '%d zadań podrzędnych dodanych prawidłowo', + 'Enter one subtask by line.' => 'Dodaj jedno zadanie podrzędne na linię.', + 'Predefined Contents' => 'Predefiniowana zawartość', + 'Predefined contents' => 'Predefiniowana zawartość', + 'Predefined Task Description' => 'Predefiniowany opis zadania', + 'Do you really want to remove this template? "%s"' => 'Na pewno chcesz usunąć szablon? "%s"', + 'Add predefined task description' => 'Dodaj predefiniowany opis zadania', + 'Predefined Task Descriptions' => 'Predefiniowany opis zadania', + 'Template created successfully.' => 'Szablon utworzony poprawnie.', + 'Unable to create this template.' => 'Utworzenie szablonu niemożliwe.', + 'Template updated successfully.' => 'Szablon uaktualniony poprawnie.', + 'Unable to update this template.' => 'Uaktualnienie szablonu niemożliwe.', + 'Template removed successfully.' => 'Szablon usunięty poprawnie.', + 'Unable to remove this template.' => 'Usunięcie szablonu niemożliwe.', + 'Template for the task description' => 'Szablon dla opisu zadania', + 'The start date is greater than the end date' => 'Data startu jest późniejsza niż data zakończenia', + 'Tags must be separated by a comma' => 'Tagi muszą być oddzielone przecinkiem', + 'Only the task title is required' => 'Wymagana jest jedynie nazwa zadania', + 'Creator Username' => 'Nazwa autora', + 'Color Name' => 'Kolor', + 'Column Name' => 'Kolumna', + 'Swimlane Name' => 'Swimlane', + 'Time Estimated' => 'Szacowany czas', + 'Time Spent' => 'Spędzony czas', + 'External Link' => 'Zewnętrzny odnośnik', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Ta opcja aktywuje kanał iCal, kanał RSS oraz publiczny widok tablicy.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Zatrzymaj odliczanie czasu we wszystkich zadaniach podrzędnych, kiedy przenosisz zadanie do innej kolumny', + 'Subtask Title' => 'Tytuł zadania podrzędnego', + 'Add a subtask and activate the timer when moving a task to another column' => 'Dodaj zadanie podrzędne i aktywuj odliczanie czasu, kiedy przenosisz zadanie do innej kolumny', + 'days' => 'dni', + 'minutes' => 'minuty', + 'seconds' => 'sekundy', + 'Assign automatically a color when preset start date is reached' => 'Ustaw kolor automatycznie, kiedy data rozpoczęcia zadania została osiągnięta', + 'Move the task to another column once a predefined start date is reached' => 'Przenieś zadanie do innej kolumny, kiedy data rozpoczęcia zadania została osiągnięta', + 'This task is now linked to the task %s with the relation "%s"' => 'Zadanie jest teraz podlinkowane do zadania %s w relacji "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'Odnośnik w relacji "%s" do zadania %s został usunięty', + 'Custom Filter:' => 'Filtr:', + 'Unable to find this group.' => 'Nie można znaleźć grupy.', + '%s moved the task #%d to the column "%s"' => '%s przeniósł zadanie nr %d do kolumny "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => '%s przeniósł zadanie nr %d w pozycę %d do kolumny "%s"', + '%s moved the task #%d to the swimlane "%s"' => '%s przeniósł zadanie nr %d do swimlane "%s"', + '%sh spent' => '%sh upłynęło', + '%sh estimated' => '%sh planowane', + 'Select All' => 'Zaznacz wszystko', + 'Unselect All' => 'Odznacz wszystko', + 'Apply action' => 'Zastosuj akcję', + 'Move selected tasks to another column or swimlane' => 'Przenieś wybrane zadania do innej kolumny', + 'Edit tasks in bulk' => 'Edytuj zadania grupowo', + 'Choose the properties that you would like to change for the selected tasks.' => 'Wybierz właściwość wskazanego zadania, które chciałbyś zmienić.', + 'Configure this project' => 'Skonfiguruj projekt', + 'Start now' => 'Wystartuj', + '%s removed a file from the task #%d' => '%s usunął plik z zadania nr %d', + 'Attachment removed from task #%d: %s' => 'Załącznik usunięty z zadania nr %d: %s', + 'No color' => 'Brak koloru', + 'Attachment removed "%s"' => 'Załącznik usunięty "%s"', + '%s removed a file from the task %s' => '%s usunął plik z zadania %s', + 'Move the task to another swimlane when assigned to a user' => 'Przenieś zadanie do innego swimlane, kiedy zostanie przydzielone do użytkownika', + 'Destination swimlane' => 'Docelowy swimlane', + 'Assign a category when the task is moved to a specific swimlane' => 'Przydziel kategorię, kiedy zadanie zostanie przeniesione do swimlane', + 'Move the task to another swimlane when the category is changed' => 'Przenieś zadanie do innego swimlane, kiedy kategoria zostanie zmieniona', + 'Reorder this column by priority (ASC)' => 'Sortuj kolumnę po priorytecie (rosnąco)', + 'Reorder this column by priority (DESC)' => 'Sortuj kolumnę po priorytecie (malejąco)', + 'Reorder this column by assignee and priority (ASC)' => 'Sortuj kolumnę po priorytecie i osobie przydzielonej do zadania (rosnąco)', + 'Reorder this column by assignee and priority (DESC)' => 'Sortuj kolumnę po priorytecie i osobie przydzielonej do zadania (malejąco)', + 'Reorder this column by assignee (A-Z)' => 'Sortuj kolumnę po osobie przydzielonej do zadania (rosnąco)', + 'Reorder this column by assignee (Z-A)' => 'Sortuj kolumnę po osobie przydzielonej do zadania (malejąco)', + 'Reorder this column by due date (ASC)' => 'Sortuj kolumnę po dacie zakończenia (rosnąco)', + 'Reorder this column by due date (DESC)' => 'Sortuj kolumnę po dacie zakończenia (malejąco)', + 'Reorder this column by id (ASC)' => 'Sortuj tę kolumnę według ID (rosnąco)', + 'Reorder this column by id (DESC)' => 'Sortuj tę kolumnę według ID (malejąco)', + '%s moved the task #%d "%s" to the project "%s"' => '%s przeniósł zadanie nr %d "%s" do projektu "%s"', + 'Task #%d "%s" has been moved to the project "%s"' => 'Zadanie nr %d "%s" zostało przeniesione do projektu "%s"', + 'Move the task to another column when the due date is less than a certain number of days' => 'Przenieś zadanie do innej kolumny, kiedy data zakończenia jest oddalona o określoną liczbę dni od aktualnej daty', + 'Automatically update the start date when the task is moved away from a specific column' => 'Automatycznie uaktualnij datę startu, kiedy zadanie jest przeniesione z danej kolumny', + 'HTTP Client:' => 'Klient HTTP:', + 'Assigned' => 'Przydzielony', + 'Task limits apply to each swimlane individually' => 'Limity zadań odnoszą się do każdego toru indywidualnie', + 'Column task limits apply to each swimlane individually' => 'Limity zadań dla kolumn dotyczą każdego toru indywidualnie', + 'Column task limits are applied to each swimlane individually' => 'Kolumnowe limity zadań są stosowane do każdego toru indywidualnie', + 'Column task limits are applied across swimlanes' => 'Limity zadań dla kolumn są stosowane na wszystkich torów', + 'Task limit: ' => 'Limit zadań: ', + 'Change to global tag' => 'Zmień na tag globalny', + 'Do you really want to make the tag "%s" global?' => 'Czy naprawdę chcesz, aby tag "%s" był globalny?', + 'Enable global tags for this project' => 'Włącz globalne tagi dla tego projektu', + 'Group membership(s):' => 'Członkostwo w grupie (grupach):', + '%s is a member of the following group(s): %s' => '%s jest członkiem następującej(ych) grupy(grup): %s', + '%d/%d group(s) shown' => '%d/%d wykazanych grup(y)', + 'Subtask creation or modification' => 'Tworzenie lub modyfikacja podzadania', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Przypisz zadanie do konkretnego użytkownika, gdy zadanie zostanie przeniesione na konkretny tor', + 'Comment' => 'Komentarz', + 'Collapse vertically' => 'Zwiń w pionie', + 'Expand vertically' => 'Rozwiń w pionie', + 'MXN - Mexican Peso' => 'MXN - peso meksykańskie', + 'Estimated vs actual time per column' => 'Szacowany vs rzeczywisty czas na kolumnę', + 'HUF - Hungarian Forint' => 'HUF - Forint węgierski', + 'XBT - Bitcoin' => 'XBT - Bitcoin', + 'You must select a file to upload as your avatar!' => 'Musisz wybrać plik, który chcesz przesłać jako swój awatar!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'Przesłany plik nie jest prawidłowym obrazem! (Tylko *.gif, *.jpg, *.jpeg i *.png są dozwolone!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Automatycznie ustaw datę wykonania, gdy zadanie zostanie przeniesione z określonej kolumny', + 'No other projects found.' => 'Nie znaleziono innych projektów.', + 'Tasks copied successfully.' => 'Zadania skopiowane pomyślnie.', + 'Unable to copy tasks.' => 'Nie udało się skopiować zadań.', + 'Theme' => 'Motyw', + 'Theme:' => 'Motyw:', + 'Light theme' => 'Jasny motyw', + 'Dark theme' => 'Ciemny motyw', + 'Automatic theme - Sync with system' => 'Motyw automatyczny – synchronizuj z systemem', + 'Application managers or more' => 'Menedżerowie aplikacji lub wyżej', + 'Administrators' => 'Administratorzy', + 'Visibility:' => 'Widoczność:', + 'Standard users' => 'Użytkownicy standardowi', + 'Visibility is required' => 'Widoczność jest wymagana', + 'The visibility should be an app role' => 'Widoczność powinna odpowiadać roli w aplikacji', + 'Reply' => 'Odpowiedz', + '%s wrote: ' => '%s napisał(a): ', + 'Number of visible tasks in this column and swimlane' => 'Liczba widocznych zadań w tej kolumnie i torze', + 'Number of tasks in this swimlane' => 'Liczba zadań w tym torze', + 'Unable to find another subtask in progress, you can close this window.' => 'Nie znaleziono innego podzadania w toku, możesz zamknąć to okno.', + 'This theme is invalid' => 'Ten motyw jest nieprawidłowy', + 'This role is invalid' => 'Ta rola jest nieprawidłowa', + 'This timezone is invalid' => 'Ta strefa czasowa jest nieprawidłowa', + 'This language is invalid' => 'Ten język jest nieprawidłowy', + 'This URL is invalid' => 'Ten adres URL jest nieprawidłowy', + 'Date format invalid' => 'Nieprawidłowy format daty', + 'Time format invalid' => 'Nieprawidłowy format czasu', + 'Invalid Mail transport' => 'Nieprawidłowy sposób przesyłania poczty', + 'Color invalid' => 'Nieprawidłowy kolor', + 'This value must be greater or equal to %d' => 'Ta wartość musi być większa lub równa %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Dodaj BOM na początku pliku (wymagane dla Microsoft Excel)', + 'Just add these tag(s)' => 'Po prostu dodaj te tagi', + 'Remove internal link(s)' => 'Usuń wewnętrzne odnośniki', + 'Import tasks from another project' => 'Importuj zadania z innego projektu', + 'Select the project to copy tasks from' => 'Wybierz projekt, z którego chcesz skopiować zadania', + 'The total maximum allowed attachments size is %sB.' => 'Maksymalny dozwolony rozmiar załączników to %sB.', + 'Add attachments' => 'Dodaj załączniki', + 'Task #%d "%s" is overdue' => 'Zadanie nr %d "%s" jest przeterminowane', + 'Enable notifications by default for all new users' => 'Włącz powiadomienia domyślnie dla wszystkich nowych użytkowników', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Przypisz zadanie do jego twórcy dla określonych kolumn, jeśli nie ustawiono ręcznie osoby odpowiedzialnej', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Przypisz zadanie do zalogowanego użytkownika przy zmianie kolumny na wskazaną, jeśli nie ma przypisanego użytkownika', +]; diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php new file mode 100644 index 0000000..4defcb7 --- /dev/null +++ b/app/Locale/pt_BR/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => '.', + 'None' => 'Nenhum', + 'Edit' => 'Editar', + 'Remove' => 'Remover', + 'Yes' => 'Sim', + 'No' => 'Não', + 'cancel' => 'cancelar', + 'or' => 'ou', + 'Yellow' => 'Amarelo', + 'Blue' => 'Azul', + 'Green' => 'Verde', + 'Purple' => 'Roxo', + 'Red' => 'Vermelho', + 'Orange' => 'Laranja', + 'Grey' => 'Cinza', + 'Brown' => 'Marrom', + 'Deep Orange' => 'Laranja escuro', + 'Dark Grey' => 'Cinza escuro', + 'Pink' => 'Rosa', + 'Teal' => 'Turquesa', + 'Cyan' => 'Azul intenso', + 'Lime' => 'Verde limão', + 'Light Green' => 'Verde claro', + 'Amber' => 'Âmbar', + 'Save' => 'Salvar', + 'Login' => 'Login', + 'Official website:' => 'Site oficial:', + 'Unassigned' => 'Não Atribuída', + 'View this task' => 'Ver esta tarefa', + 'Remove user' => 'Remover usuário', + 'Do you really want to remove this user: "%s"?' => 'Você realmente deseja remover este usuário: "%s"?', + 'All users' => 'Todos os usuários', + 'Username' => 'Nome de usuário', + 'Password' => 'Senha', + 'Administrator' => 'Administrador', + 'Sign in' => 'Entrar', + 'Users' => 'Usuários', + 'Forbidden' => 'Proibido', + 'Access Forbidden' => 'Acesso negado', + 'Edit user' => 'Editar usuário', + 'Logout' => 'Sair', + 'Bad username or password' => 'Usuário ou senha inválidos', + 'Edit project' => 'Editar projeto', + 'Name' => 'Nome', + 'Projects' => 'Projetos', + 'No project' => 'Nenhum projeto', + 'Project' => 'Projeto', + 'Status' => 'Status', + 'Tasks' => 'Tarefas', + 'Board' => 'Quadro', + 'Actions' => 'Ações', + 'Inactive' => 'Inativo', + 'Active' => 'Ativo', + 'Unable to update this board.' => 'Não foi possível atualizar este quadro.', + 'Disable' => 'Desativar', + 'Enable' => 'Ativar', + 'New project' => 'Novo projeto', + 'Do you really want to remove this project: "%s"?' => 'Você realmente deseja remover este projeto: "%s"?', + 'Remove project' => 'Remover projeto', + 'Edit the board for "%s"' => 'Editar o quadro para "%s"', + 'Add a new column' => 'Adicionar uma nova coluna', + 'Title' => 'Título', + 'Assigned to %s' => 'Designado para %s', + 'Remove a column' => 'Remover uma coluna', + 'Unable to remove this column.' => 'Não foi possível remover esta coluna.', + 'Do you really want to remove this column: "%s"?' => 'Você realmente deseja remover esta coluna: "%s"?', + 'Settings' => 'Configurações', + 'Application settings' => 'Configurações da aplicação', + 'Language' => 'Idioma', + 'Webhook token:' => 'Token de webhooks:', + 'API token:' => 'Token de API:', + 'Database size:' => 'Tamanho do banco de dados:', + 'Download the database' => 'Baixar o banco de dados', + 'Optimize the database' => 'Otimizar o banco de dados', + '(VACUUM command)' => '(Comando VACUUM)', + '(Gzip compressed Sqlite file)' => '(Arquivo Sqlite comprimido com Gzip)', + 'Close a task' => 'Finalizar uma tarefa', + 'Column' => 'Coluna', + 'Color' => 'Cor', + 'Assignee' => 'Designação', + 'Create another task' => 'Criar outra tarefa', + 'New task' => 'Nova tarefa', + 'Open a task' => 'Abrir uma tarefa', + 'Do you really want to open this task: "%s"?' => 'Você realmente deseja abrir esta tarefa: "%s"?', + 'Back to the board' => 'Voltar ao quadro', + 'There is nobody assigned' => 'Não há ninguém designado', + 'Column on the board:' => 'Coluna no quadro:', + 'Close this task' => 'Finalizar esta tarefa', + 'Open this task' => 'Abrir esta tarefa', + 'There is no description.' => 'Não há descrição.', + 'Add a new task' => 'Adicionar uma nova tarefa', + 'The username is required' => 'O nome de usuário é obrigatório', + 'The maximum length is %d characters' => 'O tamanho máximo é %d caracteres', + 'The minimum length is %d characters' => 'O tamanho mínimo é %d caracteres', + 'The password is required' => 'A senha é obrigatória', + 'This value must be an integer' => 'O valor deve ser um número inteiro', + 'The username must be unique' => 'O nome de usuário deve ser único', + 'The user id is required' => 'O ID de usuário é obrigatório', + 'Passwords don\'t match' => 'As senhas não coincidem', + 'The confirmation is required' => 'A confirmação é obrigatória', + 'The project is required' => 'O projeto é obrigatório', + 'The id is required' => 'O ID é obrigatório', + 'The project id is required' => 'O ID do projeto é obrigatório', + 'The project name is required' => 'O nome do projeto é obrigatório', + 'The title is required' => 'O título é obrigatório', + 'Settings saved successfully.' => 'Configurações salvas com sucesso.', + 'Unable to save your settings.' => 'Não é possível salvar suas configurações.', + 'Database optimization done.' => 'Otimização do banco de dados concluída.', + 'Your project has been created successfully.' => 'Seu projeto foi criado com sucesso.', + 'Unable to create your project.' => 'Não é possível criar o seu projeto.', + 'Project updated successfully.' => 'Projeto atualizado com sucesso.', + 'Unable to update this project.' => 'Não é possível atualizar este projeto.', + 'Unable to remove this project.' => 'Não é possível remover este projeto.', + 'Project removed successfully.' => 'Projeto removido com sucesso.', + 'Project activated successfully.' => 'Projeto ativado com sucesso.', + 'Unable to activate this project.' => 'Não é possível ativar este projeto.', + 'Project disabled successfully.' => 'Projeto desativado com sucesso.', + 'Unable to disable this project.' => 'Não é possível desativar este projeto.', + 'Unable to open this task.' => 'Não é possível abrir esta tarefa.', + 'Task opened successfully.' => 'Tarefa aberta com sucesso.', + 'Unable to close this task.' => 'Não é possível finalizar esta tarefa.', + 'Task closed successfully.' => 'Tarefa finalizada com sucesso.', + 'Unable to update your task.' => 'Não é possível atualizar a sua tarefa.', + 'Task updated successfully.' => 'Tarefa atualizada com sucesso.', + 'Unable to create your task.' => 'Não é possível criar a sua tarefa.', + 'Task created successfully.' => 'Tarefa criada com sucesso.', + 'User created successfully.' => 'Usuário criado com sucesso.', + 'Unable to create your user.' => 'Não é possível criar o seu usuário.', + 'User updated successfully.' => 'Usuário atualizado com sucesso.', + 'User removed successfully.' => 'Usuário removido com sucesso.', + 'Unable to remove this user.' => 'Não é possível remover este usuário.', + 'Board updated successfully.' => 'Quadro atualizado com sucesso.', + 'Ready' => 'A fazer', + 'Backlog' => 'Backlog', + 'Work in progress' => 'Em andamento', + 'Done' => 'Feito', + 'Application version:' => 'Versão da aplicação:', + 'Id' => 'Id', + 'Public link' => 'Link público', + 'Timezone' => 'Fuso horário', + 'Sorry, I didn\'t find this information in my database!' => 'Desculpe, não encontrei esta informação no meu banco de dados!', + 'Page not found' => 'Página não encontrada', + 'Complexity' => 'Complexidade', + 'Task limit' => 'Limite de tarefas', + 'Task count' => 'Número de tarefas', + 'User' => 'Usuário', + 'Comments' => 'Comentários', + 'Comment is required' => 'Comentário é obrigatório', + 'Comment added successfully.' => 'Comentário adicionado com sucesso.', + 'Unable to create your comment.' => 'Não é possível criar o seu comentário.', + 'Due Date' => 'Data fim estimada', + 'Invalid date' => 'Data inválida', + 'Automatic actions' => 'Ações automáticas', + 'Your automatic action has been created successfully.' => 'Sua ação automática foi criada com sucesso.', + 'Unable to create your automatic action.' => 'Não é possível criar sua ação automática.', + 'Remove an action' => 'Remover uma ação', + 'Unable to remove this action.' => 'Não é possível remover esta ação.', + 'Action removed successfully.' => 'Ação removida com sucesso.', + 'Automatic actions for the project "%s"' => 'Ações automáticas para o projeto "%s"', + 'Add an action' => 'Adicionar Ação', + 'Event name' => 'Nome do evento', + 'Action' => 'Ação', + 'Event' => 'Evento', + 'When the selected event occurs execute the corresponding action.' => 'Quando o evento selecionado ocorrer execute a ação correspondente.', + 'Next step' => 'Próximo passo', + 'Define action parameters' => 'Definir parâmetros da ação', + 'Do you really want to remove this action: "%s"?' => 'Você realmente deseja remover esta ação: "%s"?', + 'Remove an automatic action' => 'Remover uma ação automática', + 'Assign the task to a specific user' => 'Atribuir a tarefa para um usuário específico', + 'Assign the task to the person who does the action' => 'Atribuir a tarefa para a pessoa que executa a ação', + 'Duplicate the task to another project' => 'Duplicar a tarefa para outro projeto', + 'Move a task to another column' => 'Mover a tarefa para outra coluna', + 'Task modification' => 'Modificação de tarefa', + 'Task creation' => 'Criação de tarefa', + 'Closing a task' => 'Finalizando uma tarefa', + 'Assign a color to a specific user' => 'Atribuir uma cor para um usuário específico', + 'Position' => 'Posição', + 'Duplicate to project' => 'Duplicar para outro projeto', + 'Duplicate' => 'Duplicar', + 'Link' => 'Link', + 'Comment updated successfully.' => 'Comentário atualizado com sucesso.', + 'Unable to update your comment.' => 'Não é possível atualizar o seu comentário.', + 'Remove a comment' => 'Remover um comentário', + 'Comment removed successfully.' => 'Comentário removido com sucesso.', + 'Unable to remove this comment.' => 'Não é possível remover este comentário.', + 'Do you really want to remove this comment?' => 'Você realmente deseja remover este comentário?', + 'Current password for the user "%s"' => 'Senha atual para o usuário "%s"', + 'The current password is required' => 'A senha atual é obrigatória', + 'Wrong password' => 'Senha incorreta', + 'Unknown' => 'Desconhecido', + 'Last logins' => 'Últimos logins', + 'Login date' => 'Data de login', + 'Authentication method' => 'Método de autenticação', + 'IP address' => 'Endereço IP', + 'User agent' => 'User Agent', + 'Persistent connections' => 'Conexões persistentes', + 'No session.' => 'Nenhuma sessão.', + 'Expiration date' => 'Data de expiração', + 'Remember Me' => 'Lembre-se de mim', + 'Creation date' => 'Data de criação', + 'Everybody' => 'Todos', + 'Open' => 'Abrir', + 'Closed' => 'Finalizado', + 'Search' => 'Pesquisar', + 'Nothing found.' => 'Nada foi encontrado.', + 'Due date' => 'Data fim estimada', + 'Description' => 'Descrição', + '%d comments' => '%d comentários', + '%d comment' => '%d comentário', + 'Email address invalid' => 'Endereço de e-mail inválido', + 'Your external account is not linked anymore to your profile.' => 'Sua conta externa não está mais ligada ao seu perfil.', + 'Unable to unlink your external account.' => 'Impossível de remover a sua conta externa.', + 'External authentication failed' => 'Autenticação externa falhou', + 'Your external account is linked to your profile successfully.' => 'Sua conta externa está agora ligada ao seu perfil.', + 'Email' => 'E-mail', + 'Task removed successfully.' => 'Tarefa removida com sucesso.', + 'Unable to remove this task.' => 'Não foi possível remover esta tarefa.', + 'Remove a task' => 'Remover uma tarefa', + 'Do you really want to remove this task: "%s"?' => 'Você realmente deseja remover esta tarefa: "%s"?', + 'Assign automatically a color based on a category' => 'Atribuir automaticamente uma cor com base em uma categoria', + 'Assign automatically a category based on a color' => 'Atribuir automaticamente uma categoria com base em uma cor', + 'Task creation or modification' => 'Criação ou modificação de tarefa', + 'Category' => 'Categoria', + 'Category:' => 'Categoria:', + 'Categories' => 'Categorias', + 'Your category has been created successfully.' => 'Sua categoria foi criada com sucesso.', + 'This category has been updated successfully.' => 'A sua categoria foi atualizada com sucesso.', + 'Unable to update this category.' => 'Não foi possível atualizar a sua categoria.', + 'Remove a category' => 'Remover uma categoria', + 'Category removed successfully.' => 'Categoria removida com sucesso.', + 'Unable to remove this category.' => 'Não foi possível remover esta categoria.', + 'Category modification for the project "%s"' => 'Modificação de categoria para o projeto "%s"', + 'Category Name' => 'Nome da categoria', + 'Add a new category' => 'Adicionar uma nova categoria', + 'Do you really want to remove this category: "%s"?' => 'Você realmente deseja remover esta categoria: "%s"?', + 'All categories' => 'Todas as categorias', + 'No category' => 'Nenhuma categoria', + 'The name is required' => 'O nome é obrigatório', + 'Remove a file' => 'Remover um arquivo', + 'Unable to remove this file.' => 'Não foi possível remover este arquivo.', + 'File removed successfully.' => 'Arquivo removido com sucesso.', + 'Attach a document' => 'Anexar um documento', + 'Do you really want to remove this file: "%s"?' => 'Você realmente deseja remover este arquivo: "%s"?', + 'Attachments' => 'Anexos', + 'Edit the task' => 'Editar a tarefa', + 'Add a comment' => 'Adicionar um comentário', + 'Edit a comment' => 'Editar um comentário', + 'Summary' => 'Resumo', + 'Time tracking' => 'Rastreamento de tempo', + 'Estimate:' => 'Estimado:', + 'Spent:' => 'Gasto:', + 'Do you really want to remove this sub-task?' => 'Você realmente deseja remover esta subtarefa?', + 'Remaining:' => 'Restante:', + 'hours' => 'horas', + 'estimated' => 'estimado', + 'Sub-Tasks' => 'Subtarefas', + 'Add a sub-task' => 'Adicionar uma subtarefa', + 'Original estimate' => 'Estimativa original', + 'Create another sub-task' => 'Criar uma outra subtarefa', + 'Time spent' => 'Tempo gasto', + 'Edit a sub-task' => 'Editar uma subtarefa', + 'Remove a sub-task' => 'Remover uma subtarefa', + 'The time must be a numeric value' => 'O tempo deve ser um valor numérico', + 'Todo' => 'A fazer', + 'In progress' => 'Em andamento', + 'Sub-task removed successfully.' => 'Subtarefa removida com sucesso.', + 'Unable to remove this sub-task.' => 'Não foi possível remover esta subtarefa.', + 'Sub-task updated successfully.' => 'Subtarefa atualizada com sucesso.', + 'Unable to update your sub-task.' => 'Não foi possível atualizar a sua subtarefa.', + 'Unable to create your sub-task.' => 'Não é possível criar a sua subtarefa.', + 'Maximum size: ' => 'Tamanho máximo: ', + 'Display another project' => 'Exibir outro projeto', + 'Created by %s' => 'Criado por %s', + 'Tasks Export' => 'Exportar Tarefas', + 'Start Date' => 'Data inicial', + 'Execute' => 'Executar', + 'Task Id' => 'ID da Tarefa', + 'Creator' => 'Criado por', + 'Modification date' => 'Data da modificação', + 'Completion date' => 'Data da finalização', + 'Clone' => 'Clonar', + 'Project cloned successfully.' => 'Projeto clonado com sucesso.', + 'Unable to clone this project.' => 'Não foi possível clonar este projeto.', + 'Enable email notifications' => 'Habilitar notificações por e-mail', + 'Task position:' => 'Posição da tarefa:', + 'The task #%d has been opened.' => 'A tarefa #%d foi aberta.', + 'The task #%d has been closed.' => 'A tarefa #%d foi finalizada.', + 'Sub-task updated' => 'Subtarefa atualizada', + 'Title:' => 'Título:', + 'Status:' => 'Status:', + 'Assignee:' => 'Designado:', + 'Time tracking:' => 'Controle de tempo:', + 'New sub-task' => 'Nova subtarefa', + 'New attachment added "%s"' => 'Novo anexo adicionado "%s"', + 'New comment posted by %s' => 'Novo comentário postado por %s', + 'New comment' => 'Novo comentário', + 'Comment updated' => 'Comentário atualizado', + 'New subtask' => 'Nova subtarefa', + 'I only want to receive notifications for these projects:' => 'Quero receber notificações apenas destes projetos:', + 'view the task on Kanboard' => 'ver a tarefa no Kanboard', + 'Public access' => 'Acesso público', + 'Disable public access' => 'Desabilitar o acesso público', + 'Enable public access' => 'Habilitar o acesso público', + 'Public access disabled' => 'Acesso público desabilitado', + 'Move the task to another project' => 'Mover a tarefa para outro projeto', + 'Move to project' => 'Mover para outro projeto', + 'Do you really want to duplicate this task?' => 'Você realmente deseja duplicar esta tarefa?', + 'Duplicate a task' => 'Duplicar uma tarefa', + 'External accounts' => 'Contas externas', + 'Account type' => 'Tipo de conta', + 'Local' => 'Local', + 'Remote' => 'Remoto', + 'Enabled' => 'Habilitado', + 'Disabled' => 'Desabilitado', + 'Login:' => 'Usuário:', + 'Full Name:' => 'Nome:', + 'Email:' => 'E-mail:', + 'Notifications:' => 'Notificações:', + 'Notifications' => 'Notificações', + 'Account type:' => 'Tipo de conta:', + 'Edit profile' => 'Editar perfil', + 'Change password' => 'Alterar senha', + 'Password modification' => 'Alteração de senha', + 'External authentications' => 'Autenticação externa', + 'Never connected.' => 'Nunca conectado.', + 'No external authentication enabled.' => 'Nenhuma autenticação externa habilitada.', + 'Password modified successfully.' => 'Senha alterada com sucesso.', + 'Unable to change the password.' => 'Não foi possível alterar a senha.', + 'Change category' => 'Mudar categoria', + '%s updated the task %s' => '%s atualizou a tarefa %s', + '%s opened the task %s' => '%s abriu a tarefa %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s moveu a tarefa %s para a posição #%d na coluna "%s"', + '%s moved the task %s to the column "%s"' => '%s moveu a tarefa %s para a coluna "%s"', + '%s created the task %s' => '%s criou a tarefa %s', + '%s closed the task %s' => '%s finalizou a tarefa %s', + '%s created a subtask for the task %s' => '%s criou uma subtarefa para a tarefa %s', + '%s updated a subtask for the task %s' => '%s atualizou uma subtarefa da tarefa %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Designado para %s com tempo estimado de %s/%sh', + 'Not assigned, estimate of %sh' => 'Não designado, estimado em %sh', + '%s updated a comment on the task %s' => '%s atualizou o comentário na tarefa %s', + '%s commented the task %s' => '%s comentou a tarefa %s', + '%s\'s activity' => 'Atividades de %s', + 'RSS feed' => 'Feed RSS', + '%s updated a comment on the task #%d' => '%s atualizou um comentário sobre a tarefa #%d', + '%s commented on the task #%d' => '%s comentou sobre a tarefa #%d', + '%s updated a subtask for the task #%d' => '%s atualizou uma subtarefa para a tarefa #%d', + '%s created a subtask for the task #%d' => '%s criou uma subtarefa para a tarefa #%d', + '%s updated the task #%d' => '%s atualizou a tarefa #%d', + '%s created the task #%d' => '%s criou a tarefa #%d', + '%s closed the task #%d' => '%s finalizou a tarefa #%d', + '%s opened the task #%d' => '%s abriu a tarefa #%d', + 'Activity' => 'Atividade', + 'Default values are "%s"' => 'Os valores padrão são "%s"', + 'Default columns for new projects (Comma-separated)' => 'Colunas padrão para novos projetos (Separado por vírgula)', + 'Task assignee change' => 'Mudar designação da tarefa', + '%s changed the assignee of the task #%d to %s' => '%s mudou a designação da tarefa #%d para %s', + '%s changed the assignee of the task %s to %s' => '%s mudou a designação da tarefa %s para %s', + 'New password for the user "%s"' => 'Nova senha para o usuário "%s"', + 'Choose an event' => 'Escolher um evento', + 'Create a task from an external provider' => 'Criar uma tarefa por meio de um serviço externo', + 'Change the assignee based on an external username' => 'Alterar designação com base em um usuário externo', + 'Change the category based on an external label' => 'Alterar categoria com base em um rótulo externo', + 'Reference' => 'Referência', + 'Label' => 'Rótulo', + 'Database' => 'Banco de dados', + 'About' => 'Sobre', + 'Database driver:' => 'Driver do banco de dados:', + 'Board settings' => 'Configurações do quadro', + 'Webhook settings' => 'Configurações do Webhook', + 'Reset token' => 'Resetar token', + 'API endpoint:' => 'API endpoint:', + 'Refresh interval for personal board' => 'Intervalo de atualização para um quadro privado', + 'Refresh interval for public board' => 'Intervalo de atualização para um quadro público', + 'Task highlight period' => 'Período de Tarefa em destaque', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Período (em segundos) para considerar que uma tarefa foi modificada recentemente (0 para desativar, 2 dias por padrão)', + 'Frequency in second (60 seconds by default)' => 'Frequência em segundos (60 segundos por padrão)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frequência em segundos (0 para desativar este recurso, 10 segundos por padrão)', + 'Application URL' => 'URL da Aplicação', + 'Token regenerated.' => 'Novo token gerado.', + 'Date format' => 'Formato de data', + 'ISO format is always accepted, example: "%s" and "%s"' => 'O formato ISO é sempre aceito, exemplo: "%s" e "%s"', + 'New personal project' => 'Novo projeto pessoal', + 'This project is personal' => 'Este projeto é pessoal', + 'Add' => 'Adicionar', + 'Start date' => 'Data de início', + 'Time estimated' => 'Tempo estimado', + 'There is nothing assigned to you.' => 'Não há nada designado a você.', + 'My tasks' => 'Minhas tarefas', + 'Activity stream' => 'Atividades Recentes', + 'Dashboard' => 'Painel de Controle', + 'Confirmation' => 'Confirmação', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Create a comment from an external provider' => 'Criar um comentário por meio de um serviço externo', + 'Project management' => 'Gerenciamento de projetos', + 'Columns' => 'Colunas', + 'Task' => 'Tarefas', + 'Percentage' => 'Porcentagem', + 'Number of tasks' => 'Número de tarefas', + 'Task distribution' => 'Distribuição de tarefas', + 'Analytics' => 'Estatísticas', + 'Subtask' => 'Subtarefa', + 'User repartition' => 'Redistribuição de usuário', + 'Clone this project' => 'Clonar este projeto', + 'Column removed successfully.' => 'Coluna removida com sucesso.', + 'Not enough data to show the graph.' => 'Não há dados suficientes para mostrar o gráfico.', + 'Previous' => 'Anterior', + 'The id must be an integer' => 'O ID deve ser um número inteiro', + 'The project id must be an integer' => 'O ID do projeto deve ser um inteiro', + 'The status must be an integer' => 'O status deve ser um número inteiro', + 'The subtask id is required' => 'O ID da subtarefa é obrigatório', + 'The subtask id must be an integer' => 'O ID da subtarefa deve ser um número inteiro', + 'The task id is required' => 'O ID da tarefa é obrigatório', + 'The task id must be an integer' => 'O ID da tarefa deve ser um número inteiro', + 'The user id must be an integer' => 'O ID do usuário deve ser um número inteiro', + 'This value is required' => 'Este valor é obrigatório', + 'This value must be numeric' => 'Este valor deve ser numérico', + 'Unable to create this task.' => 'Não foi possível criar esta tarefa.', + 'Cumulative flow diagram' => 'Fluxograma cumulativo', + 'Daily project summary' => 'Resumo diário do projeto', + 'Daily project summary export' => 'Exportação diária do resumo do projeto', + 'Exports' => 'Exportar', + 'This export contains the number of tasks per column grouped per day.' => 'Esta exportação contém o número de tarefas por coluna agrupada por dia.', + 'Active swimlanes' => 'Raias ativas', + 'Add a new swimlane' => 'Adicionar uma nova raia', + 'Default swimlane' => 'Raia padrão', + 'Do you really want to remove this swimlane: "%s"?' => 'Você realmente deseja remover esta raia: "%s"?', + 'Inactive swimlanes' => 'Raias inativas', + 'Remove a swimlane' => 'Remover uma raia', + 'Swimlane modification for the project "%s"' => 'Modificação de raia para o projeto "%s"', + 'Swimlane removed successfully.' => 'Raia removida com sucesso.', + 'Swimlanes' => 'Raias', + 'Swimlane updated successfully.' => 'Raia atualizada com sucesso.', + 'Unable to remove this swimlane.' => 'Não foi possível remover esta raia.', + 'Unable to update this swimlane.' => 'Não foi possível atualizar esta raia.', + 'Your swimlane has been created successfully.' => 'Sua raia foi criada com sucesso.', + 'Example: "Bug, Feature Request, Improvement"' => 'Exemplo: "Bug, Solicitação de Recurso, Melhoria"', + 'Default categories for new projects (Comma-separated)' => 'Categorias padrões para novos projetos (separadas por vírgula)', + 'Integrations' => 'Integrações', + 'Integration with third-party services' => 'Integração com serviços de terceiros', + 'Subtask Id' => 'ID da subtarefa', + 'Subtasks' => 'Subtarefas', + 'Subtasks Export' => 'Exportar subtarefas', + 'Task Title' => 'Título da Tarefa', + 'Untitled' => 'Sem título', + 'Application default' => 'Padrão da aplicação', + 'Language:' => 'Idioma', + 'Timezone:' => 'Fuso horário', + 'All columns' => 'Todas as colunas', + 'Next' => 'Próximo', + '#%d' => '#%d', + 'All swimlanes' => 'Todas as raias', + 'All colors' => 'Todas as cores', + 'Moved to column %s' => 'Movido para a coluna %s', + 'User dashboard' => 'Painel de Controle do usuário', + 'Allow only one subtask in progress at the same time for a user' => 'Permitir apenas uma subtarefa em andamento ao mesmo tempo para um usuário', + 'Edit column "%s"' => 'Editar a coluna "%s"', + 'Select the new status of the subtask: "%s"' => 'Selecionar um novo status para a subtarefa: "%s"', + 'Subtask timesheet' => 'Gestão de tempo das subtarefas', + 'There is nothing to show.' => 'Não há nada para mostrar.', + 'Time Tracking' => 'Gestão de tempo', + 'You already have one subtask in progress' => 'Você já tem um subtarefa em andamento', + 'Which parts of the project do you want to duplicate?' => 'Quais partes do projeto você deseja duplicar?', + 'Disallow login form' => 'Proibir o formulário de login', + 'Start' => 'Início', + 'End' => 'Fim', + 'Task age in days' => 'Idade da tarefa em dias', + 'Days in this column' => 'Dias nesta coluna', + '%dd' => '%dd', + 'Add a new link' => 'Adicionar uma nova associação', + 'Do you really want to remove this link: "%s"?' => 'Você realmente deseja remover esta associação: "%s"?', + 'Do you really want to remove this link with task #%d?' => 'Você realmente deseja remover esta associação com a tarefa #%d?', + 'Field required' => 'Campo requerido', + 'Link added successfully.' => 'Associação criada com sucesso.', + 'Link updated successfully.' => 'Associação atualizada com sucesso.', + 'Link removed successfully.' => 'Associação removida com sucesso.', + 'Link labels' => 'Rótulos das associações', + 'Link modification' => 'Modificação de uma associação', + 'Opposite label' => 'Nome do rótulo oposto', + 'Remove a link' => 'Remover uma associação', + 'The labels must be different' => 'Os rótulos devem ser diferentes', + 'There is no link.' => 'Não há nenhuma associação.', + 'This label must be unique' => 'Este rótulo deve ser único', + 'Unable to create your link.' => 'Impossível de adicionar sua associação.', + 'Unable to update your link.' => 'Impossível de atualizar sua associação.', + 'Unable to remove this link.' => 'Impossível de remover sua associação.', + 'relates to' => 'é associada com', + 'blocks' => 'bloqueia', + 'is blocked by' => 'está bloqueada por', + 'duplicates' => 'duplica', + 'is duplicated by' => 'é duplicada por', + 'is a child of' => 'é filha de', + 'is a parent of' => 'é pai de', + 'targets milestone' => 'marcos alvos', + 'is a milestone of' => 'é um marco de', + 'fixes' => 'corrige', + 'is fixed by' => 'foi corrigida por', + 'This task' => 'Esta tarefa', + '<1h' => '<1h', + '%dh' => '%dh', + 'Expand tasks' => 'Expandir tarefas', + 'Collapse tasks' => 'Contrair tarefas', + 'Expand/collapse tasks' => 'Expandir/Contrair tarefas', + 'Close dialog box' => 'Fechar a caixa de diálogo', + 'Submit a form' => 'Envia o formulário', + 'Board view' => 'Visão do quadro', + 'Keyboard shortcuts' => 'Atalhos de teclado', + 'Open board switcher' => 'Abrir opções de quadros', + 'Application' => 'Aplicação', + 'Compact view' => 'Vista reduzida', + 'Horizontal scrolling' => 'Rolagem horizontal', + 'Compact/wide view' => 'Alternar entre a vista compacta e ampliada', + 'Currency' => 'Moeda', + 'Personal project' => 'Projeto pessoal', + 'AUD - Australian Dollar' => 'AUD - Dólar australiano', + 'CAD - Canadian Dollar' => 'CAD - Dólar canadense', + 'CHF - Swiss Francs' => 'CHF - Francos Suíços', + 'Custom Stylesheet' => 'Folha de estilo personalizado', + 'EUR - Euro' => 'EUR - Euro', + 'GBP - British Pound' => 'GBP - Libra esterlina', + 'INR - Indian Rupee' => 'INR - Rúpia indiana', + 'JPY - Japanese Yen' => 'JPY - Iene japonês', + 'NZD - New Zealand Dollar' => 'NZD - Dólar neozelandês', + 'PEN - Peruvian Sol' => 'PEN - Sol peruano', + 'RSD - Serbian dinar' => 'RSD - Dinar sérvio', + 'CNY - Chinese Yuan' => 'CNY - Yuan chinês', + 'USD - US Dollar' => 'USD - Dólar norte-americano', + 'VES - Venezuelan Bolívar' => 'VES - Bolívar venezuelano', + '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', + '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', + 'Reference currency' => 'Moeda de Referência', + 'The currency rate has 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.', + 'Webhook URL' => 'URL do webhook', + '%s removed the assignee of the task %s' => '%s removeu a pessoa designada para a tarefa %s', + 'Information' => 'Informações', + 'Check two factor authentication code' => 'Verifique o código de autenticação em duas etapas', + 'The two factor authentication code is not valid.' => 'O código de autenticação em duas etapas não é válido.', + 'The two factor authentication code is valid.' => 'O código de autenticação em duas etapas é válido.', + 'Code' => 'Código', + 'Two factor authentication' => 'Autenticação em duas etapas', + 'This QR code contains the key URI: ' => 'Este Código QR contém a chave URI:', + '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 for movida para uma coluna específica', + '%s via Kanboard' => '%s via Kanboard', + 'Burndown chart' => 'Gráfico de Burndown', + 'This chart show the task complexity over the time (Work Remaining).' => 'Este gráfico mostra a complexidade da tarefa ao longo do tempo (Trabalho Restante).', + 'Screenshot taken %s' => 'Captura de tela tirada em %s', + 'Add a screenshot' => 'Adicionar uma captura de tela', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Tire uma captura de tela e pressione CTRL+V ou ⌘+V para colar aqui.', + 'Screenshot uploaded successfully.' => 'Captura de tela enviada com sucesso.', + 'SEK - Swedish Krona' => 'SEK - Coroa sueca', + 'Identifier' => 'Identificador', + 'Disable two factor authentication' => 'Desativar autenticação em duas etapas', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Você realmente deseja desativar a autenticação em duas etapas para este usuário: "%s"?', + 'Edit link' => 'Editar um link', + 'Start to type task title...' => 'Digite o título da tarefa...', + 'A task cannot be linked to itself' => 'Uma tarefa não pode ser vinculada a si própria', + 'The exact same link already exists' => 'Um link idêntico já existe', + 'Recurrent task is scheduled to be generated' => 'A tarefa recorrente está programada para ser criada', + 'Score' => 'Complexidade', + 'The identifier must be unique' => 'O identificador deve ser único', + 'This linked task id doesn\'t exists' => 'O identificador da tarefa associada não existe', + 'This value must be alphanumeric' => 'Este valor deve ser alfanumérico', + 'Edit recurrence' => 'Modificar a recorrência', + 'Generate recurrent task' => 'Gerar uma tarefa recorrente', + 'Trigger to generate recurrent task' => 'Trigger para gerar tarefa recorrente', + 'Factor to calculate new due date' => 'Fator para o cálculo da nova data fim estimada', + 'Timeframe to calculate new due date' => 'Escala de tempo para o cálculo da nova data fim estimada', + 'Base date to calculate new due date' => 'Data a ser utilizada para calcular a nova data fim estimada', + 'Action date' => 'Data da ação', + 'Base date to calculate new due date: ' => 'Data a ser utilizada para calcular a nova data fim estimada: ', + 'This task has created this child task: ' => 'Esta tarefa criou a tarefa filha: ', + 'Day(s)' => 'Dia(s)', + 'Existing due date' => 'data fim estimada existente', + 'Factor to calculate new due date: ' => 'Fator para calcular a nova data fim estimada: ', + 'Month(s)' => 'Mês(es)', + 'This task has been created by: ' => 'Esta tarefa foi criada por: ', + 'Recurrent task has been generated:' => 'A tarefa recorrente foi gerada:', + 'Timeframe to calculate new due date: ' => 'Escala de tempo para o cálculo da nova data fim estimada: ', + 'Trigger to generate recurrent task: ' => 'Trigger para gerar tarefa recorrente: ', + 'When task is closed' => 'Quando a tarefa é finalizada', + 'When task is moved from first column' => 'Quando a tarefa é movida fora da primeira coluna', + 'When task is moved to last column' => 'Quando a tarefa é movida para a última coluna', + 'Year(s)' => 'Ano(s)', + 'Project settings' => 'Configurações dos projetos', + 'Automatically update the start date' => 'Atualizar automaticamente a data de início', + 'iCal feed' => 'Subscrição iCal', + 'Preferences' => 'Preferências', + 'Security' => 'Segurança', + 'Two factor authentication disabled' => 'Autenticação em duas etapas desativada', + 'Two factor authentication enabled' => 'Autenticação em duas etapas ativada', + 'Unable to update this user.' => 'Impossível de atualizar esse usuário.', + 'There is no user management for personal projects.' => 'Não há gerenciamento de usuários para projetos pessoais.', + 'User that will receive the email' => 'O usuário que vai receber o e-mail', + 'Email subject' => 'Assunto do e-mail', + 'Date' => 'Data', + 'Add a comment log when moving the task between columns' => 'Adicionar um comentário de log quando uma tarefa é movida para uma outra coluna', + 'Move the task to another column when the category is changed' => 'Mover uma tarefa para outra coluna quando a categoria mudou', + 'Send a task by email to someone' => 'Enviar uma tarefa por e-mail a alguém', + 'Reopen a task' => 'Reabrir uma tarefa', + 'Notification' => 'Notificação', + '%s moved the task #%d to the first swimlane' => '%s moveu a tarefa #%d para a primeira raia', + 'Swimlane' => 'Raia', + '%s moved the task %s to the first swimlane' => '%s moveu a tarefa %s para a primeira raia', + '%s moved the task %s to the swimlane "%s"' => '%s moveu a tarefa %s para a raia "%s"', + 'This report contains all subtasks information for the given date range.' => 'Este relatório contém informações de todas as subtarefas para o período selecionado.', + 'This report contains all tasks information for the given date range.' => 'Este relatório contém informações de todas as tarefas para o período selecionado.', + 'Project activities for %s' => 'Atividade do projeto "%s"', + 'view the board on Kanboard' => 'ver o quadro no Kanboard', + 'The task has been moved to the first swimlane' => 'A tarefa foi movida para a primeira raia', + 'The task has been moved to another swimlane:' => 'A tarefa foi movida para outra raia:', + 'New title: %s' => 'Novo título: %s', + 'The task is not assigned anymore' => 'Agora a tarefa não está mais atribuída', + 'New assignee: %s' => 'Novo designado: %s', + 'There is no category now' => 'Agora não tem mais categoria', + 'New category: %s' => 'Nova categoria: %s', + 'New color: %s' => 'Nova cor: %s', + 'New complexity: %d' => 'Nova complexidade: %d', + 'The due date has been removed' => 'A data fim estimada foi retirada', + 'There is no description anymore' => 'Agora não tem mais descrição', + 'Recurrence settings has been modified' => 'As configurações da recorrência foram modificadas', + 'Time spent changed: %sh' => 'O tempo despendido foi mudado: %sh', + 'Time estimated changed: %sh' => 'O tempo estimado foi mudado/ %sh', + 'The field "%s" has been updated' => 'O campo "%s" foi atualizada', + 'The description has been modified:' => 'A descrição foi modificada', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Você realmente deseja finalizar a tarefa "%s" e todas as suas subtarefas?', + 'I want to receive notifications for:' => 'Eu quero receber as notificações para:', + 'All tasks' => 'Todas as tarefas', + 'Only for tasks assigned to me' => 'Somente as tarefas atribuídas a mim', + 'Only for tasks created by me' => 'Apenas as tarefas que eu criei', + 'Only for tasks created by me and tasks assigned to me' => 'Apenas as tarefas que eu criei e aquelas atribuídas a mim', + '%%Y-%%m-%%d' => '%%d/%%m/%%Y', + 'Total for all columns' => 'Total para todas as colunas', + 'You need at least 2 days of data to show the chart.' => 'Você precisa de pelo menos 2 dias de dados para visualizar o gráfico.', + '<15m' => '<15m', + '<30m' => '<30m', + 'Stop timer' => 'Parar temporizador', + 'Start timer' => 'Iniciar temporizador', + 'My activity stream' => 'Meu feed de atividades', + 'Search tasks' => 'Pesquisar tarefas', + 'Reset filters' => 'Redefinir os filtros', + 'My tasks due tomorrow' => 'Minhas tarefas que expirarão amanhã', + 'Tasks due today' => 'Tarefas que expiram hoje', + 'Tasks due tomorrow' => 'Tarefas que expirarão amanhã', + 'Tasks due yesterday' => 'Tarefas que expiraram ontem', + 'Closed tasks' => 'Tarefas finalizadas', + 'Open tasks' => 'Tarefas abertas', + 'Not assigned' => 'Não designada', + 'View advanced search syntax' => 'Ver a sintaxe para pesquisa avançada', + 'Overview' => 'Visão global', + 'Board/Calendar/List view' => 'Quadro/Calendário/Lista', + 'Switch to the board view' => 'Mudar para a visão de quadro', + 'Switch to the list view' => 'Mudar par o modo Lista', + 'Go to the search/filter box' => 'Ir para o campo de filtro/pesquisa', + 'There is no activity yet.' => 'Não há nenhuma atividade ainda.', + 'No tasks found.' => 'Nenhuma tarefa encontrada.', + 'Keyboard shortcut: "%s"' => 'Tecla de atalho: "%s"', + 'List' => 'Lista', + 'Filter' => 'Filtro', + 'Advanced search' => 'Pesquisa avançada', + 'Example of query: ' => 'Exemplo de consulta: ', + 'Search by project: ' => 'Pesquisar por projeto: ', + 'Search by column: ' => 'Pesquisar por coluna: ', + 'Search by assignee: ' => 'Pesquisar por designado: ', + 'Search by color: ' => 'Pesquisar por cor: ', + 'Search by category: ' => 'Pesquisar por categoria: ', + 'Search by description: ' => 'Pesquisar por descrição: ', + 'Search by due date: ' => 'Pesquisar por data fim estimada: ', + 'Average time spent in each column' => 'Tempo médio gasto em cada coluna', + 'Average time spent' => 'Tempo médio gasto', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Este gráfico mostra o tempo médio gasto em cada coluna para as %d últimas tarefas.', + 'Average Lead and Cycle time' => 'Tempo médio do tempo de condução e tempo de ciclo', + 'Average lead time: ' => 'Tempo de condução médio: ', + 'Average cycle time: ' => 'Tempo de ciclo médio: ', + 'Cycle Time' => 'Tempo de ciclo', + 'Lead Time' => 'Tempo de condução', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Este gráfico mostra o tempo médio do tempo de condução e tempo de ciclo das últimas %d tarefas.', + 'Average time into each column' => 'Tempo médio de cada coluna', + 'Lead and cycle time' => 'Tempo de condução e tempo de ciclo', + 'Lead time: ' => 'Tempo de condução: ', + 'Cycle time: ' => 'Tempo de ciclo: ', + 'Time spent in each column' => 'Tempo gasto em cada coluna', + 'The lead time is the duration between the task creation and the completion.' => 'O tempo de condução é o tempo gasto entre a criação da tarefa e a sua conclusão.', + 'The cycle time is the duration between the start date and the completion.' => 'O Tempo de ciclo é o tempo gasto entre a data de início e a sua conclusão.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Se a tarefa não está finalizada, a hora atual é usada no lugar da data de conclusão.', + 'Set the start date automatically' => 'Definir automaticamente a data de início', + 'Edit Authentication' => 'Modificar a autenticação', + 'Remote user' => 'Usuário remoto', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Os usuários remotos não conservam as suas senhas no banco de dados Kanboard, exemplos: contas LDAP, Github ou Google.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Se você marcar "Impedir o formulário de autenticação", os identificadores entrados no formulário de login serão ignorado.', + 'Default task color' => 'Cor padrão para as tarefas', + 'This feature does not work with all browsers.' => 'Esta funcionalidade não é compatível com todos os navegadores.', + 'There is no destination project available.' => 'Não há nenhum projeto de destino disponível.', + 'Trigger automatically subtask time tracking' => 'Ativar automaticamente o monitoramento do tempo para as subtarefas', + 'Include closed tasks in the cumulative flow diagram' => 'Incluir as tarefas finalizadas no diagrama de fluxo acumulado', + 'Current swimlane: %s' => 'Raia atual: %s', + 'Current column: %s' => 'Coluna atual: %s', + 'Current category: %s' => 'Categoria atual: %s', + 'no category' => 'nenhuma categoria', + 'Current assignee: %s' => 'Designação atual: %s', + 'not assigned' => 'não designado', + 'Author:' => 'Autor:', + 'contributors' => 'contribuidores', + 'License:' => 'Licença:', + 'License' => 'Licença', + 'Enter the text below' => 'Digite o texto abaixo', + 'Start date:' => 'Data de início:', + 'Due date:' => 'Data fim estimada:', + 'People who are project managers' => 'Pessoas que são gerente de projeto', + 'People who are project members' => 'Pessoas que são membro de projeto', + 'NOK - Norwegian Krone' => 'NOK - Coroa Norueguesa', + 'Show this column' => 'Mostrar esta coluna', + 'Hide this column' => 'Esconder esta coluna', + 'End date' => 'Data de término', + 'Users overview' => 'Visão geral dos usuários', + 'Members' => 'Membros', + 'Shared project' => 'Projeto compartilhado', + 'Project managers' => 'Gerentes de projeto', + 'Projects list' => 'Lista dos projetos', + 'End date:' => 'Data de término:', + 'Change task color when using a specific task link' => 'Mudar a cor da tarefa quando um link específico é utilizado', + 'Task link creation or modification' => 'Criação ou modificação de um link em uma tarefa', + 'Milestone' => 'Marco', + 'Reset the search/filter box' => 'Reiniciar o campo de pesquisa', + 'Documentation' => 'Documentação', + 'Author' => 'Autor', + 'Version' => 'Versão', + 'Plugins' => 'Plugins', + 'There is no plugin loaded.' => 'Não há plugins carregados.', + 'My notifications' => 'Minhas notificações', + 'Custom filters' => 'Filtros personalizados', + 'Your custom filter has been created successfully.' => 'Seu filtro personalizado foi criado com sucesso.', + 'Unable to create your custom filter.' => 'Não foi possível criar seu filtro personalizado.', + 'Custom filter removed successfully.' => 'Filtro personalizado removido com sucesso.', + 'Unable to remove this custom filter.' => 'Não foi possível remover este filtro personalizado.', + 'Edit custom filter' => 'Editar filtro personalizado', + 'Your custom filter has been updated successfully.' => 'Seu filtro personalizado foi atualizado com sucesso.', + 'Unable to update custom filter.' => 'Não foi possível atualizar o filtro personalizado.', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Novo anexo na tarefa #%d: %s', + 'New comment on task #%d' => 'Novo comentário na tarefa #%d', + 'Comment updated on task #%d' => 'Comentário atualizado na tarefa #%d', + 'New subtask on task #%d' => 'Nova subtarefa na tarefa #%d', + 'Subtask updated on task #%d' => 'Subtarefa atualizada na tarefa #%d', + 'New task #%d: %s' => 'Nova tarefa #%d: %s', + 'Task updated #%d' => 'Tarefa #%d atualizada', + 'Task #%d closed' => 'Tarefa #%d finalizada', + 'Task #%d opened' => 'Tarefa #%d aberta', + 'Column changed for task #%d' => 'Coluna alterada para a tarefa #%d', + 'New position for task #%d' => 'Nova posição para a tarefa #%d', + 'Swimlane changed for task #%d' => 'Raia alterada para a tarefa #%d', + 'Assignee changed on task #%d' => 'Designação alterada na tarefa #%d', + '%d overdue tasks' => '%d tarefas atrasadas', + 'No notification.' => 'Nenhuma notificação nova.', + 'Mark all as read' => 'Marcar todas como lidas', + 'Mark as read' => 'Marcar como lida', + 'Total number of tasks in this column across all swimlanes' => 'Número total de tarefas nesta coluna através de todas as raias', + 'Collapse swimlane' => 'Contrair raia', + 'Expand swimlane' => 'Expandir raia', + 'Add a new filter' => 'Adicionar filtro', + 'Share with all project members' => 'Compartilhar com todos os membros do projeto', + 'Shared' => 'Compartilhado', + 'Owner' => 'Líder', + 'Unread notifications' => 'Notificações não lidas', + 'Notification methods:' => 'Métodos de notificação:', + 'Unable to read your file' => 'Não foi possível ler seu arquivo', + '%d task(s) have been imported successfully.' => '%d tarefa(s) importada(s) com sucesso.', + 'Nothing has been imported!' => 'Nada foi importado!', + 'Import users from CSV file' => 'Importar usuários a partir de arquivo CSV', + '%d user(s) have been imported successfully.' => '%d usuário(s) importado(s) com sucesso.', + 'Comma' => 'Vírgula', + 'Semi-colon' => 'Ponto e vírgula', + 'Tab' => 'Tab', + 'Vertical bar' => 'Barra vertical', + 'Double Quote' => 'Aspas duplas', + 'Single Quote' => 'Aspas simples', + '%s attached a file to the task #%d' => '%s anexou um arquivo à tarefa #%d', + 'There is no column or swimlane activated in your project!' => 'Não há coluna ou raia ativa em seu projeto!', + 'Append filter (instead of replacement)' => 'Adicionar filtro (em vez de substituir)', + 'Append/Replace' => 'Adicionar/Substituir', + 'Append' => 'Adicionar', + 'Replace' => 'Substituir', + 'Import' => 'Importar', + 'Change sorting' => 'alterar ordenação', + 'Tasks Importation' => 'Importação de Tarefas', + 'Delimiter' => 'Separador', + 'Enclosure' => 'Delimitador de campos', + 'CSV File' => 'Arquivo CSV', + 'Instructions' => 'Instruções', + 'Your file must use the predefined CSV format' => 'Seu arquivo deve utilizar o formato CSV pré-definido', + 'Your file must be encoded in UTF-8' => 'Seu arquivo deve estar codificado em UTF-8', + 'The first row must be the header' => 'A primeira linha deve ser o cabeçalho', + 'Duplicates are not verified for you' => 'Registros duplicados não são verificados', + 'The due date must use the ISO format: YYYY-MM-DD' => 'A data fim estimada deve utilizar o formato ISO: YYYY-MM-DD', + 'Download CSV template' => 'Baixar modelo de arquivo CSV', + 'No external integration registered.' => 'Nenhuma integração externa registrada.', + 'Duplicates are not imported' => 'Registros duplicados não são importados', + 'Usernames must be lowercase and unique' => 'Nomes de usuário devem ser únicos e em letras minúsculas', + 'Passwords will be encrypted if present' => 'Senhas serão encriptadas, se presentes', + '%s attached a new file to the task %s' => '%s anexou um novo arquivo a tarefa %s', + 'Link type' => 'Tipo de link', + 'Assign automatically a category based on a link' => 'Atribuir automaticamente uma categoria baseada num link', + 'BAM - Konvertible Mark' => 'BAM - Mark conversível', + 'Assignee Username' => 'Usuário designado', + 'Assignee Name' => 'Nome do designado', + 'Groups' => 'Grupos', + 'Members of %s' => 'Membros do %s', + 'New group' => 'Novo grupo', + 'Group created successfully.' => 'Grupo criado com sucesso.', + 'Unable to create your group.' => 'Não foi possível de criar o seu grupo.', + 'Edit group' => 'Editar o grupo', + 'Group updated successfully.' => 'Grupo atualizado com sucesso.', + 'Unable to update your group.' => 'Não foi possível atualizar o seu grupo.', + 'Add group member to "%s"' => 'Adicionar um membro ao grupo "%s"', + 'Group member added successfully.' => 'Membro adicionado com sucesso ao o grupo.', + 'Unable to add group member.' => 'Não foi possível adicionar esse membro ao grupo.', + 'Remove user from group "%s"' => 'Remover usuário do grupo "%s"', + 'User removed successfully from this group.' => 'Usuário removido com sucesso deste grupo.', + 'Unable to remove this user from the group.' => 'Não foi possível remover este Usuário do grupo.', + 'Remove group' => 'Remover o grupo', + 'Group removed successfully.' => 'Grupo removido com sucesso.', + 'Unable to remove this group.' => 'Não foi possível remover este grupo.', + 'Project Permissions' => 'Permissões do projeto', + 'Manager' => 'Gerente', + 'Project Manager' => 'Gerente de projeto', + 'Project Member' => 'Membro de projeto', + 'Project Viewer' => 'Visualizador de projeto', + 'Your account is locked for %d minutes' => 'A sua conta está bloqueada por %d minutos', + 'Invalid captcha' => 'Captcha inválido', + 'The name must be unique' => 'O nome deve ser único', + 'View all groups' => 'Ver todos os grupos', + 'There is no user available.' => 'Não há nenhum usuário disponível', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Você realmente deseja remover o usuário "%s" do grupo "%s"?', + 'There is no group.' => 'Não há nenhum grupo.', + 'Add group member' => 'Adicionar um membro ao grupo', + 'Do you really want to remove this group: "%s"?' => 'Você realmente deseja excluir este grupo: "%s"?', + 'There is no user in this group.' => 'Não há usuários neste grupo.', + 'Permissions' => 'Permissões', + 'Allowed Users' => 'Usuários autorizados', + 'No specific user has been allowed.' => 'Nenhum usuário foi especificamente autorizado.', + 'Role' => 'Função', + 'Enter user name...' => 'Digite o nome do usuário...', + 'Allowed Groups' => 'Grupos autorizados', + 'No group has been allowed.' => 'Nenhum grupo foi especificamente autorizado.', + 'Group' => 'Grupo', + 'Group Name' => 'Nome do grupo', + 'Enter group name...' => 'Digite o nome do grupo', + 'Role:' => 'Função:', + 'Project members' => 'Membros de projeto', + '%s mentioned you in the task #%d' => '%s mencionou você na tarefa #%d', + '%s mentioned you in a comment on the task #%d' => '%s mencionou você num comentário da tarefa #%d', + 'You were mentioned in the task #%d' => 'Você foi mencionado na tarefa #%d', + 'You were mentioned in a comment on the task #%d' => 'Você foi mencionado num comentário da tarefa #%d', + 'Estimated hours: ' => 'Horas estimadas: ', + 'Actual hours: ' => 'Horas reais: ', + 'Hours Spent' => 'Horas gastas', + 'Hours Estimated' => 'Horas estimadas', + 'Estimated Time' => 'Tempo estimado', + 'Actual Time' => 'Tempo real', + 'Estimated vs actual time' => 'Tempo estimado vs tempo real', + 'RUB - Russian Ruble' => 'RUB - Rublo russo', + 'Assign the task to the person who does the action when the column is changed' => 'Atribuir a tarefa a pessoa que faz a ação quando a coluna é alterada', + 'Close a task in a specific column' => 'Finalizar uma tarefa em uma coluna específica', + 'Time-based One-time Password Algorithm' => 'Senha de uso único baseado no tempo', + 'Two-Factor Provider: ' => 'Provedor de autenticação a dois fatores: ', + 'Disable two-factor authentication' => 'Desativar a autenticação a dois fatores', + 'Enable two-factor authentication' => 'Ativar a autenticação a dois fatores', + 'There is no integration registered at the moment.' => 'Não há nenhuma integração registrada por enquanto.', + 'Password Reset for Kanboard' => 'Redefinir a senha para Kanboard', + 'Forgot password?' => 'Esqueceu a senha?', + 'Enable "Forget Password"' => 'Ativar a funcionalidade "Esqueceu a senha"', + 'Password Reset' => 'Redefinir a senha', + 'New password' => 'Nova senha', + 'Change Password' => 'Alterar a senha', + 'To reset your password click on this link:' => 'Para redefinir a sua senha, clique neste link:', + 'Last Password Reset' => 'Última redefinição de senha', + 'The password has never been reinitialized.' => 'A senha nunca foi redefinida.', + 'Creation' => 'Criação', + 'Expiration' => 'Expiração', + 'Password reset history' => 'Histórico da redefinição da senha', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Todas as tarefas da coluna "%s" e da raia "%s" foram finalizadas com sucesso.', + 'Do you really want to close all tasks of this column?' => 'Você realmente deseja finalizar todas as tarefas desta coluna?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d tarefa(s) da coluna "%s" e da raia "%s" serão finalizadas.', + 'Close all tasks in this column and this swimlane' => 'Finalizar todas as tarefas desta coluna', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Nenhum plugin registrou método de notificação de projeto. Você ainda pode definir notificações individuais em seu perfil de usuário.', + 'My dashboard' => 'Meu Painel de Controle', + 'My profile' => 'Meu perfil', + 'Project owner: ' => 'Líder do projeto: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'O identificador do projeto é opcional e deve ser alfanumérico, por exemplo MEUPROJETO.', + 'Project owner' => 'Líder do projeto', + 'Personal projects do not have users and groups management.' => 'Projetos pessoais não têm gestão de usuários e grupos.', + 'There is no project member.' => 'Não há nenhum membro do projeto.', + 'Priority' => 'Prioridade', + 'Task priority' => 'Prioridade das tarefas', + 'General' => 'Geral', + 'Dates' => 'Datas', + 'Default priority' => 'Prioridade padrão', + 'Lowest priority' => 'Prioridade baixa', + 'Highest priority' => 'Prioridade alta', + 'Close a task when there is no activity' => 'Finalizar uma tarefa sem atividade', + 'Duration in days' => 'Duração em dias', + 'Send email when there is no activity on a task' => 'Enviar um e-mail quando não há nenhuma atividade em uma tarefa', + 'Unable to fetch link information.' => 'Não foi possível obter informações sobre o link.', + 'Daily background job for tasks' => 'Tarefa agendada diariamente para as tarefas', + 'Auto' => 'Auto', + 'Related' => 'Relacionado', + 'Attachment' => 'Anexo', + 'Web Link' => 'Link web', + 'External links' => 'Links externos', + 'Add external link' => 'Adicionar um link externo', + 'Type' => 'Tipo', + 'Dependency' => 'Dependência', + 'Add internal link' => 'Adicionar um link interno', + 'Add a new external link' => 'Adicionar um novo link externo', + 'Edit external link' => 'Editar um link externo', + 'External link' => 'Link externo', + 'Copy and paste your link here...' => 'Copie e cole o link aqui...', + 'URL' => 'URL', + 'Internal links' => 'Link interno', + 'Assign to me' => 'Atribuir-me', + 'Me' => 'Eu', + 'Do not duplicate anything' => 'Não duplique nada', + 'Projects management' => 'Gestão de projetos', + 'Users management' => 'Gestão dos usuários', + 'Groups management' => 'Gestão dos grupos', + 'Create from another project' => 'Criar a partir de outro projeto', + 'open' => 'aberto', + 'closed' => 'finalizado', + 'Priority:' => 'Prioridade:', + 'Reference:' => 'Referência:', + 'Complexity:' => 'Complexidade:', + 'Swimlane:' => 'Raia:', + 'Column:' => 'Coluna:', + 'Position:' => 'Posição:', + 'Creator:' => 'Criador:', + 'Time estimated:' => 'Tempo estimado:', + '%s hours' => '%s horas', + 'Time spent:' => 'Tempo gasto:', + 'Created:' => 'Criado:', + 'Modified:' => 'Modificado:', + 'Completed:' => 'Finalizado:', + 'Started:' => 'Começado:', + 'Moved:' => 'Movido:', + 'Task #%d' => 'Tarefa #%d', + 'Time format' => 'Formato da hora', + 'Start date: ' => 'Data de início: ', + 'End date: ' => 'Data final: ', + 'New due date: ' => 'Nova data fim estimada: ', + 'Start date changed: ' => 'Data de início alterada: ', + 'Disable personal projects' => 'Desativar projetos pessoais', + 'Do you really want to remove this custom filter: "%s"?' => 'Você realmente quer remover este filtro personalizado: "%s"?', + 'Remove a custom filter' => 'Remover um filtro personalizado', + 'User activated successfully.' => 'Usuário ativado com sucesso.', + 'Unable to enable this user.' => 'Impossível de ativar esse usuário.', + 'User disabled successfully.' => 'Usuário desactivado com sucesso.', + 'Unable to disable this user.' => 'Impossível de desativar esse usuário.', + 'All files have been uploaded successfully.' => 'Todos os arquivos foram enviados com sucesso.', + 'The maximum allowed file size is %sB.' => 'O tamanho máximo dos arquivos é %sB.', + 'Drag and drop your files here' => 'Arraste e solte os arquivos aqui', + 'choose files' => 'selecione os arquivos', + 'View profile' => 'Ver o perfil', + 'Two Factor' => 'Dois fatores', + 'Disable user' => 'Desativar o usuário', + 'Do you really want to disable this user: "%s"?' => 'Você realmente quer desativar este usuário: "%s"?', + 'Enable user' => 'Ativar um usuário', + 'Do you really want to enable this user: "%s"?' => 'Você realmente quer ativar este usuário: "%s"?', + 'Download' => 'Baixar', + 'Uploaded: %s' => 'Enviado: %s', + 'Size: %s' => 'Tamanho: %s', + 'Uploaded by %s' => 'Enviado por %s', + 'Filename' => 'Nome do arquivo', + 'Size' => 'Tamanho', + 'Column created successfully.' => 'A coluna criada com sucesso.', + 'Another column with the same name exists in the project' => 'Uma outra coluna com o mesmo nome já existe no projeto', + 'Default filters' => 'Filtros padrão', + 'Your board doesn\'t have any columns!' => 'O seu quadro não tem nenhuma coluna', + 'Change column position' => 'Alterar a posição da coluna', + 'Switch to the project overview' => 'Mudar para a vista geral do projeto', + 'User filters' => 'Filtros dos usuários', + 'Category filters' => 'Filtros das categorias', + 'Upload a file' => 'Enviar um arquivo', + 'View file' => 'Ver arquivo', + 'Last activity' => 'Últimas atividades', + 'Change subtask position' => 'Alterar a posição da subtarefa', + 'This value must be greater than %d' => 'Este valor deve ser maior que %d', + 'Another swimlane with the same name exists in the project' => 'Outra raia existe com o mesmo nome no projeto', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Exemplo: https://exemplo.kanboard.org/ (usado para gerar URLs absolutos)', + 'Actions duplicated successfully.' => 'Ações duplicadas com sucesso.', + 'Unable to duplicate actions.' => 'Não foi possível duplicar as ações.', + 'Add a new action' => 'Adicionar uma nova ação', + 'Import from another project' => 'Importar a partir de outro projeto', + 'There is no action at the moment.' => 'Não há nenhuma ação atualmente.', + 'Import actions from another project' => 'Importar ações a partir de outro projeto', + 'There is no available project.' => 'Não há projetos disponíveis.', + 'Local File' => 'Arquivo local', + 'Configuration' => 'Configuração', + 'PHP version:' => 'Versão do PHP:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'Versão do sistema operacional:', + 'Database version:' => 'Versão do banco de dados:', + 'Browser:' => 'Browser:', + 'Task view' => 'Vista detalhada de uma tarefa', + 'Edit task' => 'Editar a tarefa', + 'Edit description' => 'Editar a descrição', + 'New internal link' => 'Novo link interno', + 'Display list of keyboard shortcuts' => 'Ver a lista dos atalhos de teclado', + 'Avatar' => 'Avatar', + 'Upload my avatar image' => 'Enviar a minha imagem de avatar', + 'Remove my image' => 'Remover a minha imagem', + 'The OAuth2 state parameter is invalid' => 'O parâmetro "state" de OAuth2 não é válido', + 'User not found.' => 'Usuário não encontrado.', + 'Search in activity stream' => 'Procurar no fluxo de atividade', + 'My activities' => 'Minhas atividades', + 'Activity until yesterday' => 'Atividade até ontem', + 'Activity until today' => 'Atividade até hoje', + 'Search by creator: ' => 'Pesquisar por criador: ', + 'Search by creation date: ' => 'Pesquisar por data de criação: ', + 'Search by task status: ' => 'Pesquisar por estado da tarefa: ', + 'Search by task title: ' => 'Pesquisar por título da tarefa: ', + 'Activity stream search' => 'Pesquisa do fluxo de atividade', + 'Projects where "%s" is manager' => 'Projetos onde "%s" é gestor', + 'Projects where "%s" is member' => 'Projetos onde "%s" é membro', + 'Open tasks assigned to "%s"' => 'Tarefas abertas atribuídas a "%s"', + 'Closed tasks assigned to "%s"' => 'Tarefas fechadas atribuídas a "%s"', + 'Assign automatically a color based on a priority' => 'Atribuir automaticamente uma cor de acordo com a prioridade', + 'Overdue tasks for the project(s) "%s"' => 'Tarefas em atraso para o(s) projeto(s) "%s"', + 'Upload files' => 'Enviar arquivos', + 'Installed Plugins' => 'Plugins Instalados', + 'Plugin Directory' => 'Pasta de Plugins', + 'Plugin installed successfully.' => 'Plugin instalado com sucesso.', + 'Plugin updated successfully.' => 'Plugin atualizado com sucesso.', + 'Plugin removed successfully.' => 'Plugin removido com sucesso.', + 'Subtask converted to task successfully.' => 'Sub-tarefa convertida para tarefa com sucesso.', + 'Unable to convert the subtask.' => 'Não foi possível converter a subtarefa.', + 'Unable to extract plugin archive.' => 'Não foi possível extrair o arquivo do plugin.', + 'Plugin not found.' => 'Plugin não encontrado.', + 'You don\'t have the permission to remove this plugin.' => 'Você não tem permissão para remover este plugin.', + 'Unable to download plugin archive.' => 'Não foi possível transferir o arquivo do plugin.', + 'Unable to write temporary file for plugin.' => 'Não foi possível escrever o arquivo temporário para o plugin.', + 'Unable to open plugin archive.' => 'Não foi possível abrir o arquivo do plugin.', + 'There is no file in the plugin archive.' => 'Ficheiro não encontrado dentro do arquivo do plugin.', + 'Create tasks in bulk' => 'Criar tarefas em massa', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Este Kanboard não está configurado para instalar plugins através da interface de usuário.', + 'There is no plugin available.' => 'Não existe nenhum plugin instalado.', + 'Install' => 'Instalar', + 'Update' => 'Atualizar', + 'Up to date' => 'Atualizado', + 'Not available' => 'Não disponível', + 'Remove plugin' => 'Remover plugin', + 'Do you really want to remove this plugin: "%s"?' => 'Tem a certeza que quer remover este plugin: "%s"?', + 'Uninstall' => 'Desinstalar', + 'Listing' => 'Listando', + 'Metadata' => 'Metadata', + 'Manage projects' => 'Gerir projetos', + 'Convert to task' => 'Converter para tarefa', + 'Convert sub-task to task' => 'Converter subtarefa para tarefa', + 'Do you really want to convert this sub-task to a task?' => 'Tem a certeza que pretende converter esta subtarefa para tarefa?', + 'My task title' => 'Título da minha tarefa', + 'Enter one task by line.' => 'Escreva uma tarefa por linha.', + 'Number of failed login:' => 'Número de logins falhados:', + 'Account locked until:' => 'Conta bloqueada até:', + 'Email settings' => 'Configurações de e-mail', + 'Email sender address' => 'Endereço de envio de e-mail', + 'Email transport' => 'Transportador de e-mail', + 'Webhook token' => 'Token do Webhook', + 'Project tags management' => 'Gestão de etiquetas do Projeto', + 'Tag created successfully.' => 'Etiqueta criada com sucesso.', + 'Unable to create this tag.' => 'Não foi possível criar esta etiqueta.', + 'Tag updated successfully.' => 'Etiqueta atualizada com sucesso.', + 'Unable to update this tag.' => 'Não foi possível atualizar esta etiqueta.', + 'Tag removed successfully.' => 'Etiqueta removida com sucesso.', + 'Unable to remove this tag.' => 'Não foi possível remover esta etiqueta.', + 'Global tags management' => 'Gestão de etiquetas globais', + 'Tags' => 'Etiquetas', + 'Tags management' => 'Gestão de etiquetas', + 'Add new tag' => 'Adicionar etiqueta nova', + 'Edit a tag' => 'Editar a etiqueta', + 'Project tags' => 'Etiquetas do Projeto', + 'There is no specific tag for this project at the moment.' => 'Atualmente não existe nenhuma etiqueta para este projeto.', + 'Tag' => 'Etiqueta', + 'Remove a tag' => 'Remover etiqueta', + 'Do you really want to remove this tag: "%s"?' => 'Tem certeza que pretende remover esta etiqueta: "%s"?', + 'Global tags' => 'Etiquetas globais', + 'There is no global tag at the moment.' => 'Atualmente não existe nenhuma etiqueta global.', + 'This field cannot be empty' => 'Este campo não pode ficar vazio', + 'Close a task when there is no activity in a specific column' => 'Fechar a tarefa quando não houver atividade numa coluna especifica', + '%s removed a subtask for the task #%d' => '%s removeu uma subtarefa da tarefa #%d', + '%s removed a comment on the task #%d' => '%s removeu um comentário da tarefa #%d ', + 'Comment removed on task #%d' => 'Comentário removido da tarefa #%d', + 'Subtask removed on task #%d' => 'Sub-tarefa removida da tarefa #%d', + 'Hide tasks in this column in the dashboard' => 'Esconder tarefas desta coluna no Painel de Controle', + '%s removed a comment on the task %s' => '%s removeu um comentário da tarefa %s', + '%s removed a subtask for the task %s' => '%s removeu uma subtarefa da tarefa %s', + 'Comment removed' => 'Comentário removido', + 'Subtask removed' => 'Sub-tarefa removida', + '%s set a new internal link for the task #%d' => '%s definiu uma nova ligação interna para a tarefa #%d', + '%s removed an internal link for the task #%d' => '%s removeu uma ligação interna da tarefa #%d', + 'A new internal link for the task #%d has been defined' => 'Uma nova ligação para a tarefa #%d foi definida', + 'Internal link removed for the task #%d' => 'Ligação interna removida da tarefa #%d', + '%s set a new internal link for the task %s' => '%s definiu uma nova ligação interna para a tarefa %s', + '%s removed an internal link for the task %s' => '%s removeu uma ligação interna da tarefa %s', + 'Automatically set the due date on task creation' => 'Definir data de vencimento automaticamente ao criar uma tarefa', + 'Move the task to another column when closed' => 'Mover a tarefa para outra coluna quando fechada', + 'Move the task to another column when not moved during a given period' => 'Mover a tarefa para outra coluna quando não movida dentro de determinado período', + 'Dashboard for %s' => 'Painel de Controle de %s', + 'Tasks overview for %s' => 'Visão geral das tarefas de %s', + 'Subtasks overview for %s' => 'Visão geral das subtarefas de %s', + 'Projects overview for %s' => 'Visão geral dos projetos de %s', + 'Activity stream for %s' => 'Fluxo de atividade de %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Atribuir uma cor quando a tarefa for movida para uma raia especifica', + 'Assign a priority when the task is moved to a specific swimlane' => 'Atribuir uma prioridade quando a tarefa for movida para uma raia especifica', + 'User unlocked successfully.' => 'Usuário desbloqueado com sucesso.', + 'Unable to unlock the user.' => 'Não foi possível desbloquear o usuário.', + 'Move a task to another swimlane' => 'Mover a tarefa para outra raia', + 'Creator Name' => 'Nome do Criador', + 'Time spent and estimated' => 'Tempo gasto e estimado', + 'Move position' => 'Mover posição', + 'Move task to another position on the board' => 'Mover tarefa para outra posição no quadro', + 'Insert before this task' => 'Inserir antes desta tarefa', + 'Insert after this task' => 'Inserir depois desta tarefa', + 'Unlock this user' => 'Desbloquear este usuário', + 'Custom Project Roles' => 'Funções Personalizadas do Projeto', + 'Add a new custom role' => 'Adicionar uma nova função personalizada', + 'Restrictions for the role "%s"' => 'Restrições para a função: "%s"', + 'Add a new project restriction' => 'Adicionar uma nova restrição de Projeto', + 'Add a new drag and drop restriction' => 'Adicionar uma nova restrição de arrastar e soltar', + 'Add a new column restriction' => 'Adicionar uma nova restrição de colunas', + 'Edit this role' => 'Editar esta função', + 'Remove this role' => 'Remover esta função', + 'There is no restriction for this role.' => 'Não existem restrições para esta função.', + 'Only moving task between those columns is permitted' => 'Só é permitido mover a tarefa entre as seguintes colunas', + 'Close a task in a specific column when not moved during a given period' => 'Fecha a tarefa numa coluna especifica quando não é movimentada durante um dado período', + 'Edit columns' => 'Editar colunas', + 'The column restriction has been created successfully.' => 'A restrição de coluna foi criada com sucesso.', + 'Unable to create this column restriction.' => 'Não foi possível criar esta restrição de coluna.', + 'Column restriction removed successfully.' => 'Restrição de coluna removida com sucesso.', + 'Unable to remove this restriction.' => 'Não foi possível remover esta restrição.', + 'Your custom project role has been created successfully.' => 'A sua função de projeto personalizada foi criada com sucesso.', + 'Unable to create custom project role.' => 'Não foi possível criar a função de projeto personalizada.', + 'Your custom project role has been updated successfully.' => 'A sua função de projeto personalizada foi atualizada com sucesso.', + 'Unable to update custom project role.' => 'Não foi possível atualizar a função de projeto personalizada.', + 'Custom project role removed successfully.' => 'Função de projeto removida com sucesso.', + 'Unable to remove this project role.' => 'Não foi possível remover esta função de projeto.', + 'The project restriction has been created successfully.' => 'A restrição de projeto foi criada com sucesso.', + 'Unable to create this project restriction.' => 'Não foi possível remover esta restrição de projeto.', + 'Project restriction removed successfully.' => 'Restrição de projeto removida com sucesso.', + 'You cannot create tasks in this column.' => 'Não pode criar tarefas nesta coluna.', + 'Task creation is permitted for this column' => 'A criação de tarefas é permitida nesta coluna', + 'Closing or opening a task is permitted for this column' => 'Fechar ou abrir tarefas é permitido nesta coluna', + 'Task creation is blocked for this column' => 'A criação de tarefas está bloqueada nesta coluna', + 'Closing or opening a task is blocked for this column' => 'Fechar ou abrir tarefas está bloqueado nesta coluna', + 'Task creation is not permitted' => 'A criação de tarefas não é permitida', + 'Closing or opening a task is not permitted' => 'Fechar ou abrir tarefas não é permitido', + 'New drag and drop restriction for the role "%s"' => 'Nova restrição de arrastar e soltar para a função: "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Pessoas pertecentes a esta função poderão mover apenas tarefas entre essas colunas de origem e de destino.', + 'Remove a column restriction' => 'Remover a restrição de coluna', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Tem a certeza que quer remover a restrição de coluna: "%s" para "%s"?', + 'New column restriction for the role "%s"' => 'Nova restrição de coluna para a função: "%s"', + 'Rule' => 'Regra', + 'Do you really want to remove this column restriction?' => 'Tem a certeza que quer remover esta restrição de coluna?', + 'Custom roles' => 'Funções personalizadas', + 'New custom project role' => 'Nova função de projeto personalizada', + 'Edit custom project role' => 'Editar função de projeto personalizada', + 'Remove a custom role' => 'Remover função de projeto personalizada', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Tem certeza que quer remover esta função personalizada: "%s"? Todas as pessoas atribuídas a esta função ficarão membros do projeto.', + 'There is no custom role for this project.' => 'Não existem funções personalizadas para este projeto', + 'New project restriction for the role "%s"' => 'Nova restrição de projeto para a função: "%s"', + 'Restriction' => 'Restrição', + 'Remove a project restriction' => 'Remover uma restrição de projeto', + 'Do you really want to remove this project restriction: "%s"?' => 'Tem a certeza que quer remover a restrição de projeto: "%s"?', + 'Duplicate to multiple projects' => 'Duplicar para vários projetos', + 'This field is required' => 'Este campo é obrigatório', + 'Moving a task is not permitted' => 'Mover uma tarefa não é permitido', + 'This value must be in the range %d to %d' => 'Este valor precisa estar no intervalo %d até %d', + 'You are not allowed to move this task.' => 'Você não está autorizado a mover esta tarefa.', + 'API User Access' => 'Acesso à API do usuário', + 'Preview' => 'Pré-visualizar', + 'Write' => 'Escrever', + 'Write your text in Markdown' => 'Escreva seu texto em Markdown', + 'No personal API access token registered.' => 'Nenhum token de acesso pessoal à API registrado.', + 'Your personal API access token is "%s"' => 'Seu token de acesso pessoal à API é "%s"', + 'Remove your token' => 'Remover seu token', + 'Generate a new token' => 'Gerar um novo token', + 'Showing %d-%d of %d' => 'Mostrando %d-%d de %d', + 'Outgoing Emails' => 'E-mails de saída', + 'Add or change currency rate' => 'Adicionar ou alterar taxa de câmbio', + 'Reference currency: %s' => 'Câmbio de referência: %s', + 'Add custom filters' => 'Adicionar filtros personalizados', + 'Export' => 'Exportar', + 'Add link label' => 'Adicionar rótulo de link', + 'Incompatible Plugins' => 'Plugin incompatível', + 'Compatibility' => 'Compatibilidade', + 'Permissions and ownership' => 'Permissões e propriedade', + 'Priorities' => 'Propriedades', + 'Close this window' => 'Fechar esta janela', + 'Unable to upload this file.' => 'Não é possível carregar este arquivo', + 'Import tasks' => 'Importar tarefas', + 'Choose a project' => 'Escolher um projeto', + 'Profile' => 'Perfil', + 'Application role' => 'Regra de aplicação', + '%d invitations were sent.' => '%d convites foram enviados.', + '%d invitation was sent.' => '%d convite foi enviado.', + 'Unable to create this user.' => 'Não foi possível criar este usuário.', + 'Kanboard Invitation' => 'Convite do Kanboard', + 'Visible on dashboard' => 'Visível no painel de controle', + 'Created at:' => 'Criado em:', + 'Updated at:' => 'Atualizado em:', + 'There is no custom filter.' => 'Não existe nenhum filtro personalizado.', + 'New User' => 'Novo Usuário', + 'Authentication' => 'Autenticação', + 'If checked, this user will use a third-party system for authentication.' => 'Se marcado, este usuário usará um sistema de autenticação de terceiros.', + 'The password is necessary only for local users.' => 'A senha é necessária somente para usuários locais.', + 'You have been invited to register on Kanboard.' => 'Você foi convidado a se registrar no Kanboard', + 'Click here to join your team' => 'Clique aqui para se unir ao seu time', + 'Invite people' => 'Convidar pessoas', + 'Emails' => 'E-mails', + 'Enter one email address by line.' => 'Digite um e-mail por linha. ', + 'Add these people to this project' => 'Adicionar estas pessoas para este projeto', + 'Add this person to this project' => 'Adicionar esta pessoa para este projeto', + 'Sign-up' => 'Inscrever-se', + 'Credentials' => 'Credenciais', + 'New user' => 'Novo usuário', + 'This username is already taken' => 'Este nome de usuário já está em uso', + 'Your profile must have a valid email address.' => 'Seu perfil precisa ter um endereço de e-mail válido.', + 'TRL - Turkish Lira' => 'TRL - Lira turca', + 'The project email is optional and could be used by several plugins.' => 'O e-mail do projeto é opcional e pode ser usado por diversos plugins.', + 'The project email must be unique across all projects' => 'O e-mail do projeto deve ser exclusivo em relação aos demais os projetos', + 'The email configuration has been disabled by the administrator.' => 'A configuração de e-mail foi desabilitada pelo administrador.', + 'Close this project' => 'Fechar este projeto', + 'Open this project' => 'Abrir este projeto', + 'Close a project' => 'Fechar um projeto', + 'Do you really want to close this project: "%s"?' => 'Deseja realmente fechar este projeto: "%s"?', + 'Reopen a project' => 'Reabrir um projeto', + 'Do you really want to reopen this project: "%s"?' => 'Deseja realmente reabrir este projeto: "%s"?', + 'This project is open' => 'Este projeto está aberto', + 'This project is closed' => 'Este projeto está fechado', + 'Unable to upload files, check the permissions of your data folder.' => 'Incapaz de enviar arquivos. Verifique as permissões da sua pasta de dados.', + 'Another category with the same name exists in this project' => 'Outra categoria com o mesmo nome existe neste projeto', + 'Comment sent by email successfully.' => 'Comentário enviado por e-mail com sucesso.', + 'Sent by email to "%s" (%s)' => 'Enviar por e-mail para "%s" (%s)', + 'Unable to read uploaded file.' => 'Incapaz de ler arquivos enviados.', + 'Database uploaded successfully.' => 'Base de dados enviado com sucesso.', + 'Task sent by email successfully.' => 'Tarefa enviada por e-mail com sucesso.', + 'There is no category in this project.' => 'Não há categoria neste projeto.', + 'Send by email' => 'Enviar por e-mail', + 'Create and send a comment by email' => 'Criar e enviar um comentário por e-mail', + 'Subject' => 'Assunto', + 'Upload the database' => 'Enviar uma base de dados', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Você pode enviar uma base de dados Sqlite baixada anteriormente (formato Gzip).', + 'Database file' => 'Arquivo de base de dados', + 'Upload' => 'Enviar', + 'Your project must have at least one active swimlane.' => 'Seu projeto precisa ter pelo menos uma raia ativa.', + 'Project: %s' => 'Projeto: %s', + 'Automatic action not found: "%s"' => 'Ação automática não encontrada: "%s"', + '%d projects' => '%d projetos', + '%d project' => '%d projeto', + 'There is no project.' => 'Não há projeto.', + 'Sort' => 'Ordenar', + 'Project ID' => 'ID do projeto', + 'Project name' => 'Nome do projeto', + 'Public' => 'Público', + 'Personal' => 'Privado', + '%d tasks' => '%d tarefas', + '%d task' => '%d tarefa', + 'Task ID' => 'ID da tarefa', + 'Assign automatically a color when due date is expired' => 'Atribuir automaticamente uma cor quando a data de vencimento está expirada', + 'Total score in this column across all swimlanes' => 'Pontuação total nesta coluna através de todas as raias', + 'HRK - Kuna' => 'HRK - Kuna', + 'ARS - Argentine Peso' => 'ARS - Peso argentino', + 'COP - Colombian Peso' => 'COP - Peso colombiano', + '%d groups' => '%d grupos', + '%d group' => '%d grupo', + 'Group ID' => 'ID do grupo', + 'External ID' => 'ID externo', + '%d users' => '%d usuários', + '%d user' => '%d usuário', + 'Hide subtasks' => 'Esconder subtarefa', + 'Show subtasks' => 'Mostrar subtarefa', + 'Authentication Parameters' => 'Parâmetros de autenticação', + 'API Access' => 'Acesso à API', + 'No users found.' => 'Usuários não encontrados.', + 'User ID' => 'ID do usuário', + 'Notifications are activated' => 'As notificações estão ativadas', + 'Notifications are disabled' => 'As notificações estão desativadas', + 'User disabled' => 'Usuário desativado', + '%d notifications' => '%d notificações', + '%d notification' => '%d notificação', + 'There is no external integration installed.' => 'Não existe integração externa instalada.', + 'You are not allowed to update tasks assigned to someone else.' => 'Você não está autorizado a atualizar tarefas designadas para outros.', + 'You are not allowed to change the assignee.' => 'Você não está autorizado a mudar a designação.', + 'Task suppression is not permitted' => 'Supressão de tarefa não é permitida', + 'Changing assignee is not permitted' => 'Mudança de designação não é permitida', + 'Update only assigned tasks is permitted' => 'Atualizar somente tarefas designadas é permitida', + 'Only for tasks assigned to the current user' => 'Somente para tarefas designadas para o usuário atual', + 'My projects' => 'Meus projetos', + 'You are not a member of any project.' => 'Você não é membro de nenhum projeto.', + 'My subtasks' => 'Minhas subtarefas', + '%d subtasks' => '%d subtarefas', + '%d subtask' => '%d subtarefa', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Somente movimentação de tarefas entre aquelas colunas são permitidas para tarefas designadas para o usuário atual', + '[DUPLICATE]' => '[DUPLICADO]', + 'DKK - Danish Krona' => 'DKK - Coroa Dinamarquesa', + 'Remove user from group' => 'Remover usuário do grupo', + 'Assign the task to its creator' => 'Atribuir a tarefa ao seu criador', + 'This task was sent by email to "%s" with subject "%s".' => 'Esta tarefa foi enviada por e-mail para "%s" com o assunto "%s".', + 'Predefined Email Subjects' => 'Assuntos predefinidos de e-mail', + 'Write one subject by line.' => 'Escreva um assunto por linha.', + 'Create another link' => 'Criar outro link', + 'BRL - Brazilian Real' => 'BRL - Real Brasileiro', + 'Add a new Kanboard task' => 'Adicionar uma nova tarefa do Kanboard', + 'Subtask not started' => 'Subtarefa não iniciada', + 'Subtask currently in progress' => 'Subtarefa atualmente em progresso', + 'Subtask completed' => 'Subtarefa finalizada', + 'Subtask added successfully.' => 'Subtarefa adicionada com sucesso.', + '%d subtasks added successfully.' => '%d subtarefas adicionadas com sucesso.', + 'Enter one subtask by line.' => 'Escreva uma subtarefa por linha.', + 'Predefined Contents' => 'Conteúdos predefinidos', + 'Predefined contents' => 'Conteúdos predefinidos', + 'Predefined Task Description' => 'Descrição de tarefa pré-definida', + 'Do you really want to remove this template? "%s"' => 'Deseja realmente remover este modelo? "%s"', + 'Add predefined task description' => 'Adicionar descrição de tarefa pré-definida', + 'Predefined Task Descriptions' => 'Descrições de tarefas pré-definidas', + 'Template created successfully.' => 'Modelo criado com sucesso.', + 'Unable to create this template.' => 'Incapaz de criar este modelo.', + 'Template updated successfully.' => 'Modelo atualizado com sucesso.', + 'Unable to update this template.' => 'Incapaz de atualizar este modelo.', + 'Template removed successfully.' => 'Modelo removido com sucesso.', + 'Unable to remove this template.' => 'Incapaz de remover este modelo.', + 'Template for the task description' => 'Modelo para a descrição de tarefa', + 'The start date is greater than the end date' => 'A data de início é maior que a data final', + 'Tags must be separated by a comma' => 'As tags precisam ser separadas por vírgula', + 'Only the task title is required' => 'Somente o título da tarefa é requerida', + 'Creator Username' => 'Usuário criador', + 'Color Name' => 'Nome da cor', + 'Column Name' => 'Nome da coluna', + 'Swimlane Name' => 'Nome da raia', + 'Time Estimated' => 'Tempo estimado', + 'Time Spent' => 'Tempo gasto', + 'External Link' => 'Link externo', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Essa funcionalidade habilita o alimentador de iCal, RSS e visualização pública do quadro.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Parar o temporizador de todas as subtarefas quando mover a tarefa para outra coluna', + 'Subtask Title' => 'Título da subtarefa', + 'Add a subtask and activate the timer when moving a task to another column' => 'Adicionar uma subtarefa e ativar o temporizador quando mover a tarefa para outra coluna', + 'days' => 'dias', + 'minutes' => 'minutos', + 'seconds' => 'segundos', + 'Assign automatically a color when preset start date is reached' => 'Atribuir automaticamente uma cor quando a data de início for atingida', + 'Move the task to another column once a predefined start date is reached' => 'Mover a tarefa para outra coluna quando a data de início for atingida', + 'This task is now linked to the task %s with the relation "%s"' => 'Esta tarefa agora está vinculada à tarefa %s com a relação "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'O vínculo com a relação "%s" para a tarefa %s foi removido', + 'Custom Filter:' => 'Filtro personalizado:', + 'Unable to find this group.' => 'Incapaz de encontrar este grupo.', + '%s moved the task #%d to the column "%s"' => '%s moveu a tarefa #%d para a coluna "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => '%s moveu a tarefa #%d para a posição %d na coluna "%s"', + '%s moved the task #%d to the swimlane "%s"' => '%s moveu a tarefa #%d para a raia "%s"', + '%sh spent' => '%sh gasto', + '%sh estimated' => '%sh estimado', + 'Select All' => 'Selecionar todos', + 'Unselect All' => 'Desmarcar todos', + 'Apply action' => 'Aplicar ação', + 'Move selected tasks to another column or swimlane' => 'Mover tarefas selecionadas para outra coluna', + 'Edit tasks in bulk' => 'Editar tarefas em massa', + 'Choose the properties that you would like to change for the selected tasks.' => 'Escolher as propriedades que deseja mudar para as tarefas selecionadas.', + 'Configure this project' => 'Configurar este projeto', + 'Start now' => 'Iniciar agora', + '%s removed a file from the task #%d' => '%s removeu um arquivo da tarefa #%d', + 'Attachment removed from task #%d: %s' => 'Anexo removido da tarefa #%d: %s', + 'No color' => 'Nenhuma cor', + 'Attachment removed "%s"' => 'Anexo removido "%s"', + '%s removed a file from the task %s' => '%s removeu um arquivo da tarefa %s', + 'Move the task to another swimlane when assigned to a user' => 'Mover a tarefa para uma outra raia quando esta está atribuída a um usuário', + 'Destination swimlane' => 'Raia de destino', + 'Assign a category when the task is moved to a specific swimlane' => 'Atribuir uma categoria quando a tarefa for movida para uma raia específica', + 'Move the task to another swimlane when the category is changed' => 'Mover a tarefa para outra raia quando a categoria é alterada', + 'Reorder this column by priority (ASC)' => 'Reordenar esta coluna por prioridade (crescente)', + 'Reorder this column by priority (DESC)' => 'Reordenar esta coluna por prioridade (decrescente)', + 'Reorder this column by assignee and priority (ASC)' => 'Reordenar esta coluna por designado e prioridade (crescente)', + 'Reorder this column by assignee and priority (DESC)' => 'Reordenar esta coluna por designado e prioridade (decrescente)', + 'Reorder this column by assignee (A-Z)' => 'Reordenar esta coluna por designado (A-Z)', + 'Reorder this column by assignee (Z-A)' => 'Reordenar esta coluna por designado (Z-A)', + 'Reorder this column by due date (ASC)' => 'Reordenar esta coluna por data fim estimada (crescente)', + 'Reorder this column by due date (DESC)' => 'Reordenar esta coluna por data fim estimada (decrescente)', + 'Reorder this column by id (ASC)' => 'Reordenar esta coluna por id (ASC)', + 'Reorder this column by id (DESC)' => 'Reordenar esta coluna por id (DESC)', + '%s moved the task #%d "%s" to the project "%s"' => '%s moveu a tarefa #%d "%s" para o projeto "%s"', + 'Task #%d "%s" has been moved to the project "%s"' => 'Tarefa #%d "%s" foi movida para o projeto "%s"', + 'Move the task to another column when the due date is less than a certain number of days' => 'Mover a tarefa para outra coluna quando a data fim estimada for menor que uma quantidade de dias', + 'Automatically update the start date when the task is moved away from a specific column' => 'Atualizar automaticamente a data de início quando a tarefa sair de determinada coluna', + 'HTTP Client:' => 'Cliente HTTP:', + 'Assigned' => 'Atribuído', + 'Task limits apply to each swimlane individually' => 'Limites de tarefas aplicam-se a cada raia individualmente', + 'Column task limits apply to each swimlane individually' => 'Limites de tarefas da coluna aplicam-se a cada raia individualmente', + 'Column task limits are applied to each swimlane individually' => 'Limites de tarefas da coluna estão aplicados a cada raia individualmente', + 'Column task limits are applied across swimlanes' => 'Limites de tarefas da coluna estão aplicados por todas as raias', + 'Task limit: ' => 'Limite de tarefas:', + 'Change to global tag' => 'Transformar em etiqueta global', + 'Do you really want to make the tag "%s" global?' => 'Você realmente deseja transformar a etiqueta "%s" em etiqueta global?', + 'Enable global tags for this project' => 'Habilitar etiquetas globais para este projeto', + 'Group membership(s):' => 'Filiação a grupo(s):', + '%s is a member of the following group(s): %s' => '%s é um membro do(s) seguinte(s) grupo(s): %s', + '%d/%d group(s) shown' => '%d/%d grupo(s) exibidos', + 'Subtask creation or modification' => 'Criação ou edição de subtarefas', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Atribuir a tarefa a um usuário específico quando a tarefa for movida para uma raia especifica', + 'Comment' => 'Comentário', + 'Collapse vertically' => 'Contrair verticalmente', + 'Expand vertically' => 'Expandir verticalmente', + 'MXN - Mexican Peso' => 'MXN - Peso mexicano', + 'Estimated vs actual time per column' => 'Tempo estimado vs real por coluna', + 'HUF - Hungarian Forint' => 'HUF - Forint húngaro', + 'XBT - Bitcoin' => 'XBT - Bitcoin', + 'You must select a file to upload as your avatar!' => 'Você deve selecionar um arquivo para enviar como seu avatar!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'O arquivo enviado não é uma imagem válida! (Apenas *.gif, *.jpg, *.jpeg e *.png são permitidos!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Definir automaticamente a data de vencimento quando a tarefa for movida de uma coluna específica', + 'No other projects found.' => 'Nenhum outro projeto encontrado.', + 'Tasks copied successfully.' => 'Tarefas copiadas com sucesso.', + 'Unable to copy tasks.' => 'Não foi possível copiar as tarefas.', + 'Theme' => 'Tema', + 'Theme:' => 'Tema:', + 'Light theme' => 'Tema claro', + 'Dark theme' => 'Tema escuro', + 'Automatic theme - Sync with system' => 'Tema automático - Sincronizar com o sistema', + 'Application managers or more' => 'Gerentes de aplicação ou mais', + 'Administrators' => 'Administradores', + 'Visibility:' => 'Visibilidade:', + 'Standard users' => 'Usuários padrão', + 'Visibility is required' => 'A visibilidade é obrigatória', + 'The visibility should be an app role' => 'A visibilidade deve ser um papel do aplicativo', + 'Reply' => 'Responder', + '%s wrote: ' => '%s escreveu: ', + 'Number of visible tasks in this column and swimlane' => 'Número de tarefas visíveis nesta coluna e raia', + 'Number of tasks in this swimlane' => 'Número de tarefas nesta raia', + 'Unable to find another subtask in progress, you can close this window.' => 'Não foi possível encontrar outra subtarefa em andamento, você pode fechar esta janela.', + 'This theme is invalid' => 'Este tema é inválido', + 'This role is invalid' => 'Este papel é inválido', + 'This timezone is invalid' => 'Este fuso horário é inválido', + 'This language is invalid' => 'Este idioma é inválido', + 'This URL is invalid' => 'Esta URL é inválida', + 'Date format invalid' => 'Formato de data inválido', + 'Time format invalid' => 'Formato de hora inválido', + 'Invalid Mail transport' => 'Transporte de e-mail inválido', + 'Color invalid' => 'Cor inválida', + 'This value must be greater or equal to %d' => 'Este valor deve ser maior ou igual a %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Adicione um BOM no início do arquivo (necessário para o Microsoft Excel)', + 'Just add these tag(s)' => 'Adicione apenas estas etiquetas', + 'Remove internal link(s)' => 'Remova os links internos', + 'Import tasks from another project' => 'Importe tarefas de outro projeto', + 'Select the project to copy tasks from' => 'Selecione o projeto do qual deseja copiar tarefas', + 'The total maximum allowed attachments size is %sB.' => 'O tamanho máximo total permitido para anexos é %sB.', + 'Add attachments' => 'Adicionar anexos', + 'Task #%d "%s" is overdue' => 'Tarefa #%d "%s" está atrasada', + 'Enable notifications by default for all new users' => 'Habilitar notificações por padrão para todos os novos usuários', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Atribuir a tarefa ao seu criador para colunas específicas se nenhum responsável for definido manualmente', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Atribuir a tarefa ao usuário logado ao mudar de coluna para a coluna especificada se nenhum usuário estiver atribuído', +]; diff --git a/app/Locale/pt_PT/translations.php b/app/Locale/pt_PT/translations.php new file mode 100644 index 0000000..3cb955c --- /dev/null +++ b/app/Locale/pt_PT/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => ' ', + 'None' => 'Nenhum', + 'Edit' => 'Editar', + 'Remove' => 'Remover', + 'Yes' => 'Sim', + 'No' => 'Não', + 'cancel' => 'cancelar', + 'or' => 'ou', + 'Yellow' => 'Amarelo', + 'Blue' => 'Azul', + 'Green' => 'Verde', + 'Purple' => 'Roxo', + 'Red' => 'Vermelho', + 'Orange' => 'Laranja', + 'Grey' => 'Cinza', + 'Brown' => 'Castanho', + 'Deep Orange' => 'Laranja escuro', + 'Dark Grey' => 'Cinza escuro', + 'Pink' => 'Rosa', + 'Teal' => 'Turquesa', + 'Cyan' => 'Azul intenso', + 'Lime' => 'Verde limão', + 'Light Green' => 'Verde claro', + 'Amber' => 'Âmbar', + 'Save' => 'Guardar', + 'Login' => 'Login', + 'Official website:' => 'Site oficial:', + 'Unassigned' => 'Não Atribuída', + 'View this task' => 'Ver esta tarefa', + 'Remove user' => 'Remover utilizador', + 'Do you really want to remove this user: "%s"?' => 'Pretende mesmo remover este utilizador: "%s"?', + 'All users' => 'Todos os utilizadores', + 'Username' => 'Nome de utilizador', + 'Password' => 'Senha', + 'Administrator' => 'Administrador', + 'Sign in' => 'Entrar', + 'Users' => 'Utilizadores', + 'Forbidden' => 'Proibido', + 'Access Forbidden' => 'Acesso negado', + 'Edit user' => 'Editar utilizador', + 'Logout' => 'Sair', + 'Bad username or password' => 'Utilizador ou senha inválidos', + 'Edit project' => 'Editar projeto', + 'Name' => 'Nome', + 'Projects' => 'Projetos', + 'No project' => 'Nenhum projeto', + 'Project' => 'Projeto', + 'Status' => 'Estado', + 'Tasks' => 'Tarefas', + 'Board' => 'Quadro', + 'Actions' => 'Acções', + 'Inactive' => 'Inactivo', + 'Active' => 'Activo', + 'Unable to update this board.' => 'Não foi possível actualizar este quadro.', + 'Disable' => 'Desactivar', + 'Enable' => 'Activar', + 'New project' => 'Novo projeto', + 'Do you really want to remove this project: "%s"?' => 'Tem a certeza que quer remover este projeto: "%s" ?', + 'Remove project' => 'Remover projeto', + 'Edit the board for "%s"' => 'Editar o quadro para "%s"', + 'Add a new column' => 'Adicionar uma nova coluna', + 'Title' => 'Título', + 'Assigned to %s' => 'Designado para %s', + 'Remove a column' => 'Remover uma coluna', + 'Unable to remove this column.' => 'Não foi possível remover esta coluna.', + 'Do you really want to remove this column: "%s"?' => 'Tem a certeza que quer remover esta coluna: "%s"?', + 'Settings' => 'Configurações', + 'Application settings' => 'Configurações da aplicação', + 'Language' => 'Idioma', + 'Webhook token:' => 'Token de webhooks:', + 'API token:' => 'API Token:', + 'Database size:' => 'Tamanho da base de dados:', + 'Download the database' => 'Download da base de dados', + 'Optimize the database' => 'Otimizar a base de dados', + '(VACUUM command)' => '(Comando VACUUM)', + '(Gzip compressed Sqlite file)' => '(Arquivo Sqlite comprimido com Gzip)', + 'Close a task' => 'Finalizar uma tarefa', + 'Column' => 'Coluna', + 'Color' => 'Cor', + 'Assignee' => 'Assignado', + 'Create another task' => 'Criar outra tarefa', + 'New task' => 'Nova tarefa', + 'Open a task' => 'Abrir uma tarefa', + 'Do you really want to open this task: "%s"?' => 'Tem a certeza que quer abrir esta tarefa: "%s"?', + 'Back to the board' => 'Voltar ao quadro', + 'There is nobody assigned' => 'Não há ninguém assignado', + 'Column on the board:' => 'Coluna no quadro:', + 'Close this task' => 'Finalizar esta tarefa', + 'Open this task' => 'Abrir esta tarefa', + 'There is no description.' => 'Não há descrição.', + 'Add a new task' => 'Adicionar uma nova tarefa', + 'The username is required' => 'O nome de utilizador é obrigatório', + 'The maximum length is %d characters' => 'O tamanho máximo é %d caracteres', + 'The minimum length is %d characters' => 'O tamanho mínimo é %d caracteres', + 'The password is required' => 'A senha é obrigatória', + 'This value must be an integer' => 'O valor deve ser um número inteiro', + 'The username must be unique' => 'O nome de utilizador deve ser único', + 'The user id is required' => 'O ID de utilizador é obrigatório', + 'Passwords don\'t match' => 'As senhas não coincidem', + 'The confirmation is required' => 'A confirmação é obrigatória', + 'The project is required' => 'O projeto é obrigatório', + 'The id is required' => 'O ID é obrigatório', + 'The project id is required' => 'O ID do projeto é obrigatório', + 'The project name is required' => 'O nome do projeto é obrigatório', + 'The title is required' => 'O título é obrigatório', + 'Settings saved successfully.' => 'Configurações guardadas com sucesso.', + 'Unable to save your settings.' => 'Não é possível guardar as suas configurações.', + 'Database optimization done.' => 'Otimização da base de dados finalizada.', + 'Your project has been created successfully.' => 'Projeto foi criado com sucesso.', + 'Unable to create your project.' => 'Não é possível criar o projeto.', + 'Project updated successfully.' => 'Projeto actualizado com sucesso.', + 'Unable to update this project.' => 'Não é possível actualizar este projeto.', + 'Unable to remove this project.' => 'Não é possível remover este projeto.', + 'Project removed successfully.' => 'Projeto removido com sucesso.', + 'Project activated successfully.' => 'Projeto activado com sucesso.', + 'Unable to activate this project.' => 'Não é possível activar este projeto.', + 'Project disabled successfully.' => 'Projeto desactivado com sucesso.', + 'Unable to disable this project.' => 'Não é possível desactivar este projeto.', + 'Unable to open this task.' => 'Não é possível abrir esta tarefa.', + 'Task opened successfully.' => 'Tarefa aberta com sucesso.', + 'Unable to close this task.' => 'Não é possível finalizar esta tarefa.', + 'Task closed successfully.' => 'Tarefa finalizada com sucesso.', + 'Unable to update your task.' => 'Não é possível actualizar a sua tarefa.', + 'Task updated successfully.' => 'Tarefa actualizada com sucesso.', + 'Unable to create your task.' => 'Não é possível criar a sua tarefa.', + 'Task created successfully.' => 'Tarefa criada com sucesso.', + 'User created successfully.' => 'Utilizador criado com sucesso.', + 'Unable to create your user.' => 'Não é possível criar o seu Utilizador.', + 'User updated successfully.' => 'Utilizador actualizado com sucesso.', + 'User removed successfully.' => 'Utilizador removido com sucesso.', + 'Unable to remove this user.' => 'Não é possível remover este Utilizador.', + 'Board updated successfully.' => 'Quadro actualizado com sucesso.', + 'Ready' => 'Pronto', + 'Backlog' => 'Backlog', + 'Work in progress' => 'Em andamento', + 'Done' => 'Finalizado', + 'Application version:' => 'Versão da aplicação:', + 'Id' => 'Id', + 'Public link' => 'Link público', + 'Timezone' => 'Fuso horário', + 'Sorry, I didn\'t find this information in my database!' => 'Desculpe, não encontrei esta informação na minha base de dados!', + 'Page not found' => 'Página não encontrada', + 'Complexity' => 'Complexidade', + 'Task limit' => 'Limite da tarefa', + 'Task count' => 'Número de tarefas', + 'User' => 'Utilizador', + 'Comments' => 'Comentários', + 'Comment is required' => 'Comentário é obrigatório', + 'Comment added successfully.' => 'Comentário adicionado com sucesso.', + 'Unable to create your comment.' => 'Não é possível criar o seu comentário.', + 'Due Date' => 'Data de vencimento', + 'Invalid date' => 'Data inválida', + 'Automatic actions' => 'Acções automáticas', + 'Your automatic action has been created successfully.' => 'A sua acção automática foi criada com sucesso.', + 'Unable to create your automatic action.' => 'Não é possível criar a sua acção automática.', + 'Remove an action' => 'Remover uma acção', + 'Unable to remove this action.' => 'Não é possível remover esta acção.', + 'Action removed successfully.' => 'Acção removida com sucesso.', + 'Automatic actions for the project "%s"' => 'Acções automáticas para o projeto "%s"', + 'Add an action' => 'Adicionar Acção', + 'Event name' => 'Nome do evento', + 'Action' => 'Acção', + 'Event' => 'Evento', + 'When the selected event occurs execute the corresponding action.' => 'Quando o evento selecionado ocorrer execute a acção correspondente.', + 'Next step' => 'Próximo passo', + 'Define action parameters' => 'Definir parêmetros da acção', + 'Do you really want to remove this action: "%s"?' => 'Tem a certeza que quer remover esta acção: "%s"?', + 'Remove an automatic action' => 'Remover uma acção automática', + 'Assign the task to a specific user' => 'Designar a tarefa para um utilizador específico', + 'Assign the task to the person who does the action' => 'Designar a tarefa para a pessoa que executa a acção', + 'Duplicate the task to another project' => 'Duplicar a tarefa para um outro projeto', + 'Move a task to another column' => 'Mover a tarefa para outra coluna', + 'Task modification' => 'Modificação de tarefa', + 'Task creation' => 'Criação de tarefa', + 'Closing a task' => 'A finalizar uma tarefa', + 'Assign a color to a specific user' => 'Designar uma cor para um utilizador específico', + 'Position' => 'Posição', + 'Duplicate to project' => 'Duplicar para outro projeto', + 'Duplicate' => 'Duplicar', + 'Link' => 'Link', + 'Comment updated successfully.' => 'Comentário actualizado com sucesso.', + 'Unable to update your comment.' => 'Não é possível actualizar o seu comentário.', + 'Remove a comment' => 'Remover um comentário', + 'Comment removed successfully.' => 'Comentário removido com sucesso.', + 'Unable to remove this comment.' => 'Não é possível remover este comentário.', + 'Do you really want to remove this comment?' => 'Tem a certeza que quer remover este comentário?', + 'Current password for the user "%s"' => 'Senha atual para o utilizador "%s"', + 'The current password is required' => 'A senha atual é obrigatória', + 'Wrong password' => 'Senha incorreta', + 'Unknown' => 'Desconhecido', + 'Last logins' => 'Últimos logins', + 'Login date' => 'Data de login', + 'Authentication method' => 'Método de autenticação', + 'IP address' => 'Endereço IP', + 'User agent' => 'User Agent', + 'Persistent connections' => 'Conexões persistentes', + 'No session.' => 'Nenhuma sessão.', + 'Expiration date' => 'Data de expiração', + 'Remember Me' => 'Lembre-se de mim', + 'Creation date' => 'Data de criação', + 'Everybody' => 'Todos', + 'Open' => 'Aberto', + 'Closed' => 'Finalizado', + 'Search' => 'Pesquisar', + 'Nothing found.' => 'Nada encontrado.', + 'Due date' => 'Data de vencimento', + 'Description' => 'Descrição', + '%d comments' => '%d comentários', + '%d comment' => '%d comentário', + 'Email address invalid' => 'Endereço de e-mail inválido', + 'Your external account is not linked anymore to your profile.' => 'A sua conta externa já não se encontra vinculada a este perfil', + 'Unable to unlink your external account.' => 'Não foi possivel desvincular a sua conta externa', + 'External authentication failed' => 'Autenticação externa falhou', + 'Your external account is linked to your profile successfully.' => 'A sua conta externa foi vinculada com sucesso ao seu perfil', + 'Email' => 'E-mail', + 'Task removed successfully.' => 'Tarefa removida com sucesso.', + 'Unable to remove this task.' => 'Não foi possível remover esta tarefa.', + 'Remove a task' => 'Remover uma tarefa', + 'Do you really want to remove this task: "%s"?' => 'Tem a certeza que quer remover esta tarefa: "%s"', + 'Assign automatically a color based on a category' => 'Atribuir automaticamente uma cor com base numa categoria', + 'Assign automatically a category based on a color' => 'Atribuir automaticamente uma categoria com base numa cor', + 'Task creation or modification' => 'Criação ou modificação de tarefa', + 'Category' => 'Categoria', + 'Category:' => 'Categoria:', + 'Categories' => 'Categorias', + 'Your category has been created successfully.' => 'A sua categoria foi criada com sucesso.', + 'This category has been updated successfully.' => 'A sua categoria foi actualizada com sucesso.', + 'Unable to update this category.' => 'Não foi possível actualizar a sua categoria.', + 'Remove a category' => 'Remover uma categoria', + 'Category removed successfully.' => 'Categoria removida com sucesso.', + 'Unable to remove this category.' => 'Não foi possível remover esta categoria.', + 'Category modification for the project "%s"' => 'Modificação de categoria para o projeto "%s"', + 'Category Name' => 'Nome da Categoria', + 'Add a new category' => 'Adicionar uma nova categoria', + 'Do you really want to remove this category: "%s"?' => 'Tem a certeza que quer remover esta categoria: "%s"', + 'All categories' => 'Todas as categorias', + 'No category' => 'Nenhuma categoria', + 'The name is required' => 'O nome é obrigatório', + 'Remove a file' => 'Remover um arquivo', + 'Unable to remove this file.' => 'Não foi possível remover este arquivo.', + 'File removed successfully.' => 'Arquivo removido com sucesso.', + 'Attach a document' => 'Anexar um documento', + 'Do you really want to remove this file: "%s"?' => 'Tem a certeza que quer remover este arquivo: "%s"', + 'Attachments' => 'Anexos', + 'Edit the task' => 'Editar a tarefa', + 'Add a comment' => 'Adicionar um comentário', + 'Edit a comment' => 'Editar um comentário', + 'Summary' => 'Resumo', + 'Time tracking' => 'Rastreamento de tempo', + 'Estimate:' => 'Estimado:', + 'Spent:' => 'Gasto:', + 'Do you really want to remove this sub-task?' => 'Tem a certeza que quer remover esta subtarefa?', + 'Remaining:' => 'Restante:', + 'hours' => 'horas', + 'estimated' => 'estimado', + 'Sub-Tasks' => 'Subtarefas', + 'Add a sub-task' => 'Adicionar uma subtarefa', + 'Original estimate' => 'Estimativa original', + 'Create another sub-task' => 'Criar uma outra subtarefa', + 'Time spent' => 'Tempo gasto', + 'Edit a sub-task' => 'Editar uma subtarefa', + 'Remove a sub-task' => 'Remover uma subtarefa', + 'The time must be a numeric value' => 'O tempo deve ser um valor numérico', + 'Todo' => 'A fazer', + 'In progress' => 'Em andamento', + 'Sub-task removed successfully.' => 'Subtarefa removida com sucesso.', + 'Unable to remove this sub-task.' => 'Não foi possível remover esta subtarefa.', + 'Sub-task updated successfully.' => 'Subtarefa atualizada com sucesso.', + 'Unable to update your sub-task.' => 'Não foi possível atualizar a sua subtarefa.', + 'Unable to create your sub-task.' => 'Não é possível criar a sua subtarefa.', + 'Maximum size: ' => 'Tamanho máximo: ', + 'Display another project' => 'Mostrar outro projeto', + 'Created by %s' => 'Criado por %s', + 'Tasks Export' => 'Exportar Tarefas', + 'Start Date' => 'Data inicial', + 'Execute' => 'Executar', + 'Task Id' => 'ID da Tarefa', + 'Creator' => 'Criado por', + 'Modification date' => 'Data da modificação', + 'Completion date' => 'Data da finalização', + 'Clone' => 'Clonar', + 'Project cloned successfully.' => 'Projeto clonado com sucesso.', + 'Unable to clone this project.' => 'Não foi possível clonar este projeto.', + 'Enable email notifications' => 'Activar notificações por e-mail', + 'Task position:' => 'Posição da tarefa:', + 'The task #%d has been opened.' => 'A tarefa #%d foi aberta.', + 'The task #%d has been closed.' => 'A tarefa #%d foi finalizada.', + 'Sub-task updated' => 'Subtarefa atualizada', + 'Title:' => 'Título:', + 'Status:' => 'Estado:', + 'Assignee:' => 'Assignado:', + 'Time tracking:' => 'Controle de tempo:', + 'New sub-task' => 'Nova subtarefa', + 'New attachment added "%s"' => 'Novo anexo adicionado "%s"', + 'New comment posted by %s' => 'Novo comentário por %s', + 'New comment' => 'Novo comentário', + 'Comment updated' => 'Comentário actualizado', + 'New subtask' => 'Nova subtarefa', + 'I only want to receive notifications for these projects:' => 'Quero receber notificações apenas destes projetos:', + 'view the task on Kanboard' => 'ver a tarefa no Kanboard', + 'Public access' => 'Acesso público', + 'Disable public access' => 'Desactivar o acesso público', + 'Enable public access' => 'Activar o acesso público', + 'Public access disabled' => 'Acesso público desactivado', + 'Move the task to another project' => 'Mover a tarefa para outro projeto', + 'Move to project' => 'Mover para outro projeto', + 'Do you really want to duplicate this task?' => 'Tem a certeza que quer duplicar esta tarefa?', + 'Duplicate a task' => 'Duplicar uma tarefa', + 'External accounts' => 'Contas externas', + 'Account type' => 'Tipo de conta', + 'Local' => 'Local', + 'Remote' => 'Remoto', + 'Enabled' => 'Activado', + 'Disabled' => 'Desactivado', + 'Login:' => 'Utilizador:', + 'Full Name:' => 'Nome:', + 'Email:' => 'E-mail:', + 'Notifications:' => 'Notificações:', + 'Notifications' => 'Notificações', + 'Account type:' => 'Tipo de conta:', + 'Edit profile' => 'Editar perfil', + 'Change password' => 'Alterar senha', + 'Password modification' => 'Alteração de senha', + 'External authentications' => 'Autenticação externa', + 'Never connected.' => 'Nunca conectado.', + 'No external authentication enabled.' => 'Nenhuma autenticação externa activa.', + 'Password modified successfully.' => 'Senha alterada com sucesso.', + 'Unable to change the password.' => 'Não foi possível alterar a senha.', + 'Change category' => 'Mudar categoria', + '%s updated the task %s' => '%s actualizou a tarefa %s', + '%s opened the task %s' => '%s abriu a tarefa %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s moveu a tarefa %s para a posição #%d na coluna "%s"', + '%s moved the task %s to the column "%s"' => '%s moveu a tarefa %s para a coluna "%s"', + '%s created the task %s' => '%s criou a tarefa %s', + '%s closed the task %s' => '%s finalizou a tarefa %s', + '%s created a subtask for the task %s' => '%s criou uma subtarefa para a tarefa %s', + '%s updated a subtask for the task %s' => '%s actualizou uma subtarefa da tarefa %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Designado para %s com tempo estimado de %s/%sh', + 'Not assigned, estimate of %sh' => 'Não assignado, estimado em %sh', + '%s updated a comment on the task %s' => '%s actualizou o comentário na tarefa %s', + '%s commented the task %s' => '%s comentou a tarefa %s', + '%s\'s activity' => 'Atividades %s', + 'RSS feed' => 'Feed RSS', + '%s updated a comment on the task #%d' => '%s actualizou um comentário sobre a tarefa #%d', + '%s commented on the task #%d' => '%s comentou sobre a tarefa #%d', + '%s updated a subtask for the task #%d' => '%s actualizou uma subtarefa para a tarefa #%d', + '%s created a subtask for the task #%d' => '%s criou uma subtarefa para a tarefa #%d', + '%s updated the task #%d' => '%s actualizou a tarefa #%d', + '%s created the task #%d' => '%s criou a tarefa #%d', + '%s closed the task #%d' => '%s finalizou a tarefa #%d', + '%s opened the task #%d' => '%s abriu a tarefa #%d', + 'Activity' => 'Actividade', + 'Default values are "%s"' => 'Os valores padrão são "%s"', + 'Default columns for new projects (Comma-separated)' => 'Colunas padrão para novos projetos (Separado por vírgula)', + 'Task assignee change' => 'Mudar assignação da tarefa', + '%s changed the assignee of the task #%d to %s' => '%s mudou a assignação da tarefa #%d para %s', + '%s changed the assignee of the task %s to %s' => '%s mudou a assignação da tarefa %s para %s', + 'New password for the user "%s"' => 'Nova senha para o utilizador "%s"', + 'Choose an event' => 'Escolher um evento', + 'Create a task from an external provider' => 'Criar uma tarefa por meio de um serviço externo', + 'Change the assignee based on an external username' => 'Alterar assignação com base num utilizador externo', + 'Change the category based on an external label' => 'Alterar categoria com base num rótulo externo', + 'Reference' => 'Referência', + 'Label' => 'Rótulo', + 'Database' => 'Base de dados', + 'About' => 'Sobre', + 'Database driver:' => 'Driver da base de dados:', + 'Board settings' => 'Configurações do Quadro', + 'Webhook settings' => 'Configurações do Webhook', + 'Reset token' => 'Redefinir token', + 'API endpoint:' => 'API endpoint:', + 'Refresh interval for personal board' => 'Intervalo de actualização para um quadro privado', + 'Refresh interval for public board' => 'Intervalo de actualização para um quadro público', + 'Task highlight period' => 'Período de Tarefa em destaque', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Período (em segundos) para considerar que uma tarefa foi modificada recentemente (0 para desactivar, 2 dias por defeito)', + 'Frequency in second (60 seconds by default)' => 'Frequência em segundos (60 segundos por defeito)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frequência em segundos (0 para desactivar este recurso, 10 segundos por defeito)', + 'Application URL' => 'URL da Aplicação', + 'Token regenerated.' => 'Token ', + 'Date format' => 'Formato da data', + 'ISO format is always accepted, example: "%s" and "%s"' => 'O formato ISO é sempre aceite, exemplo: "%s" e "%s"', + 'New personal project' => 'Novo projeto pessoal', + 'This project is personal' => 'Este projeto é pessoal', + 'Add' => 'Adicionar', + 'Start date' => 'Data de início', + 'Time estimated' => 'Tempo estimado', + 'There is nothing assigned to you.' => 'Não há nada assignado a si.', + 'My tasks' => 'As minhas tarefas', + 'Activity stream' => 'Atividades Recentes', + 'Dashboard' => 'Painel de Controlo', + 'Confirmation' => 'Confirmação', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Create a comment from an external provider' => 'Criar um comentário por meio de um serviço externo', + 'Project management' => 'Gestão de projetos', + 'Columns' => 'Colunas', + 'Task' => 'Tarefas', + 'Percentage' => 'Percentagem', + 'Number of tasks' => 'Número de tarefas', + 'Task distribution' => 'Distribuição de tarefas', + 'Analytics' => 'Estatísticas', + 'Subtask' => 'Subtarefa', + 'User repartition' => 'Redistribuição de utilizador', + 'Clone this project' => 'Clonar este projeto', + 'Column removed successfully.' => 'Coluna removida com sucesso.', + 'Not enough data to show the graph.' => 'Não há dados suficientes para mostrar o gráfico.', + 'Previous' => 'Anterior', + 'The id must be an integer' => 'O ID deve ser um número inteiro', + 'The project id must be an integer' => 'O ID do projeto deve ser um inteiro', + 'The status must be an integer' => 'O estado deve ser um número inteiro', + 'The subtask id is required' => 'O ID da subtarefa é obrigatório', + 'The subtask id must be an integer' => 'O ID da subtarefa deve ser um número inteiro', + 'The task id is required' => 'O ID da tarefa é obrigatório', + 'The task id must be an integer' => 'O ID da tarefa deve ser um número inteiro', + 'The user id must be an integer' => 'O ID do utilizador deve ser um número inteiro', + 'This value is required' => 'Este valor é obrigatório', + 'This value must be numeric' => 'Este valor deve ser numérico', + 'Unable to create this task.' => 'Não foi possível criar esta tarefa.', + 'Cumulative flow diagram' => 'Fluxograma cumulativo', + 'Daily project summary' => 'Resumo diário do projeto', + 'Daily project summary export' => 'Exportação diária do resumo do projeto', + 'Exports' => 'Exportar', + 'This export contains the number of tasks per column grouped per day.' => 'Esta exportação contém o número de tarefas por coluna agrupada por dia.', + 'Active swimlanes' => 'Activar swimlanes', + 'Add a new swimlane' => 'Adicionar novo swimlane', + 'Default swimlane' => 'Swimlane padrão', + 'Do you really want to remove this swimlane: "%s"?' => 'Tem a certeza que quer remover este swimlane: "%s"?', + 'Inactive swimlanes' => 'Desactivar swimlanes', + 'Remove a swimlane' => 'Remover um swimlane', + 'Swimlane modification for the project "%s"' => 'Modificação de swimlane para o projeto "%s"', + 'Swimlane removed successfully.' => 'Swimlane removido com sucesso.', + 'Swimlanes' => 'Swimlanes', + 'Swimlane updated successfully.' => 'Swimlane atualizado com sucesso.', + 'Unable to remove this swimlane.' => 'Não foi possível remover este swimlane.', + 'Unable to update this swimlane.' => 'Não foi possível atualizar este swimlane.', + 'Your swimlane has been created successfully.' => 'Seu swimlane foi criado com sucesso.', + 'Example: "Bug, Feature Request, Improvement"' => 'Exemplo: "Bug, Feature Request, Improvement"', + 'Default categories for new projects (Comma-separated)' => 'Categorias padrão para novos projetos (Separadas por vírgula)', + 'Integrations' => 'Integrações', + 'Integration with third-party services' => 'Integração com serviços de terceiros', + 'Subtask Id' => 'ID da subtarefa', + 'Subtasks' => 'Subtarefas', + 'Subtasks Export' => 'Exportar subtarefas', + 'Task Title' => 'Título da Tarefa', + 'Untitled' => 'Sem título', + 'Application default' => 'Aplicação padrão', + 'Language:' => 'Idioma:', + 'Timezone:' => 'Fuso horário:', + 'All columns' => 'Todas as colunas', + 'Next' => 'Próximo', + '#%d' => '#%d', + 'All swimlanes' => 'Todos os swimlane', + 'All colors' => 'Todas as cores', + 'Moved to column %s' => 'Mover para a coluna %s', + 'User dashboard' => 'Painel de Controlo do utilizador', + 'Allow only one subtask in progress at the same time for a user' => 'Permitir apenas uma subtarefa em andamento ao mesmo tempo para um utilizador', + 'Edit column "%s"' => 'Editar a coluna "%s"', + 'Select the new status of the subtask: "%s"' => 'Selecionar um novo estado para a subtarefa: "%s"', + 'Subtask timesheet' => 'Gestão de tempo das subtarefas', + 'There is nothing to show.' => 'Não há nada para mostrar', + 'Time Tracking' => 'Gestão de tempo', + 'You already have one subtask in progress' => 'Já tem uma subtarefa em andamento', + 'Which parts of the project do you want to duplicate?' => 'Quais as partes do projeto que deseja duplicar?', + 'Disallow login form' => 'Desactivar login', + 'Start' => 'Inicio', + 'End' => 'Fim', + 'Task age in days' => 'Idade da tarefa em dias', + 'Days in this column' => 'Dias nesta coluna', + '%dd' => '%dd', + 'Add a new link' => 'Adicionar uma nova associação', + 'Do you really want to remove this link: "%s"?' => 'Tem a certeza que quer remover esta associação: "%s"?', + 'Do you really want to remove this link with task #%d?' => 'Tem a certeza que quer remover esta associação com a tarefa n°%d?', + 'Field required' => 'Campo requerido', + 'Link added successfully.' => 'Associação criada com sucesso.', + 'Link updated successfully.' => 'Associação atualizada com sucesso.', + 'Link removed successfully.' => 'Associação removida com sucesso.', + 'Link labels' => 'Etiquetas das associações', + 'Link modification' => 'Modificação de uma associação', + 'Opposite label' => 'Nome da etiqueta oposta', + 'Remove a link' => 'Remover uma associação', + 'The labels must be different' => 'As etiquetas devem ser diferentes', + 'There is no link.' => 'Não há nenhuma associação.', + 'This label must be unique' => 'Esta etiqueta deve ser unica', + 'Unable to create your link.' => 'Impossível de adicionar sua associação.', + 'Unable to update your link.' => 'Impossível de atualizar sua associação.', + 'Unable to remove this link.' => 'Impossível de remover sua associação.', + 'relates to' => 'é associada a', + 'blocks' => 'bloqueia', + 'is blocked by' => 'está bloqueada por', + 'duplicates' => 'duplica', + 'is duplicated by' => 'é duplicada por', + 'is a child of' => 'é um filha de', + 'is a parent of' => 'é um parente do', + 'targets milestone' => 'visa um objectivo', + 'is a milestone of' => 'é um objectivo de', + 'fixes' => 'corrige', + 'is fixed by' => 'foi corrigida por', + 'This task' => 'Esta tarefa', + '<1h' => '<1h', + '%dh' => '%dh', + 'Expand tasks' => 'Expandir tarefas', + 'Collapse tasks' => 'Contrair tarefas', + 'Expand/collapse tasks' => 'Expandir/Contrair tarefas', + 'Close dialog box' => 'Fechar a caixa de diálogo', + 'Submit a form' => 'Envia o formulário', + 'Board view' => 'Vista do Quadro', + 'Keyboard shortcuts' => 'Atalhos de teclado', + 'Open board switcher' => 'Abrir o comutador de painel', + 'Application' => 'Aplicação', + 'Compact view' => 'Vista reduzida', + 'Horizontal scrolling' => 'Deslocamento horizontal', + 'Compact/wide view' => 'Alternar entre a vista compacta e ampliada', + 'Currency' => 'Moeda', + 'Personal project' => 'Projeto pessoal', + 'AUD - Australian Dollar' => 'AUD - Dólar Australiano', + 'CAD - Canadian Dollar' => 'CAD - Dólar Canadense', + 'CHF - Swiss Francs' => 'CHF - Francos Suíços', + 'Custom Stylesheet' => 'Folha de estilos personalizada', + 'EUR - Euro' => 'EUR - Euro', + 'GBP - British Pound' => 'GBP - Libra Esterlina', + 'INR - Indian Rupee' => 'INR - Rúpia Indiana', + 'JPY - Japanese Yen' => 'JPY - Iene Japonês', + 'NZD - New Zealand Dollar' => 'NZD - Dólar Neozelandês', + 'PEN - Peruvian Sol' => 'PEN – Sol peruano', + 'RSD - Serbian dinar' => 'RSD - Dinar Sérvio', + 'CNY - Chinese Yuan' => 'CNY - Yuan Chinês', + 'USD - US Dollar' => 'USD - Dólar Norte-americano', + 'VES - Venezuelan Bolívar' => 'VES – Bolívar venezuelano', + 'Destination column' => 'Coluna de destino', + 'Move the task to another column when assigned to a user' => 'Mover a tarefa para uma outra coluna quando esta está atribuída a um utilizador', + 'Move the task to another column when assignee is cleared' => 'Mover a tarefa para uma outra coluna quando esta não está atribuída', + 'Source column' => 'Coluna de origem', + '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 utilizador 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', + 'Reference currency' => 'Moeda de Referência', + 'The currency rate has been added successfully.' => 'A taxa de câmbio foi adicionada com sucesso.', + 'Unable to add this currency rate.' => 'Impossível adicionar essa taxa de câmbio.', + 'Webhook URL' => 'URL do webhook', + '%s removed the assignee of the task %s' => '%s removeu a pessoa assignada à tarefa %s', + 'Information' => 'Informações', + 'Check two factor authentication code' => 'Verificação do código de autenticação com factor duplo', + 'The two factor authentication code is not valid.' => 'O código de autenticação com factor duplo não é válido', + 'The two factor authentication code is valid.' => 'O código de autenticação com factor duplo é válido', + 'Code' => 'Código', + 'Two factor authentication' => 'Autenticação com factor duplo', + 'This QR code contains the key URI: ' => 'Este Código QR contém a chave URI: ', + 'Check my code' => 'Verificar 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', + '%s via Kanboard' => '%s via Kanboard', + 'Burndown chart' => 'Gráfico de Burndown', + 'This chart show the task complexity over the time (Work Remaining).' => 'Este gráfico mostra a complexidade da tarefa ao longo do tempo (Trabalho Restante).', + 'Screenshot taken %s' => 'Screenshot tirada a %s', + 'Add a screenshot' => 'Adicionar uma Screenshot', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Tire um screenshot e pressione CTRL + V ou ⌘ + V para colar aqui.', + 'Screenshot uploaded successfully.' => 'Screenshot enviada com sucesso.', + 'SEK - Swedish Krona' => 'SEK - Coroa Sueca', + 'Identifier' => 'Identificador', + 'Disable two factor authentication' => 'Desactivar autenticação com dois factores', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Tem a certeza que quer desactivar a autenticação com dois factores para esse utilizador: "%s"?', + 'Edit link' => 'Editar um link', + 'Start to type task title...' => 'Escreva o título do trabalho...', + 'A task cannot be linked to itself' => 'Uma tarefa não pode ser ligada a si própria', + 'The exact same link already exists' => 'Um link idêntico jà existe', + 'Recurrent task is scheduled to be generated' => 'A tarefa recorrente está programada para ser criada', + 'Score' => 'Complexidade', + 'The identifier must be unique' => 'O identificador deve ser único', + 'This linked task id doesn\'t exists' => 'O identificador da tarefa associada não existe', + 'This value must be alphanumeric' => 'Este valor deve ser alfanumérico', + 'Edit recurrence' => 'Modificar a recorrência', + 'Generate recurrent task' => 'Gerar uma tarefa recorrente', + 'Trigger to generate recurrent task' => 'Activador para gerar tarefa recorrente', + 'Factor to calculate new due date' => 'Factor para o cálculo do nova data de vencimento', + 'Timeframe to calculate new due date' => 'Escala de tempo para o cálculo da nova data de vencimento', + 'Base date to calculate new due date' => 'Data a ser utilizada para calcular a nova data de vencimento', + 'Action date' => 'Data da acção', + 'Base date to calculate new due date: ' => 'Data a ser utilizada para calcular a nova data de vencimento: ', + 'This task has created this child task: ' => 'Esta tarefa criou a tarefa filha: ', + 'Day(s)' => 'Dia(s)', + 'Existing due date' => 'Data de vencimento existente', + 'Factor to calculate new due date: ' => 'Factor para calcular a nova data de vencimento: ', + 'Month(s)' => 'Mês(es)', + 'This task has been created by: ' => 'Esta tarefa foi criada por: ', + 'Recurrent task has been generated:' => 'A tarefa recorrente foi gerada:', + 'Timeframe to calculate new due date: ' => 'Escala de tempo para o cálculo da nova data de vencimento: ', + 'Trigger to generate recurrent task: ' => 'Activador para gerar tarefa recorrente: ', + 'When task is closed' => 'Quando a tarefa é fechada', + 'When task is moved from first column' => 'Quando a tarefa é movida fora da primeira coluna', + 'When task is moved to last column' => 'Quando a tarefa é movida para a última coluna', + 'Year(s)' => 'Ano(s)', + 'Project settings' => 'Configurações dos projetos', + 'Automatically update the start date' => 'Actualizar automaticamente a data de início', + 'iCal feed' => 'Subscrição iCal', + 'Preferences' => 'Preferências', + 'Security' => 'Segurança', + 'Two factor authentication disabled' => 'Autenticação com factor duplo desactivado', + 'Two factor authentication enabled' => 'Autenticação com factor duplo activado', + 'Unable to update this user.' => 'Impossível de actualizar este utilizador.', + 'There is no user management for personal projects.' => 'Não há gestão de utilizadores para projetos pessoais.', + 'User that will receive the email' => 'O utilizador que vai receber o e-mail', + 'Email subject' => 'Assunto do e-mail', + 'Date' => 'Data', + 'Add a comment log when moving the task between columns' => 'Adicionar um comentário de log quando uma tarefa é movida para uma outra coluna', + 'Move the task to another column when the category is changed' => 'Mover uma tarefa para outra coluna quando a categoria mudar', + 'Send a task by email to someone' => 'Enviar uma tarefa por e-mail a alguém', + 'Reopen a task' => 'Reabrir uma tarefa', + 'Notification' => 'Notificação', + '%s moved the task #%d to the first swimlane' => '%s moveu a tarefa n° %d no primeiro swimlane', + 'Swimlane' => 'Swimlane', + '%s moved the task %s to the first swimlane' => '%s moveu a tarefa %s no primeiro swimlane', + '%s moved the task %s to the swimlane "%s"' => '%s moveu a tarefa %s no swimlane "%s"', + 'This report contains all subtasks information for the given date range.' => 'Este relatório contém informações de todas as sub-tarefas para o período selecionado.', + 'This report contains all tasks information for the given date range.' => 'Este relatório contém informações de todas as tarefas para o período selecionado.', + 'Project activities for %s' => 'Actividade do projeto "%s"', + 'view the board on Kanboard' => 'ver o painel no Kanboard', + 'The task has been moved to the first swimlane' => 'A tarefa foi movida para o primeiro Swimlane', + 'The task has been moved to another swimlane:' => 'A tarefa foi movida para outro Swimlane:', + 'New title: %s' => 'Novo título: %s', + 'The task is not assigned anymore' => 'Tarefa já não está atribuída', + 'New assignee: %s' => 'Novo assignado: %s', + 'There is no category now' => 'Já não existe categoria', + 'New category: %s' => 'Nova categoria: %s', + 'New color: %s' => 'Nova cor: %s', + 'New complexity: %d' => 'Nova complexidade: %d', + 'The due date has been removed' => 'A data de vencimento foi retirada', + 'There is no description anymore' => 'Já não há descrição', + 'Recurrence settings has been modified' => 'As configurações da recorrência foram modificadas', + 'Time spent changed: %sh' => 'O tempo despendido foi mudado: %sh', + 'Time estimated changed: %sh' => 'O tempo estimado foi mudado/ %sh', + 'The field "%s" has been updated' => 'O campo "%s" foi actualizada', + 'The description has been modified:' => 'A descrição foi modificada', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Tem a certeza que quer fechar a tarefa "%s" e todas as suas sub-tarefas?', + 'I want to receive notifications for:' => 'Eu quero receber as notificações para:', + 'All tasks' => 'Todas as tarefas', + 'Only for tasks assigned to me' => 'Somente as tarefas atribuídas a mim', + 'Only for tasks created by me' => 'Apenas as tarefas que eu criei', + 'Only for tasks created by me and tasks assigned to me' => 'Apenas as tarefas que eu criei e aquelas atribuídas a mim', + '%%Y-%%m-%%d' => '%%d/%%m/%%Y', + 'Total for all columns' => 'Total para todas as colunas', + 'You need at least 2 days of data to show the chart.' => 'Precisa de pelo menos 2 dias de dados para visualizar o gráfico.', + '<15m' => '<15m', + '<30m' => '<30m', + 'Stop timer' => 'Parar temporizador', + 'Start timer' => 'Iniciar temporizador', + 'My activity stream' => 'O meu feed de actividade', + 'Search tasks' => 'Pesquisar tarefas', + 'Reset filters' => 'Redefinir os filtros', + 'My tasks due tomorrow' => 'A minhas tarefas que expiram amanhã', + 'Tasks due today' => 'Tarefas que expiram hoje', + 'Tasks due tomorrow' => 'Tarefas que expiram amanhã', + 'Tasks due yesterday' => 'Tarefas que expiraram ontem', + 'Closed tasks' => 'Tarefas fechadas', + 'Open tasks' => 'Tarefas abertas', + 'Not assigned' => 'Não assignada', + 'View advanced search syntax' => 'Ver sintaxe avançada de pesquisa', + 'Overview' => 'Visão global', + 'Board/Calendar/List view' => 'Vista Painel/Calendário/Lista', + 'Switch to the board view' => 'Mudar para o modo Painel', + 'Switch to the list view' => 'Mudar para o modo Lista', + 'Go to the search/filter box' => 'Ir para o campo de pesquisa', + 'There is no activity yet.' => 'Ainda não há nenhuma actividade.', + 'No tasks found.' => 'Nenhuma tarefa encontrada', + 'Keyboard shortcut: "%s"' => 'Tecla de atalho: "%s"', + 'List' => 'Lista', + 'Filter' => 'Filtro', + 'Advanced search' => 'Pesquisa avançada', + 'Example of query: ' => 'Exemplo de consulta: ', + 'Search by project: ' => 'Pesquisar por projeto: ', + 'Search by column: ' => 'Pesquisar por coluna: ', + 'Search by assignee: ' => 'Pesquisar por assignado: ', + 'Search by color: ' => 'Pesquisar por cor: ', + 'Search by category: ' => 'Pesquisar por categoria: ', + 'Search by description: ' => 'Pesquisar por descrição: ', + 'Search by due date: ' => 'Pesquisar por data de vencimento: ', + 'Average time spent in each column' => 'Tempo médio gasto por coluna', + 'Average time spent' => 'Tempo médio gasto', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Este gráfico mostra o tempo médio gasto em cada coluna nas últimas %d tarefas.', + 'Average Lead and Cycle time' => 'Tempo de Espera e Ciclo médio', + 'Average lead time: ' => 'Tempo médio de Espera: ', + 'Average cycle time: ' => 'Tempo médio de Ciclo: ', + 'Cycle Time' => 'Tempo de Ciclo', + 'Lead Time' => 'Tempo de Espera', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Este gráfico mostra o tempo médio de espera e ciclo para as últimas %d tarefas realizadas.', + 'Average time into each column' => 'Tempo médio em cada coluna', + 'Lead and cycle time' => 'Tempo de Espera e Ciclo', + 'Lead time: ' => 'Tempo de Espera: ', + 'Cycle time: ' => 'Tempo de Ciclo: ', + 'Time spent in each column' => 'Tempo gasto em cada coluna', + 'The lead time is the duration between the task creation and the completion.' => 'O tempo de espera é a duração entre a criação e o fim da tarefa', + 'The cycle time is the duration between the start date and the completion.' => 'O tempo de ciclo é a duração entre a data de inicio e o fim da tarefa', + 'If the task is not closed the current time is used instead of the completion date.' => 'Se a tarefa não estiver fechada o hora actual será usada em vez da hora de conclusão', + 'Set the start date automatically' => 'Definir data de inicio automáticamente', + 'Edit Authentication' => 'Editar Autenticação', + 'Remote user' => 'Utilizador remoto', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Utilizadores remotos não guardam a password na base de dados do Kanboard, por exemplo: LDAP, contas do Google e Github.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Se activar a opção "Desactivar login", as credenciais digitadas no login serão ignoradas.', + 'Default task color' => 'Cor de tarefa por defeito', + 'This feature does not work with all browsers.' => 'Esta funcionalidade não funciona em todos os browsers', + 'There is no destination project available.' => 'Não há projeto de destino disponivel', + 'Trigger automatically subtask time tracking' => 'Activar automáticamente subtarefa de controlo de tempo', + 'Include closed tasks in the cumulative flow diagram' => 'Incluir tarefas fechadas no diagrama de fluxo acumulado', + 'Current swimlane: %s' => 'Swimlane actual: %s', + 'Current column: %s' => 'Coluna actual: %s', + 'Current category: %s' => 'Categoria actual: %s', + 'no category' => 'sem categoria', + 'Current assignee: %s' => 'Assignado a: %s', + 'not assigned' => 'não assignado', + 'Author:' => 'Autor:', + 'contributors' => 'contribuidores', + 'License:' => 'Licença:', + 'License' => 'Licença', + 'Enter the text below' => 'Escreva o texto em baixo', + 'Start date:' => 'Data de inicio:', + 'Due date:' => 'Data de vencimento:', + 'People who are project managers' => 'Pessoas que são gestores do projeto', + 'People who are project members' => 'Pessoas que são membros do projeto', + 'NOK - Norwegian Krone' => 'NOK - Coroa Norueguesa', + 'Show this column' => 'Mostrar esta coluna', + 'Hide this column' => 'Esconder esta coluna', + 'End date' => 'Data de fim', + 'Users overview' => 'Visão geral de Utilizadores', + 'Members' => 'Membros', + 'Shared project' => 'Projeto partilhado', + 'Project managers' => 'Gestores do projeto', + 'Projects list' => 'Lista de projetos', + 'End date:' => 'Data de fim:', + 'Change task color when using a specific task link' => 'Alterar cor da tarefa quando se usar um tipo especifico de ligação de tarefa', + 'Task link creation or modification' => 'Criação ou modificação de ligação de tarefa', + 'Milestone' => 'Objectivo', + 'Reset the search/filter box' => 'Repor caixa de procura/filtro', + 'Documentation' => 'Documentação', + 'Author' => 'Autor', + 'Version' => 'Versão', + 'Plugins' => 'Plugins', + 'There is no plugin loaded.' => 'Não existem extras carregados', + 'My notifications' => 'As minhas notificações', + 'Custom filters' => 'Filtros personalizados', + 'Your custom filter has been created successfully.' => 'O seu filtro personalizado foi criado com sucesso.', + 'Unable to create your custom filter.' => 'Não foi possivel criar o seu filtro personalizado.', + 'Custom filter removed successfully.' => 'Filtro personalizado removido com sucesso.', + 'Unable to remove this custom filter.' => 'Não foi possivel remover este filtro personalizado.', + 'Edit custom filter' => 'Editar filtro personalizado', + 'Your custom filter has been updated successfully.' => 'O seu filtro personalizado foi actualizado com sucesso.', + 'Unable to update custom filter.' => 'Não foi possivel actualizar o filtro personalizado.', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Novo anexo na tarefa #%d: %s', + 'New comment on task #%d' => 'Novo comentário na tarefa #%d', + 'Comment updated on task #%d' => 'Comentário actualizado na tarefa #%d', + 'New subtask on task #%d' => 'Nova sub-tarefa na tarefa #%d', + 'Subtask updated on task #%d' => 'Sub-tarefa actualizada na tarefa #%d', + 'New task #%d: %s' => 'Nova tarefa #%d: %s', + 'Task updated #%d' => 'Tarefa actualizada #%d', + 'Task #%d closed' => 'Tarefa #%d fechada', + 'Task #%d opened' => 'Tarefa #%d aberta', + 'Column changed for task #%d' => 'Coluna alterada para tarefa #%d', + 'New position for task #%d' => 'Nova posição para tarefa #%d', + 'Swimlane changed for task #%d' => 'Swimlane alterado na tarefa #%d', + 'Assignee changed on task #%d' => 'Assignado alterado na tarefa #%d', + '%d overdue tasks' => '%d tarefas em atraso', + 'No notification.' => 'Sem novas notificações.', + 'Mark all as read' => 'Marcar tudo como lido', + 'Mark as read' => 'Marcar como lido', + 'Total number of tasks in this column across all swimlanes' => 'Número total de tarefas nesta coluna em todas as swimlanes', + 'Collapse swimlane' => 'Colapsar swimlane', + 'Expand swimlane' => 'Expandir swimlane', + 'Add a new filter' => 'Adicionar um novo filtro', + 'Share with all project members' => 'Partilhar com todos os membros do projeto', + 'Shared' => 'Partilhado', + 'Owner' => 'Dono', + 'Unread notifications' => 'Notificações por ler', + 'Notification methods:' => 'Métodos de notificação:', + 'Unable to read your file' => 'Não foi possivel ler o ficheiro', + '%d task(s) have been imported successfully.' => '%d tarefa(s) importada(s) com successo.', + 'Nothing has been imported!' => 'Nada foi importado', + 'Import users from CSV file' => 'Importar utilizadores de um ficheiro CSV', + '%d user(s) have been imported successfully.' => '%d utilizadore(s) importados com successo.', + 'Comma' => 'Vírgula', + 'Semi-colon' => 'Ponto e Vírgula', + 'Tab' => 'Tabulação', + 'Vertical bar' => 'Barra vertical', + 'Double Quote' => 'Aspas', + 'Single Quote' => 'Plica', + '%s attached a file to the task #%d' => '%s anexou um ficheiro à tarefa #%d', + 'There is no column or swimlane activated in your project!' => 'Não existe nenhuma coluna ou swimlane activado no seu projeto!', + 'Append filter (instead of replacement)' => 'Acrescentar filtro (em vez de substituir)', + 'Append/Replace' => 'Acrescentar/Substituir', + 'Append' => 'Acrescentar', + 'Replace' => 'Substituir', + 'Import' => 'Importar', + 'Change sorting' => 'alterar ordernação', + 'Tasks Importation' => 'Importação de Tarefas', + 'Delimiter' => 'Delimitador', + 'Enclosure' => 'Clausura', + 'CSV File' => 'Ficheiro CSV', + 'Instructions' => 'Instruções', + 'Your file must use the predefined CSV format' => 'O seu ficheiro tem de usar um formato CSV pre-definido', + 'Your file must be encoded in UTF-8' => 'O seu ficheiro tem de estar codificado como UTF-8', + 'The first row must be the header' => 'A primeira linha tem de ser o cabeçalho', + 'Duplicates are not verified for you' => 'Duplicados não são verificados por si', + 'The due date must use the ISO format: YYYY-MM-DD' => 'A data de expiração tem de estar no formato ISO: AAAA-MM-DD', + 'Download CSV template' => 'Descarregar template CSV', + 'No external integration registered.' => 'Nenhuma integração externa registrada.', + 'Duplicates are not imported' => 'Duplicados não são importados', + 'Usernames must be lowercase and unique' => 'Utilizadores tem de estar em letra pequena e ser unicos', + 'Passwords will be encrypted if present' => 'Senhas serão encriptadas se presentes', + '%s attached a new file to the task %s' => '%s anexou um novo ficheiro à tarefa %s', + 'Link type' => 'Tipo de ligação', + 'Assign automatically a category based on a link' => 'Atribuir automáticamente a categoria baseada num link', + 'BAM - Konvertible Mark' => 'BAM - Marca Conversível', + 'Assignee Username' => 'Utilizador do Assignado', + 'Assignee Name' => 'Nome do Assignado', + 'Groups' => 'Grupos', + 'Members of %s' => 'Membros de %s', + 'New group' => 'Novo grupo', + 'Group created successfully.' => 'Grupo criado com sucesso.', + 'Unable to create your group.' => 'Não foi possivel criar o seu grupo.', + 'Edit group' => 'Editar grupo', + 'Group updated successfully.' => 'Grupo actualizado com sucesso.', + 'Unable to update your group.' => 'Não foi possivel actualizar o seu grupo.', + 'Add group member to "%s"' => 'Adicionar membro do grupo a "%s"', + 'Group member added successfully.' => 'Membro de grupo adicionado com sucesso.', + 'Unable to add group member.' => 'Não foi possivel adicionar membro de grupo.', + 'Remove user from group "%s"' => 'Remover utilizador do grupo "%s"', + 'User removed successfully from this group.' => 'Utilizador removido com sucesso deste grupo.', + 'Unable to remove this user from the group.' => 'Não foi possivel remover este utilizador do grupo.', + 'Remove group' => 'Remover grupo.', + 'Group removed successfully.' => 'Grupo removido com sucesso.', + 'Unable to remove this group.' => 'Não foi possivel remover este grupo.', + 'Project Permissions' => 'Permissões de Projeto', + 'Manager' => 'Gestor', + 'Project Manager' => 'Gestor de Projeto', + 'Project Member' => 'Membro de Projeto', + 'Project Viewer' => 'Visualizador de Projeto', + 'Your account is locked for %d minutes' => 'A sua conta está bloqueada por %d minutos', + 'Invalid captcha' => 'Captcha inválido', + 'The name must be unique' => 'O nome deve ser único', + 'View all groups' => 'Ver todos os grupos', + 'There is no user available.' => 'Não existe utilizador disponivel.', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Tem a certeza que quer remover o utilizador "%s" do grupo "%s"?', + 'There is no group.' => 'Não existe grupo.', + 'Add group member' => 'Adicionar membro de grupo', + 'Do you really want to remove this group: "%s"?' => 'Tem a certeza que quer remover este grupo: "%s"?', + 'There is no user in this group.' => 'Não existe utilizadores neste grupo.', + 'Permissions' => 'Permissões', + 'Allowed Users' => 'Utilizadores Permitidos', + 'No specific user has been allowed.' => 'Nenhum utilizador foi especificamente permitido.', + 'Role' => 'Função', + 'Enter user name...' => 'Escreva o nome do utilizador...', + 'Allowed Groups' => 'Grupos Permitidos', + 'No group has been allowed.' => 'Nenhum grupo foi especificamente permitido.', + 'Group' => 'Grupo', + 'Group Name' => 'Nome do Grupo', + 'Enter group name...' => 'Escreva o nome do Grupo', + 'Role:' => 'Função:', + 'Project members' => 'Membros do projeto', + '%s mentioned you in the task #%d' => '%s mencionou-te na tarefa #%d', + '%s mentioned you in a comment on the task #%d' => '%s mencionou-te num comentário na tarefa #%d', + 'You were mentioned in the task #%d' => 'Foi mencionado na tarefa #%d', + 'You were mentioned in a comment on the task #%d' => 'Foi mencionado num comentário na tarefa #%d', + 'Estimated hours: ' => 'Horas estimadas: ', + 'Actual hours: ' => 'Horas reais: ', + 'Hours Spent' => 'Horas Gastas', + 'Hours Estimated' => 'Horas Estimadas', + 'Estimated Time' => 'Tempo Estimado', + 'Actual Time' => 'Tempo Real', + 'Estimated vs actual time' => 'Tempo estimado vs real', + 'RUB - Russian Ruble' => 'RUB - Rublo Russo', + 'Assign the task to the person who does the action when the column is changed' => 'Atribuir a tarefa à pessoa que realiza a acção quando a coluna é alterada', + 'Close a task in a specific column' => 'Fechar tarefa numa coluna especifica', + 'Time-based One-time Password Algorithm' => 'Algoritmo de password para uso único baseado em tempo', + 'Two-Factor Provider: ' => 'Provedor de Dois Passos: ', + 'Disable two-factor authentication' => 'Desactivar autenticação de dois passos', + 'Enable two-factor authentication' => 'Activar autenticação de dois passos', + 'There is no integration registered at the moment.' => 'Não existe nenhuma integração registrada até ao momento.', + 'Password Reset for Kanboard' => 'Redefinir Password para Kanboard', + 'Forgot password?' => 'Esqueceu a password?', + 'Enable "Forget Password"' => 'Activar "Esqueceu a password"', + 'Password Reset' => 'Redefinir a Password', + 'New password' => 'Nova Password', + 'Change Password' => 'Alterar Password', + 'To reset your password click on this link:' => 'Para redefinir a sua password click nesta ligação:', + 'Last Password Reset' => 'Última Redefinição da Password', + 'The password has never been reinitialized.' => 'A password nunca foi redefinida.', + 'Creation' => 'Criação', + 'Expiration' => 'Expiração', + 'Password reset history' => 'Histórico da redefinição da password', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Todas as tarefas na coluna "%s" e na swimlane "%s" foram fechadas com successo.', + 'Do you really want to close all tasks of this column?' => 'Tem a certeza que quer fechar todas as tarefas nesta coluna?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d tarefa(s) na coluna "%s" e na swimlane "%s" serão fechadas.', + 'Close all tasks in this column and this swimlane' => 'Fechar todas as tarefas nesta coluna', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Nenhum plugin tem registado um método de notificação do projeto. Pode continuar a configurar notificações individuais no seu perfil de utilizador.', + 'My dashboard' => 'Meu painel', + 'My profile' => 'Meu perfil', + 'Project owner: ' => 'Dono do projeto: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'O identificador do projeto é opcional e tem de ser alfa-numerico, exemplo: MEUPROJETO.', + 'Project owner' => 'Dono do projeto', + 'Personal projects do not have users and groups management.' => 'Projetos pessoais não têm gestão de utilizadores nem de grupos.', + 'There is no project member.' => 'Não existe membro do projeto.', + 'Priority' => 'Prioridade', + 'Task priority' => 'Prioridade da Tarefa', + 'General' => 'Geral', + 'Dates' => 'Datas', + 'Default priority' => 'Prioridade por defeito', + 'Lowest priority' => 'Prioridade mais baixa', + 'Highest priority' => 'Prioridade mais alta', + 'Close a task when there is no activity' => 'Fechar tarefa quando não há actividade', + 'Duration in days' => 'Duração em dias', + 'Send email when there is no activity on a task' => 'Enviar e-mail quando não há actividade numa tarefa', + 'Unable to fetch link information.' => 'Impossivel obter informação da ligação.', + 'Daily background job for tasks' => 'Trabalho diário em segundo plano para tarefas', + 'Auto' => 'Auto', + 'Related' => 'Relacionado', + 'Attachment' => 'Anexo', + 'Web Link' => 'Ligação Web', + 'External links' => 'Ligações externas', + 'Add external link' => 'Adicionar ligação externa', + 'Type' => 'Tipo', + 'Dependency' => 'Dependencia', + 'Add internal link' => 'Adicionar ligação interna', + 'Add a new external link' => 'Adicionar nova ligação externa', + 'Edit external link' => 'Editar ligação externa', + 'External link' => 'Ligação externa', + 'Copy and paste your link here...' => 'Copie e cole a sua ligação aqui...', + 'URL' => 'URL', + 'Internal links' => 'Ligações internas', + 'Assign to me' => 'Atribuir a mim', + 'Me' => 'Eu', + 'Do not duplicate anything' => 'Não duplicar nada', + 'Projects management' => 'Gestão de projetos', + 'Users management' => 'Gestão de utilizadores', + 'Groups management' => 'Gestão de grupos', + 'Create from another project' => 'Criar apartir de outro projeto', + 'open' => 'aberto', + 'closed' => 'fechado', + 'Priority:' => 'Prioridade:', + 'Reference:' => 'Referência:', + 'Complexity:' => 'Complexidade:', + 'Swimlane:' => 'Swimlane:', + 'Column:' => 'Coluna:', + 'Position:' => 'Posição:', + 'Creator:' => 'Criador:', + 'Time estimated:' => 'Tempo estimado:', + '%s hours' => '%s horas', + 'Time spent:' => 'Tempo gasto:', + 'Created:' => 'Criado:', + 'Modified:' => 'Modificado:', + 'Completed:' => 'Completado:', + 'Started:' => 'Iniciado:', + 'Moved:' => 'Movido:', + 'Task #%d' => 'Tarefa #%d', + 'Time format' => 'Formato tempo', + 'Start date: ' => 'Data inicio: ', + 'End date: ' => 'Data final: ', + 'New due date: ' => 'Nova data estimada: ', + 'Start date changed: ' => 'Data inicio alterada: ', + 'Disable personal projects' => 'Desactivar projetos pessoais', + 'Do you really want to remove this custom filter: "%s"?' => 'Tem a certeza que quer remover este filtro personalizado: "%s"?', + 'Remove a custom filter' => 'Remover o filtro personalizado', + 'User activated successfully.' => 'Utilizador activado com sucesso.', + 'Unable to enable this user.' => 'Não foi possivel activar este utilizador.', + 'User disabled successfully.' => 'Utilizador desactivado com sucesso.', + 'Unable to disable this user.' => 'Não foi possivel desactivar este utilizador.', + 'All files have been uploaded successfully.' => 'Todos os ficheiros foram enviados com sucesso.', + 'The maximum allowed file size is %sB.' => 'O tamanho máximo permitido é %sB.', + 'Drag and drop your files here' => 'Arraste e deixe os ficheiros para aqui', + 'choose files' => 'escolher ficheiros', + 'View profile' => 'Ver perfil', + 'Two Factor' => 'Dois Factores', + 'Disable user' => 'Desactivar utilizador', + 'Do you really want to disable this user: "%s"?' => 'Tem a certeza que quer desactivar este utilizador: "%s"?', + 'Enable user' => 'Activar utilizador', + 'Do you really want to enable this user: "%s"?' => 'Tem a certeza que quer activar este utilizador: "%s"?', + 'Download' => 'Transferir', + 'Uploaded: %s' => 'Enviado: %s', + 'Size: %s' => 'Tamanho: %s', + 'Uploaded by %s' => 'Enviado por %s', + 'Filename' => 'Nome do ficheiro', + 'Size' => 'Tamanho', + 'Column created successfully.' => 'Coluna criada com sucesso.', + 'Another column with the same name exists in the project' => 'Já existe outra coluna com o mesmo nome no projeto', + 'Default filters' => 'Filtros padrão', + 'Your board doesn\'t have any columns!' => 'O seu quadro não tem nenhuma coluna!', + 'Change column position' => 'Mudar posição da coluna', + 'Switch to the project overview' => 'Mudar para vista geral do projeto', + 'User filters' => 'Filtros de utilizador', + 'Category filters' => 'Filtros de categoria', + 'Upload a file' => 'Enviar um ficheiro', + 'View file' => 'Ver ficheiro', + 'Last activity' => 'Ultima actividade', + 'Change subtask position' => 'Mudar posição da sub-tarefa', + 'This value must be greater than %d' => 'Este valor tem de ser maior que %d', + 'Another swimlane with the same name exists in the project' => 'Já existe outra swimlane com o mesmo nome no projeto', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Exemplo: https://example.kanboard.org/ (usado para gerar URLs absolutos)', + 'Actions duplicated successfully.' => 'Acções duplicadas com sucesso.', + 'Unable to duplicate actions.' => 'Não foi possivel duplicar acções.', + 'Add a new action' => 'Adicionar nova acção', + 'Import from another project' => 'Importar de outro projeto', + 'There is no action at the moment.' => 'De momento não existe acção.', + 'Import actions from another project' => 'Importar acções de outro projeto', + 'There is no available project.' => 'Não existe projeto disponivel.', + 'Local File' => 'Ficheiro Local', + 'Configuration' => 'Configuração', + 'PHP version:' => 'Versão PHP:', + 'PHP SAPI:' => 'SAPI PHP:', + 'OS version:' => 'Versão SO:', + 'Database version:' => 'Versão base de dados:', + 'Browser:' => 'Navegador:', + 'Task view' => 'Vista de Tarefas', + 'Edit task' => 'Editar tarefa', + 'Edit description' => 'Editar descrição', + 'New internal link' => 'Nova ligação interna', + 'Display list of keyboard shortcuts' => 'Mostrar lista de atalhos do teclado', + 'Avatar' => 'Avatar', + 'Upload my avatar image' => 'Enviar a minha imagem de avatar', + 'Remove my image' => 'Remover a minha imagem', + 'The OAuth2 state parameter is invalid' => 'O parametro de estado do OAuth2 é inválido', + 'User not found.' => 'Utilizador não encontrado.', + 'Search in activity stream' => 'Procurar no fluxo de atividade', + 'My activities' => 'Minhas actividades', + 'Activity until yesterday' => 'Actividade até ontem', + 'Activity until today' => 'Actividade até hoje', + 'Search by creator: ' => 'Procurar por criador: ', + 'Search by creation date: ' => 'Procurar por data de criação: ', + 'Search by task status: ' => 'Procurar por estado da tarefa: ', + 'Search by task title: ' => 'Procurar por titulo da tarefa: ', + 'Activity stream search' => 'Procurar fluxo de actividade', + 'Projects where "%s" is manager' => 'Projetos onde "%s" é gestor', + 'Projects where "%s" is member' => 'Projetos onde "%s" é membro', + 'Open tasks assigned to "%s"' => 'Tarefas abertas assignadas a "%s"', + 'Closed tasks assigned to "%s"' => 'Tarefas fechadas assignadas a "%s"', + 'Assign automatically a color based on a priority' => 'Atribuir uma cor automáticamente de acordo com a prioridade', + 'Overdue tasks for the project(s) "%s"' => 'Tarefas em atraso para o(s) projeto(s) "%s"', + 'Upload files' => 'Enviar ficheiros', + 'Installed Plugins' => 'Plugins Instalados', + 'Plugin Directory' => 'Directoria de Plugins', + 'Plugin installed successfully.' => 'Plugin instalado com sucesso.', + 'Plugin updated successfully.' => 'Plugin actualizado com sucesso.', + 'Plugin removed successfully.' => 'Plugin removido com sucesso.', + 'Subtask converted to task successfully.' => 'Sub-tarefa convertida para tarefa com sucesso.', + 'Unable to convert the subtask.' => 'Não foi possivel converter a sub-tarefa.', + 'Unable to extract plugin archive.' => 'Não foi possivel extrair o arquivo do plugin.', + 'Plugin not found.' => 'Plugin não encontrado.', + 'You don\'t have the permission to remove this plugin.' => 'Não tem permissão para remover este plugin.', + 'Unable to download plugin archive.' => 'Não foi possivel transferir o arquivo do plugin.', + 'Unable to write temporary file for plugin.' => 'Não foi possivel escrever o ficheiro temporário para o plugin.', + 'Unable to open plugin archive.' => 'Não foi possivel abrir o arquivo do plugin.', + 'There is no file in the plugin archive.' => 'Ficheiro não encontrado dentro do arquivo do plugin.', + 'Create tasks in bulk' => 'Criar tarefas em massa', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Este Kanboard não está configurado para instalar plugins através do interface de utilizador.', + 'There is no plugin available.' => 'Não existe nenhum plugin instalado.', + 'Install' => 'Instalar', + 'Update' => 'Actualizar', + 'Up to date' => 'Actualizado', + 'Not available' => 'Não disponivel', + 'Remove plugin' => 'Remover plugin', + 'Do you really want to remove this plugin: "%s"?' => 'Tem a certeza que pretende remover este plugin: "%s%"?', + 'Uninstall' => 'Desinstalar', + 'Listing' => 'A Listar', + 'Metadata' => 'Metadata', + 'Manage projects' => 'Gerir projetos', + 'Convert to task' => 'Converter para tarefa', + 'Convert sub-task to task' => 'Converter sub-tarefa para tarefa', + 'Do you really want to convert this sub-task to a task?' => 'Tem a certeza que pretende converter esta sub-tarefa para tarefa?', + 'My task title' => 'Titulo da minha tarefa', + 'Enter one task by line.' => 'Escreva uma tarefa por linha.', + 'Number of failed login:' => 'Número de logins falhados:', + 'Account locked until:' => 'Conta bloqueada até:', + 'Email settings' => 'Definições de E-mail', + 'Email sender address' => 'Endereço de envido de E-mail', + 'Email transport' => 'Transportador de E-mail', + 'Webhook token' => 'Token do Webhook', + 'Project tags management' => 'Gestão de etiquetas do Projeto', + 'Tag created successfully.' => 'Etiqueta criada com sucesso.', + 'Unable to create this tag.' => 'Não foi possivel criar esta etiqueta.', + 'Tag updated successfully.' => 'Etiqueta actualizada com sucesso.', + 'Unable to update this tag.' => 'Não foi possivel actualizar esta etiqueta.', + 'Tag removed successfully.' => 'Etiqueta removida com sucesso.', + 'Unable to remove this tag.' => 'Não foi possivel remover esta etiqueta.', + 'Global tags management' => 'Gestão de etiquetas globais', + 'Tags' => 'Etiquetas', + 'Tags management' => 'Gestão de Etiquetas', + 'Add new tag' => 'Adicionar etiqueta nova', + 'Edit a tag' => 'Editar a etiqueta', + 'Project tags' => 'Etiquetas do Projeto', + 'There is no specific tag for this project at the moment.' => 'De momento não existe nenhuma etiqueta para este projeto.', + 'Tag' => 'Etiqueta', + 'Remove a tag' => 'Remover etiqueta', + 'Do you really want to remove this tag: "%s"?' => 'Tem a certeza que pretende remover esta etiqueta: "%s"?', + 'Global tags' => 'Etiquetas globais', + 'There is no global tag at the moment.' => 'De momento não existe nenhuma etiqueta global.', + 'This field cannot be empty' => 'Este campo não pode ficar vazio', + 'Close a task when there is no activity in a specific column' => 'Fechar tarefa quando não houver actividade numa coluna especifica', + '%s removed a subtask for the task #%d' => '%s removeu uma sub-tarefa da tarefa #%d', + '%s removed a comment on the task #%d' => '%s removeu um comentário da tarefa #%d ', + 'Comment removed on task #%d' => 'Comentário removido da tarefa #%d', + 'Subtask removed on task #%d' => 'Sub-tarefa removida da tarefa #%d', + 'Hide tasks in this column in the dashboard' => 'Esconder do meu painel tarefas nesta coluna', + '%s removed a comment on the task %s' => '%s removeu um comentário da tarefa %s', + '%s removed a subtask for the task %s' => '%s removeu uma sub-tarefa da tarefa %s', + 'Comment removed' => 'Comentário removido', + 'Subtask removed' => 'Sub-tarefa removida', + '%s set a new internal link for the task #%d' => '%s definiu uma nova ligação interna para a tarefa #%d', + '%s removed an internal link for the task #%d' => '%s removeu uma ligação interna da tarefa #%d', + 'A new internal link for the task #%d has been defined' => 'Uma nova ligação para a tarea #%d foi definida', + 'Internal link removed for the task #%d' => 'Ligação interna removida da tarefa #%d', + '%s set a new internal link for the task %s' => '%s definiu uma nova ligação interna para a tarefa %s', + '%s removed an internal link for the task %s' => '%s removeu uma ligação interna da tarefa %s', + 'Automatically set the due date on task creation' => 'Definir data de vencimento automáticamente ao criar uma tarefa', + 'Move the task to another column when closed' => 'Mover a tarefa para outra coluna quando fechada', + 'Move the task to another column when not moved during a given period' => 'Mover a tarefa para outra coluna quando não movida dentro de determinado periodo', + 'Dashboard for %s' => 'Painel de %s', + 'Tasks overview for %s' => 'Vista geral das tarefas de %s', + 'Subtasks overview for %s' => 'Vista geral das sub-tarefas de %s', + 'Projects overview for %s' => 'Vista geral dos projetos de %s', + 'Activity stream for %s' => 'Fluxo de actividade de %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Atribuir uma cor quando a tarefa é movida para uma swimlane especifica', + 'Assign a priority when the task is moved to a specific swimlane' => 'Atribuir uma prioridade quando a tarefa é movida para uma swimlane especifica', + 'User unlocked successfully.' => 'Utilizador desbloqueado com sucesso.', + 'Unable to unlock the user.' => 'Não foi possivel desbloquear o utilizador.', + 'Move a task to another swimlane' => 'Mover a tarefa para outra swimlane', + 'Creator Name' => 'Nome do Criador', + 'Time spent and estimated' => 'Tempo gasto e estimado', + 'Move position' => 'Mover posição', + 'Move task to another position on the board' => 'Mover tarefa para outra posição no quadro', + 'Insert before this task' => 'Inserir antes desta tarefa', + 'Insert after this task' => 'Inserir depois desta tarefa', + 'Unlock this user' => 'Desbloquear este utilizador', + 'Custom Project Roles' => 'Funções Personalizadas do Projeto', + 'Add a new custom role' => 'Adicionar uma nova função personalizada', + 'Restrictions for the role "%s"' => 'Restrições para a função: "%s"', + 'Add a new project restriction' => 'Adicionar uma nova restrição de Projeto', + 'Add a new drag and drop restriction' => 'Adicionar uma nova restrição de arrastar e largar', + 'Add a new column restriction' => 'Adicionar uma nova restrição de colunas', + 'Edit this role' => 'Editar esta função', + 'Remove this role' => 'Remover esta função', + 'There is no restriction for this role.' => 'Não existem restrições para esta função.', + 'Only moving task between those columns is permitted' => 'Só é permitido mover a tarefa entre as seguintes colunas', + 'Close a task in a specific column when not moved during a given period' => 'Fecha a tarefa numa coluna especifica quando não é movimentada durante um dado periodo', + 'Edit columns' => 'Editar colunas', + 'The column restriction has been created successfully.' => 'A restrição de coluna foi criada com sucesso.', + 'Unable to create this column restriction.' => 'Não foi possivel criar esta restrição de coluna.', + 'Column restriction removed successfully.' => 'Restrição de coluna removida com sucesso.', + 'Unable to remove this restriction.' => 'Não foi possivel remover esta restrição.', + 'Your custom project role has been created successfully.' => 'O sua função de projeto personalizada foi criada com sucesso.', + 'Unable to create custom project role.' => 'Não foi possivel criar a função de projeto personalizada.', + 'Your custom project role has been updated successfully.' => 'A sua função de projeto personalizada foi atualizada com sucesso.', + 'Unable to update custom project role.' => 'Não foi possivel atualizar a função de projeto personalizada.', + 'Custom project role removed successfully.' => 'Função de projeto removida com sucesso.', + 'Unable to remove this project role.' => 'Não foi possivel remover esta função de projeto.', + 'The project restriction has been created successfully.' => 'A restrição de projeto foi criada com sucesso.', + 'Unable to create this project restriction.' => 'Não foi possivel remover esta restrição de projeto.', + 'Project restriction removed successfully.' => 'Restrição de projeto removida com sucesso.', + 'You cannot create tasks in this column.' => 'Não pode criar tarefas nesta coluna.', + 'Task creation is permitted for this column' => 'A criação de tarefas é permitida nesta coluna', + 'Closing or opening a task is permitted for this column' => 'Fechar ou abrir tarefas é permitido nesta coluna', + 'Task creation is blocked for this column' => 'A criação de tarefas está bloqueada nesta coluna', + 'Closing or opening a task is blocked for this column' => 'Fechar ou abrir tarefas está bloqueado nesta coluna', + 'Task creation is not permitted' => 'A criação de tarefas não é permitida', + 'Closing or opening a task is not permitted' => 'Fechar ou abrir tarefas não é permitido', + 'New drag and drop restriction for the role "%s"' => 'Nova restrição de arrastar e largar para a função: "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Pessoas pertecentes a esta função poderam mover terefas só entre as colunas de origem e de destino.', + 'Remove a column restriction' => 'Remover a restrição de coluna', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Tem a certeza que quer remover a restrição de coluna: "%s" para "%s"?', + 'New column restriction for the role "%s"' => 'Nova restrição de coluna para a função: "%s"', + 'Rule' => 'Regra', + 'Do you really want to remove this column restriction?' => 'Tem a certeza que quer remover esta restrição de coluna?', + 'Custom roles' => 'Funções personalizadas', + 'New custom project role' => 'Nova função de projeto personalizada', + 'Edit custom project role' => 'Editar função de projeto personalizada', + 'Remove a custom role' => 'Remover função de projeto personalizada', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Tem a certeza que quer remover esta função personalizada: "%s"? Todas as pessoas atribuídas a esta função ficarão membros do projecto.', + 'There is no custom role for this project.' => 'Não existem funções personalizadas para este projeto', + 'New project restriction for the role "%s"' => 'Nova restrição de projeto para a função: "%s"', + 'Restriction' => 'Restrição', + 'Remove a project restriction' => 'Remover uma restrição de projeto', + 'Do you really want to remove this project restriction: "%s"?' => 'Tem a certeza que quer remover a restrição de projeto: "%s"?', + 'Duplicate to multiple projects' => 'Duplicar para vários projetos', + 'This field is required' => 'Este campo é obrigatório', + 'Moving a task is not permitted' => 'Não é permitido mover uma tarefa', + 'This value must be in the range %d to %d' => 'Este valor deve estar entre %d e %d', + 'You are not allowed to move this task.' => 'Não lhe é permitido mover esta tarefa.', + 'API User Access' => 'Acesso de Utilizador ao API', + 'Preview' => 'Pré-Visualizar', + 'Write' => 'Escrever', + 'Write your text in Markdown' => 'Escreva o seu texto em Markdown', + 'No personal API access token registered.' => 'Nenhum token pessoal de acesso ao API ', + 'Your personal API access token is "%s"' => 'O seu token de acesso pessoal ao API é "%s"', + 'Remove your token' => 'Remover o seu token', + 'Generate a new token' => 'Gerar um novo token', + 'Showing %d-%d of %d' => 'A mostrar %d-%d de %d', + 'Outgoing Emails' => 'E-mails de saída', + 'Add or change currency rate' => 'Adicionar ou alterar taxa da moeda', + 'Reference currency: %s' => 'Moeda de referência: %s', + 'Add custom filters' => 'Adicionar filtros personalizados', + 'Export' => 'Exportar', + 'Add link label' => 'Adicionar etiqueta de associação', + 'Incompatible Plugins' => 'Plugins incompatíveis', + 'Compatibility' => 'Compatibilidade', + 'Permissions and ownership' => 'Permissões e propriedade', + 'Priorities' => 'Prioridades', + 'Close this window' => 'Feche esta janela', + 'Unable to upload this file.' => 'Não foi possível enviar este ficheiro.', + 'Import tasks' => 'Importar tarefas', + 'Choose a project' => 'Escolha um projeto', + 'Profile' => 'Perfil', + 'Application role' => 'Função na Aplicação', + '%d invitations were sent.' => '%d convites foram enviados.', + '%d invitation was sent.' => '%d convite foi enviado.', + 'Unable to create this user.' => 'Não foi possível criar este utilizador.', + 'Kanboard Invitation' => 'Convite de Kanboard', + 'Visible on dashboard' => 'Visível no painel', + 'Created at:' => 'Criado a:', + 'Updated at:' => 'Atualizado a:', + 'There is no custom filter.' => 'Não existe nenhum filtro personalizado.', + 'New User' => 'Novo Utilizador', + 'Authentication' => 'Autenticação', + 'If checked, this user will use a third-party system for authentication.' => 'Se selecionado, este utilizador irá utilizar um serviços de terceiros para autenticação.', + 'The password is necessary only for local users.' => 'A password só é necessária para utilizadores locais.', + 'You have been invited to register on Kanboard.' => 'Foi convidado para se registrar no Kanboard.', + 'Click here to join your team' => 'Clique aqui para se juntar à sua equipa', + 'Invite people' => 'Convidar pessoas', + 'Emails' => 'E-mails', + 'Enter one email address by line.' => 'Insira um endereço de e-mail por linha.', + 'Add these people to this project' => 'Adicione estas pessoas a este projeto', + 'Add this person to this project' => 'Adicione esta pessoa a este projeto', + 'Sign-up' => 'Registe-se', + 'Credentials' => 'Credenciais', + 'New user' => 'Novo utilizador', + 'This username is already taken' => 'Este nome de utilizador já está a ser utilizado', + 'Your profile must have a valid email address.' => 'O seu perfil deve ter um endereço de email válido.', + 'TRL - Turkish Lira' => 'TRL - Lira Turca', + 'The project email is optional and could be used by several plugins.' => 'O e-mail do projeto é opcional e pode ser usado por vários plugins.', + 'The project email must be unique across all projects' => 'O e-mail do projeto tem de ser único em todos os projetos', + 'The email configuration has been disabled by the administrator.' => 'A configuração de e-mail foi desativada pelo administrador.', + 'Close this project' => 'Fechar este projeto', + 'Open this project' => 'Abrir este projeto', + 'Close a project' => 'Fechar um projeto', + 'Do you really want to close this project: "%s"?' => 'Deseja realmente fechar este projeto: "%s"?', + 'Reopen a project' => 'Reabrir um projeto', + 'Do you really want to reopen this project: "%s"?' => 'Deseja mesmo reabrir este projecto?: "%s"?', + 'This project is open' => 'Este projeto está aberto', + 'This project is closed' => 'Este projecto está fechado', + 'Unable to upload files, check the permissions of your data folder.' => 'Impossivel enviar ficheiros, verifique as permissões da pasta data', + 'Another category with the same name exists in this project' => 'Outra categoria com o mesmo nome já existe neste projecto', + 'Comment sent by email successfully.' => 'Comentário enviado por email com sucesso.', + 'Sent by email to "%s" (%s)' => 'Enviado por email para "%s" (%s)', + 'Unable to read uploaded file.' => 'Não foi possivel ler ficheiro enviado.', + 'Database uploaded successfully.' => 'Base de dados enviada com sucesso.', + 'Task sent by email successfully.' => 'Tarefa enviada por email com sucesso.', + 'There is no category in this project.' => 'Não existe categorias neste projecto.', + 'Send by email' => 'Enviar por email', + 'Create and send a comment by email' => 'Criar e enviar um comentário por email', + 'Subject' => 'Assunto', + 'Upload the database' => 'Enviar a base de dados', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Poderia enviar a base de dados Sqlite transferida anteriormente (formato Gzip).', + 'Database file' => 'Ficheiro base de dados', + 'Upload' => 'Enviar', + 'Your project must have at least one active swimlane.' => 'O seu projecto deve ter pelo menos uma swimlane activa.', + 'Project: %s' => 'Projecto: %s', + 'Automatic action not found: "%s"' => 'Acção automática não encontrada: "%s"', + '%d projects' => '%d projetos', + '%d project' => '%d projecto', + 'There is no project.' => 'Não existe projecto.', + 'Sort' => 'Ordenar', + 'Project ID' => 'ID do Projecto', + 'Project name' => 'Nome do Projecto', + 'Public' => 'Público', + 'Personal' => 'Privado', + '%d tasks' => '%d tarefas', + '%d task' => '%d tarefa', + 'Task ID' => 'ID da Tarefa', + 'Assign automatically a color when due date is expired' => 'Atribuir automaticamente uma cor quando a data de vencimento expirar', + 'Total score in this column across all swimlanes' => 'Pontuação total nesta coluna em todos os swimlanes', + 'HRK - Kuna' => 'HRK - Kuna Croata', + 'ARS - Argentine Peso' => 'ARS - Peso Argentino', + 'COP - Colombian Peso' => 'COP - Peso Colombiano', + '%d groups' => '%d grupos', + '%d group' => '%d grupo', + 'Group ID' => 'ID do Grupo', + 'External ID' => 'ID Externo', + '%d users' => '%d utilizadores', + '%d user' => '%d utilizador', + 'Hide subtasks' => 'Esconder subtarefas', + 'Show subtasks' => 'Mostrar subtarefas', + 'Authentication Parameters' => 'Parâmetros de autenticação', + 'API Access' => 'Acesso à API', + 'No users found.' => 'Não foram encontrados utilizadores.', + 'User ID' => 'ID de Utilizador', + 'Notifications are activated' => 'Notificações ativadas', + 'Notifications are disabled' => 'Notificações desativadas', + 'User disabled' => 'Utilizador desactivado', + '%d notifications' => '%d notificações', + '%d notification' => '%d notificação', + 'There is no external integration installed.' => 'Não há integração externa instalada.', + 'You are not allowed to update tasks assigned to someone else.' => 'Não tem permissão para atualizar tarefas atribuídas a outra pessoa.', + 'You are not allowed to change the assignee.' => 'Não tem permissão para alterar a atribuição', + 'Task suppression is not permitted' => 'A supressão de tarefas não é permitida', + 'Changing assignee is not permitted' => 'Não é permitido alterar a atribuição', + 'Update only assigned tasks is permitted' => 'Apenas é permitido atualizar as tarefas atribuídas', + 'Only for tasks assigned to the current user' => 'Apenas para tarefas atribuídas ao utilizador atual', + 'My projects' => 'Meus projetos', + 'You are not a member of any project.' => 'Não é membro de nenhum projeto.', + 'My subtasks' => 'Minhas subtarefas', + '%d subtasks' => '%d subtarefas', + '%d subtask' => '%d subtarefa', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Apenas é permitido mover a tarefa entre essas colunas para tarefas atribuídas ao utilizador atual', + '[DUPLICATE]' => '[DUPLICADA]', + 'DKK - Danish Krona' => 'DKK - Coroa Dinamarquesa', + 'Remove user from group' => 'Remover utilizador do grupo', + 'Assign the task to its creator' => 'Atribuir a tarefa ao seu criador', + 'This task was sent by email to "%s" with subject "%s".' => 'Esta tarefa foi enviada por email para "%s" com o assunto "%s".', + 'Predefined Email Subjects' => 'Assuntos de email predefinidos', + 'Write one subject by line.' => 'Escreva um assunto por linha.', + 'Create another link' => 'Criar outro link', + 'BRL - Brazilian Real' => 'BRL - Real Brasileiro', + 'Add a new Kanboard task' => 'Adicionar nova tarefa Kanboard', + 'Subtask not started' => 'Sub-tarefa não iniciada', + 'Subtask currently in progress' => 'Sub-tarefa em progresso', + 'Subtask completed' => 'Sub-tarefa concluida', + 'Subtask added successfully.' => 'Sub-tarefa adicionada com sucesso.', + '%d subtasks added successfully.' => '%d sub-tarefas adicionadas com sucesso.', + 'Enter one subtask by line.' => 'Escreva uma sub-tarefa por linha.', + 'Predefined Contents' => 'Conteudos Predefinidos', + 'Predefined contents' => 'Conteudos predefinidos', + 'Predefined Task Description' => 'Descricção de Tarefa Predefinida', + 'Do you really want to remove this template? "%s"' => 'Tem a certeza que pretende remover este template? "%s"', + 'Add predefined task description' => 'Adicionar descrição predefinida de tarefa', + 'Predefined Task Descriptions' => 'Descrições Predefinidas de Tarefa', + 'Template created successfully.' => 'Template criado com sucesso.', + 'Unable to create this template.' => 'Não foi possivel criar este template.', + 'Template updated successfully.' => 'Template actualizado com sucesso.', + 'Unable to update this template.' => 'Não foi possivel actualizar este template.', + 'Template removed successfully.' => 'Template removido com sucesso.', + 'Unable to remove this template.' => 'Não foi possivel remover este template.', + 'Template for the task description' => 'Template para descrição de tarefa', + 'The start date is greater than the end date' => 'A data de inicio é maior que a data de fim', + 'Tags must be separated by a comma' => 'Etiquetas tem de ser separadas por virgula', + 'Only the task title is required' => 'Apenas o titulo da tarefa é necessário', + 'Creator Username' => 'Utilizador do Criador', + 'Color Name' => 'Nome da Cor', + 'Column Name' => 'Noma da Coluna', + 'Swimlane Name' => 'Nome da Swimlane', + 'Time Estimated' => 'Tempo Estimado', + 'Time Spent' => 'Tempo Gasto', + 'External Link' => 'Ligação Externa', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Esta característica activa a feed do iCal, a feed do RSS e a vista publica do quadro.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Parar o temporizador de todas as sub-tarefas quando a tarefa for movida para outra coluna', + 'Subtask Title' => 'Titulo de sub-tarefa', + 'Add a subtask and activate the timer when moving a task to another column' => 'Adicionar uma sub-tarefa e activar o temporizador quando a tarefa for movida para outra coluna', + 'days' => 'dias', + 'minutes' => 'minutos', + 'seconds' => 'segundos', + 'Assign automatically a color when preset start date is reached' => 'Atribuir automaticamente uma cor quando a data de início for atingida', + 'Move the task to another column once a predefined start date is reached' => 'Mover a tarefa para outra coluna quando a data de início for atingida', + 'This task is now linked to the task %s with the relation "%s"' => 'Esta tarefa está agora ligada à tarefa %s com a relação "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'A ligação com a relação "%s" à tarefa %s foi removida', + 'Custom Filter:' => 'Filtro Personalizado:', + 'Unable to find this group.' => 'Não foi possivel encontrar este grupo.', + '%s moved the task #%d to the column "%s"' => '%s moveu a tarefa #%d para a coluna "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => '%s moveu a tarefa #%d para a posição %d na coluna "%s"', + '%s moved the task #%d to the swimlane "%s"' => '%s moveu a tarefa #%d para a swimlane "%s"', + '%sh spent' => '%sh gastas', + '%sh estimated' => '%sh estimadas', + 'Select All' => 'Selecionar Tudo', + 'Unselect All' => 'Desmarcar Tudo', + 'Apply action' => 'Aplicar ação', + 'Move selected tasks to another column or swimlane' => 'Mover tarefas selecionadas para outra coluna', + 'Edit tasks in bulk' => 'Editar tarefas em massa', + 'Choose the properties that you would like to change for the selected tasks.' => 'Escolha as propriedades que pretente modificar nas tarefas selecionadas.', + 'Configure this project' => 'Configurar este projeto', + 'Start now' => 'Começar agora', + '%s removed a file from the task #%d' => '%s removeu um ficheiro da tarefa #%d', + 'Attachment removed from task #%d: %s' => 'Anexo removido da tarefa #%d: %s', + 'No color' => 'Sem cor', + 'Attachment removed "%s"' => 'Anexo removido "%s"', + '%s removed a file from the task %s' => '%s removeu um ficheiro da tarefa %s', + 'Move the task to another swimlane when assigned to a user' => 'Mover a tarefa para outra swimlane quando assignada a um utilizador', + 'Destination swimlane' => 'Swimlane de destino', + 'Assign a category when the task is moved to a specific swimlane' => 'Assignar uma categoria quando a tarefa é movida para um swimlane especifica', + 'Move the task to another swimlane when the category is changed' => 'Mover a tarefa para outra swimlane quando a categoria é alterada', + 'Reorder this column by priority (ASC)' => 'Reordenar esta coluna por prioridade (ASC)', + 'Reorder this column by priority (DESC)' => 'Reordenar esta coluna por prioridade (DESC)', + 'Reorder this column by assignee and priority (ASC)' => 'Reordenar esta coluna por assignado e prioridade (ASC)', + 'Reorder this column by assignee and priority (DESC)' => 'Reordenar esta coluna por assignado e prioridade (DESC)', + 'Reorder this column by assignee (A-Z)' => 'Reordenar esta coluna por assignado (A-Z)', + 'Reorder this column by assignee (Z-A)' => 'Reordenar esta coluna por assignado (Z-A)', + 'Reorder this column by due date (ASC)' => 'Reordenar esta coluna por data de vencimento (ASC)', + 'Reorder this column by due date (DESC)' => 'Reordenar esta coluna por data de vencimento (DESC)', + 'Reorder this column by id (ASC)' => 'Reordenar esta coluna por id (ASC)', + 'Reorder this column by id (DESC)' => 'Reordenar esta coluna por id (DESC)', + '%s moved the task #%d "%s" to the project "%s"' => '%s moveu a tarefa #%d "%s" para o projeto "%s"', + 'Task #%d "%s" has been moved to the project "%s"' => 'Tarefa #%d "%s" foi movida para o projeto "%s"', + 'Move the task to another column when the due date is less than a certain number of days' => 'Mover a tarefa para outra coluna quando a data de vencimento é menos que um certo numero de dias', + 'Automatically update the start date when the task is moved away from a specific column' => 'Automaticamente atualizar a data de inicio quando a tarefa for movida de uma certa coluna', + 'HTTP Client:' => 'Cliente HTTP:', + 'Assigned' => 'Assignado', + 'Task limits apply to each swimlane individually' => 'Limites de tarefa aplicam-se a cada swimlane individualmente', + 'Column task limits apply to each swimlane individually' => 'Limites de tarefas da coluna aplicam-se a cada swimlane individualmente', + 'Column task limits are applied to each swimlane individually' => 'Limites de tarefas da coluna são aplicadas a cada swimlane individualmente', + 'Column task limits are applied across swimlanes' => 'Limites de tarefas da coluna são aplicados a todas a swimlanes', + 'Task limit: ' => 'Limite da tarefa: ', + 'Change to global tag' => 'Alterar para etiqueta global', + 'Do you really want to make the tag "%s" global?' => 'Tem a certeza que pretende por a etiqueta "%s" como global?', + 'Enable global tags for this project' => 'Activar etiquetas globais para este projeto', + 'Group membership(s):' => 'Membro(s) do grupo:', + '%s is a member of the following group(s): %s' => '%s é membro do(s) seguinte(s) grupo(s): %s', + '%d/%d group(s) shown' => '%d/%d grupo(s) mostrado(s)', + 'Subtask creation or modification' => 'Alteração ou criação de sub-tarefa', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Assignar a tarefa a um utilizador especificado quando a tarefa é movida para uma swimlane especificada', + 'Comment' => 'Comentário', + 'Collapse vertically' => 'Colapsar verticalmente', + 'Expand vertically' => 'Expandir verticalmente', + 'MXN - Mexican Peso' => 'MXN - Peso Mexicano', + 'Estimated vs actual time per column' => 'Tempo estimado vs real por coluna', + 'HUF - Hungarian Forint' => 'HUF - Florim Húngaro', + 'XBT - Bitcoin' => 'XBT - Bitcoin', + 'You must select a file to upload as your avatar!' => 'Tem selecionar um ficheiro para carregar como seu avatar!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'O ficheiro que carregou não uma imagem válida! (Apenas *.gif, *.jpg, *.jpeg e *.png são permitidas!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Definir automaticamente a data de vencimento quando a tarefa for arrastada de uma coluna específica', + 'No other projects found.' => 'Nenhum outro projeto encontrado.', + 'Tasks copied successfully.' => 'Tarefas copiadas com sucesso.', + 'Unable to copy tasks.' => 'Não foi possível copiar as tarefas.', + 'Theme' => 'Tema', + 'Theme:' => 'Tema:', + 'Light theme' => 'Tema claro', + 'Dark theme' => 'Tema escuro', + 'Automatic theme - Sync with system' => 'Tema automático – Sincronizar com o sistema', + 'Application managers or more' => 'Gestores da aplicação ou superior', + 'Administrators' => 'Administradores', + 'Visibility:' => 'Visibilidade:', + 'Standard users' => 'Utilizadores padrão', + 'Visibility is required' => 'A visibilidade é obrigatória', + 'The visibility should be an app role' => 'A visibilidade deve ser um papel da aplicação', + 'Reply' => 'Responder', + '%s wrote: ' => '%s escreveu: ', + 'Number of visible tasks in this column and swimlane' => 'Número de tarefas visíveis nesta coluna e faixa', + 'Number of tasks in this swimlane' => 'Número de tarefas nesta faixa', + 'Unable to find another subtask in progress, you can close this window.' => 'Não foi possível encontrar outra subtarefa em progresso, pode fechar esta janela.', + 'This theme is invalid' => 'Este tema é inválido', + 'This role is invalid' => 'Este papel é inválido', + 'This timezone is invalid' => 'Este fuso horário é inválido', + 'This language is invalid' => 'Este idioma é inválido', + 'This URL is invalid' => 'Este URL é inválido', + 'Date format invalid' => 'Formato de data inválido', + 'Time format invalid' => 'Formato de hora inválido', + 'Invalid Mail transport' => 'Transporte de e-mail inválido', + 'Color invalid' => 'Cor inválida', + 'This value must be greater or equal to %d' => 'Este valor deve ser maior ou igual a %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Adicionar um BOM no início do ficheiro (necessário para Microsoft Excel)', + 'Just add these tag(s)' => 'Apenas adicione estas etiquetas', + 'Remove internal link(s)' => 'Remover ligações internas', + 'Import tasks from another project' => 'Importar tarefas de outro projeto', + 'Select the project to copy tasks from' => 'Selecionar o projeto de onde copiar as tarefas', + 'The total maximum allowed attachments size is %sB.' => 'O tamanho máximo total permitido para anexos é de %sB.', + 'Add attachments' => 'Adicionar anexos', + 'Task #%d "%s" is overdue' => 'Tarefa #%d "%s" está atrasada', + 'Enable notifications by default for all new users' => 'Ativar notificações por padrão para todos os novos usuários', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Atribuir a tarefa ao seu criador para colunas específicas se nenhum responsável for definido manualmente', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Atribuir a tarefa ao utilizador com sessão iniciada ao mudar de coluna para a coluna especificada se nenhum utilizador estiver atribuído', +]; diff --git a/app/Locale/ro_RO/translations.php b/app/Locale/ro_RO/translations.php new file mode 100644 index 0000000..9abd233 --- /dev/null +++ b/app/Locale/ro_RO/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => '.', + 'number.thousands_separator' => ' ', + 'None' => 'Nimic', + 'Edit' => 'Modifică', + 'Remove' => 'Șterge', + 'Yes' => 'Da', + 'No' => 'Nu', + 'cancel' => 'anulare', + 'or' => 'sau', + 'Yellow' => 'Galben', + 'Blue' => 'Albastru', + 'Green' => 'Verde', + 'Purple' => 'Violet', + 'Red' => 'Roșu', + 'Orange' => 'Portocaliu', + 'Grey' => 'Gri', + 'Brown' => 'Maro', + 'Deep Orange' => 'Portocaliu închis', + 'Dark Grey' => 'Gri închis', + 'Pink' => 'Roz', + 'Teal' => 'Turcoaz', + 'Cyan' => 'Bleu', + 'Lime' => 'Lime', + 'Light Green' => 'Verde deschis', + 'Amber' => 'Ambră', + 'Save' => 'Salvează', + 'Login' => 'Conectare', + 'Official website:' => 'Site web oficial:', + 'Unassigned' => 'Neatribuit', + 'View this task' => 'Vezi sarcina aceasta', + 'Remove user' => 'Șterge utilizator', + 'Do you really want to remove this user: "%s"?' => 'Vrei să ștergi acest utilizator: "%s" ?', + 'All users' => 'Toți utilizatorii', + 'Username' => 'Nume utilizator', + 'Password' => 'Parolă', + 'Administrator' => 'Administrator', + 'Sign in' => 'Conectare', + 'Users' => 'Utilizatori', + 'Forbidden' => 'Interzis', + 'Access Forbidden' => 'Acces interzis', + 'Edit user' => 'Modifică utilizator', + 'Logout' => 'Deconectare', + 'Bad username or password' => 'Utilizator sau parolă incorectă', + 'Edit project' => 'Modifică proiect', + 'Name' => 'Nume', + 'Projects' => 'Proiecte', + 'No project' => 'Fără proiect', + 'Project' => 'Proiect', + 'Status' => 'Stare', + 'Tasks' => 'Sarcini', + 'Board' => 'Bord', + 'Actions' => 'Actiuni', + 'Inactive' => 'Inactiv', + 'Active' => 'Activ', + 'Unable to update this board.' => 'Nu am putut actualiza acest bord.', + 'Disable' => 'Dezactivează', + 'Enable' => 'Activează', + 'New project' => 'Proiect nou', + 'Do you really want to remove this project: "%s"?' => 'Vrei să ștergi acest proiect: "%s" ?', + 'Remove project' => 'Șterge proiect', + 'Edit the board for "%s"' => 'Modifică bordul pentru "%s"', + 'Add a new column' => 'Adaugă o coloană nouă', + 'Title' => 'Titlu', + 'Assigned to %s' => 'Atribuie lui %s', + 'Remove a column' => 'Șterge o coloană', + 'Unable to remove this column.' => 'Nu pot șterge această coloană.', + 'Do you really want to remove this column: "%s"?' => 'Vrei să ștergi acestă coloană: "%s" ?', + 'Settings' => 'Preferințe', + 'Application settings' => 'Preferințele aplicației', + 'Language' => 'Limbă', + 'Webhook token:' => 'Token de securitate pentru webhook:', + 'API token:' => 'Token de securitate pentru API:', + 'Database size:' => 'Dimensiune bază de date:', + 'Download the database' => 'Descarcă baza de date', + 'Optimize the database' => 'Optimizează baza de date', + '(VACUUM command)' => '(Comanda VACUUM)', + '(Gzip compressed Sqlite file)' => '(Fișier Sqlite comprimat cu Gzip)', + 'Close a task' => 'Închide o sarcină', + 'Column' => 'Coloană', + 'Color' => 'Culoare', + 'Assignee' => 'Persoană desemnată', + 'Create another task' => 'Creează altă sarcină', + 'New task' => 'Sarcină nouă', + 'Open a task' => 'Deschide o sarcină', + 'Do you really want to open this task: "%s"?' => 'Vrei să deschizi sarcina: "%s" ?', + 'Back to the board' => 'Înapoi la bord', + 'There is nobody assigned' => 'Nu este desemnată o persoană', + 'Column on the board:' => 'Coloană pe bord: ', + 'Close this task' => 'Închide sarcina', + 'Open this task' => 'Deschide Sarcina', + 'There is no description.' => 'Nu există o descriere.', + 'Add a new task' => 'Adaugă o sarcină nouă', + 'The username is required' => 'Numele este obligatoriu', + 'The maximum length is %d characters' => 'Lungimea maximă este de %d caractere', + 'The minimum length is %d characters' => 'Lungimea minimă este de %d caractere', + 'The password is required' => 'Parola este obligatorie', + 'This value must be an integer' => 'Valoarea trebuie să fie număr întreg', + 'The username must be unique' => 'Utilizatorul trebuie să fie unic', + 'The user id is required' => 'ID-ul de utilizator este obligatoriu', + 'Passwords don\'t match' => 'Parolele nu corespund', + 'The confirmation is required' => 'Confirmarea este obligatorie', + 'The project is required' => 'Proiectul este obligatoriu', + 'The id is required' => 'Identificatorul este obligatoriu', + 'The project id is required' => 'ID-ul proiectului este obligatoriu', + 'The project name is required' => 'Numele proiectului este obligatoriu', + 'The title is required' => 'Titlul este obligatoriu', + 'Settings saved successfully.' => 'Preferințele au fost salvate.', + 'Unable to save your settings.' => 'Nu am putut salva preferințele.', + 'Database optimization done.' => 'Optimizarea bazei de date s-a încheiat.', + 'Your project has been created successfully.' => 'Proiectul dumneavoastră a fost creat cu succes.', + 'Unable to create your project.' => 'Nu am putut crea proiectul dumneavoastră.', + 'Project updated successfully.' => 'Proiectul a fost actualizat.', + 'Unable to update this project.' => 'Nu am putut actualiza proiectul.', + 'Unable to remove this project.' => 'Nu am putut șterge proiectul.', + 'Project removed successfully.' => 'Proiectul a fost șters.', + 'Project activated successfully.' => 'Proiectul a fost activat.', + 'Unable to activate this project.' => 'Nu am putut activa proiectul.', + 'Project disabled successfully.' => 'Proiectul a fost dezactivat.', + 'Unable to disable this project.' => 'Nu am putut dezactiva proiectul', + 'Unable to open this task.' => 'Nu am putut deschide sarcina.', + 'Task opened successfully.' => 'Sarcina a fost deschisă', + 'Unable to close this task.' => 'Nu am putut închide sarcina.', + 'Task closed successfully.' => 'Sarcina a fost închisă.', + 'Unable to update your task.' => 'Nu am putut actualiza sarcina.', + 'Task updated successfully.' => 'Sarcina fost actualizată.', + 'Unable to create your task.' => 'Nu am putut crea sarcina.', + 'Task created successfully.' => 'Sarcina a fost creată.', + 'User created successfully.' => 'Utilizatorul a fost creat.', + 'Unable to create your user.' => 'Nu am putut crea utilizatorul.', + 'User updated successfully.' => 'Utilizatorul a fost actualizat.', + 'User removed successfully.' => 'Utilizatorul a fost șters.', + 'Unable to remove this user.' => 'Nu am putut șterge utilizatorul.', + 'Board updated successfully.' => 'Bordul a fost actualizat.', + 'Ready' => 'Pregătit', + 'Backlog' => 'Restant', + 'Work in progress' => 'În desfășurare', + 'Done' => 'Finalizat', + 'Application version:' => 'Versiunea aplicației:', + 'Id' => 'Id', + 'Public link' => 'Legătură publică', + 'Timezone' => 'Fus orar', + 'Sorry, I didn\'t find this information in my database!' => 'Scuze, nu am găsit informația asta în baza mea de date!', + 'Page not found' => 'Pagina nu a fost găsită', + 'Complexity' => 'Complexitate', + 'Task limit' => 'Limită sarcini.', + 'Task count' => 'Număr de sarcini', + 'User' => 'Utilizator', + 'Comments' => 'Comentarii', + 'Comment is required' => 'Comentariul este obligatoriu', + 'Comment added successfully.' => 'Comentariul a fost adăugat', + 'Unable to create your comment.' => 'Nu am putut crea comentariul.', + 'Due Date' => 'Data scadentă', + 'Invalid date' => 'Dată invalidă', + 'Automatic actions' => 'Acțiuni automatizate', + 'Your automatic action has been created successfully.' => 'Acțiunea automatizată a fost creată.', + 'Unable to create your automatic action.' => 'Nu am putut crea acțiunea automatizată.', + 'Remove an action' => 'Șterge o acțiune', + 'Unable to remove this action.' => 'Nu am putut șterge acțiunea', + 'Action removed successfully.' => 'Acțiunea a fost ștearsă.', + 'Automatic actions for the project "%s"' => 'Acțiuni automatizate pentru proiectul "%s"', + 'Add an action' => 'Adaugă o acțiune', + 'Event name' => 'Nume eveniment', + 'Action' => 'Acțiune', + 'Event' => 'Eveniment', + 'When the selected event occurs execute the corresponding action.' => 'Când are loc evenimentul ales execută acțiunea corespunzătoare.', + 'Next step' => 'Pasul următor', + 'Define action parameters' => 'Definește parametrii acțiunii', + 'Do you really want to remove this action: "%s"?' => 'Vrei să ștergi acțiunea: "%s" ?', + 'Remove an automatic action' => 'Șterge o acțiune automatizată', + 'Assign the task to a specific user' => 'Atribuie sarcina unui utilizator specific', + 'Assign the task to the person who does the action' => 'Atribuie sarcina persoanei care îndeplinește acțiunea', + 'Duplicate the task to another project' => 'Duplichează sarcina în alt proiect', + 'Move a task to another column' => 'Mută sarcina în altă coloană', + 'Task modification' => 'Modificare sarcină', + 'Task creation' => 'Creare sarcină', + 'Closing a task' => 'Închidere sarcină', + 'Assign a color to a specific user' => 'Atribuie o culoare unui utilizator specific', + 'Position' => 'Poziție', + 'Duplicate to project' => 'Duplichează în alt proiect', + 'Duplicate' => 'Duplicare', + 'Link' => 'Legătură', + 'Comment updated successfully.' => 'Comentariu actualizat.', + 'Unable to update your comment.' => 'Nu am putut actualiza comentariul.', + 'Remove a comment' => 'Șterge un comentariu', + 'Comment removed successfully.' => 'Comentariul a fost șters.', + 'Unable to remove this comment.' => 'Nu am putut șterge comentariul.', + 'Do you really want to remove this comment?' => 'Vrei să ștergi acest comentariu?', + 'Current password for the user "%s"' => 'Parola actuală pentru utilizatorul "%s"', + 'The current password is required' => 'Parola actuală este obligatorie', + 'Wrong password' => 'Parolă greșită', + 'Unknown' => 'Necunoscut', + 'Last logins' => 'Ultimele conectări', + 'Login date' => 'Data conectării', + 'Authentication method' => 'Metodă de autentificare', + 'IP address' => 'Adresă IP', + 'User agent' => 'Agent utilizator', + 'Persistent connections' => 'Conexiuni persistente', + 'No session.' => 'Fără sesiune.', + 'Expiration date' => 'Data expirării', + 'Remember Me' => 'Amintește-ți de mine', + 'Creation date' => 'Data creării', + 'Everybody' => 'Toată lumea', + 'Open' => 'Deschis', + 'Closed' => 'Închis', + 'Search' => 'Caută', + 'Nothing found.' => 'Nimic găsit.', + 'Due date' => 'Dată scadentă', + 'Description' => 'Descriere', + '%d comments' => '%d comentarii', + '%d comment' => '%d comentariu', + 'Email address invalid' => 'Adresă e-mail invalidă', + 'Your external account is not linked anymore to your profile.' => 'Contul tău extern nu mai este legat cu profilul.', + 'Unable to unlink your external account.' => 'Nu am putut dezlega contul tău extern.', + 'External authentication failed' => 'Autentificarea externă a eșuat', + 'Your external account is linked to your profile successfully.' => 'Contul tău extern a fost legat de profil.', + 'Email' => 'E-mail', + 'Task removed successfully.' => 'Sarcină ștearsă.', + 'Unable to remove this task.' => 'Nu pot șterge sarcina.', + 'Remove a task' => 'Șterge o sarcină', + 'Do you really want to remove this task: "%s"?' => 'Vrei să ștergi sarcina: "%s" ?', + 'Assign automatically a color based on a category' => 'Atribuie automat o culoare bazat pe categorie', + 'Assign automatically a category based on a color' => 'Atribuie automat o categorie bazat pe culoare', + 'Task creation or modification' => 'Creare sau modificare sarcină', + 'Category' => 'Categorie', + 'Category:' => 'Categorie:', + 'Categories' => 'Categorii', + 'Your category has been created successfully.' => 'Categoria a fost creată.', + 'This category has been updated successfully.' => 'Categoria a fost actualizată.', + 'Unable to update this category.' => 'Nu am putut actualiza categoria.', + 'Remove a category' => 'Șterge o categorie', + 'Category removed successfully.' => 'Categoria a fost ștearsă.', + 'Unable to remove this category.' => 'Nu am putut șterge categoria.', + 'Category modification for the project "%s"' => 'Modificarea categoriei pentru proiectul "%s"', + 'Category Name' => 'Numele categoriei', + 'Add a new category' => 'Adaugă o categorie nouă', + 'Do you really want to remove this category: "%s"?' => 'Vrei să ștergi categoria: "%s" ?', + 'All categories' => 'Toate categoriile', + 'No category' => 'Fără categorie', + 'The name is required' => 'Numele este obligatoriu', + 'Remove a file' => 'Șterge un fișier', + 'Unable to remove this file.' => 'Nu am putut șterge fișierul.', + 'File removed successfully.' => 'Fișierul a fost șters.', + 'Attach a document' => 'Atașează un document', + 'Do you really want to remove this file: "%s"?' => 'Vrei să ștergi acest fișier: "%s" ?', + 'Attachments' => 'Atașamente', + 'Edit the task' => 'Modifică sarcina', + 'Add a comment' => 'Adaugă un comentariu', + 'Edit a comment' => 'Modifică un comentariu', + 'Summary' => 'Sumar', + 'Time tracking' => 'Urmărirea timpului', + 'Estimate:' => 'Estimat:', + 'Spent:' => 'Petrecut:', + 'Do you really want to remove this sub-task?' => 'Vrei să ștergi această sub-sarcină?', + 'Remaining:' => 'Rămas:', + 'hours' => 'ore', + 'estimated' => 'estimate', + 'Sub-Tasks' => 'Sub-sarcini', + 'Add a sub-task' => 'Adaugă o sub-sarcină', + 'Original estimate' => 'Estimat original', + 'Create another sub-task' => 'Creează altă sub-sarcină', + 'Time spent' => 'Timp petrecut', + 'Edit a sub-task' => 'Modifică o sub-sarcină', + 'Remove a sub-task' => 'Șterge o sub-sarcină', + 'The time must be a numeric value' => 'Timpul trebuie să fie o valoare numerică', + 'Todo' => 'De făcut', + 'In progress' => 'În curs', + 'Sub-task removed successfully.' => 'Sub-sarcină ștearsă.', + 'Unable to remove this sub-task.' => 'Nu am putut șterge sub-sarcina.', + 'Sub-task updated successfully.' => 'Sub-sarcină actualizată.', + 'Unable to update your sub-task.' => 'Nu am putut actualiza sub-sarcina.', + 'Unable to create your sub-task.' => 'Nu am putut crea sub-sarcina.', + 'Maximum size: ' => 'Dimensiune maximă: ', + 'Display another project' => 'Afișează alt proiect', + 'Created by %s' => 'Creat de %s', + 'Tasks Export' => 'Exportă sarcini', + 'Start Date' => 'Data pornirii', + 'Execute' => 'Execută', + 'Task Id' => 'ID Sarcină', + 'Creator' => 'Creator', + 'Modification date' => 'Data modificării', + 'Completion date' => 'Data finalizării', + 'Clone' => 'Clonează', + 'Project cloned successfully.' => 'Proiectul a fost clonat.', + 'Unable to clone this project.' => 'Nu am putut clona proiectul.', + 'Enable email notifications' => 'Activează notificarile pe e-mail', + 'Task position:' => 'Poziția sarcinii:', + 'The task #%d has been opened.' => 'Sarcina #%d a fost deschisă.', + 'The task #%d has been closed.' => 'Sarcina #%d a fost închisă.', + 'Sub-task updated' => 'Sub-sarcină actualizată', + 'Title:' => 'Titlu:', + 'Status:' => 'Stare:', + 'Assignee:' => 'Atribuire:', + 'Time tracking:' => 'Gestionarea timpului:', + 'New sub-task' => 'Sub-sarcină nouă', + 'New attachment added "%s"' => 'Atașament nou adăugat "%s"', + 'New comment posted by %s' => 'Comentariu nou postat de "%s"', + 'New comment' => 'Comentariu nou', + 'Comment updated' => 'Comentariu actualizat', + 'New subtask' => 'Sub-sarcină nouă', + 'I only want to receive notifications for these projects:' => 'Vreau să primesc notificări numai pentru acele proiecte:', + 'view the task on Kanboard' => 'vezi sarcina pe Kanboard', + 'Public access' => 'Acces public', + 'Disable public access' => 'Dezactivează accesul public', + 'Enable public access' => 'Activează accesul public', + 'Public access disabled' => 'Accesul public dezactivat', + 'Move the task to another project' => 'Mută sarcina în alt proiect', + 'Move to project' => 'Mută în alt proiect', + 'Do you really want to duplicate this task?' => 'Vrei să duplichezi această sarcină?', + 'Duplicate a task' => 'Duplichează o sarcină', + 'External accounts' => 'Conturi externe', + 'Account type' => 'Tip de cont', + 'Local' => 'Local', + 'Remote' => 'La distanță', + 'Enabled' => 'Activat', + 'Disabled' => 'Dezactivat', + 'Login:' => 'Utilizator:', + 'Full Name:' => 'Nume :', + 'Email:' => 'E-mail :', + 'Notifications:' => 'Notificări:', + 'Notifications' => 'Notificări', + 'Account type:' => 'Tip de cont:', + 'Edit profile' => 'Modifică profilul', + 'Change password' => 'Schimbă parola', + 'Password modification' => 'Modificare parolă', + 'External authentications' => 'Autentificări externe', + 'Never connected.' => 'Niciodată conectat.', + 'No external authentication enabled.' => 'Nici o autentificare externă activă.', + 'Password modified successfully.' => 'Parolă modificată.', + 'Unable to change the password.' => 'Nu am putut modifica parola.', + 'Change category' => 'Schimbă categoria', + '%s updated the task %s' => '%s a actualizat sarcina %s', + '%s opened the task %s' => '%s a deschis sarcina %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s a mutat sarcina %s la poziția #%d în coloana "%s"', + '%s moved the task %s to the column "%s"' => '%s a mutat sarcina %s în coloana "%s"', + '%s created the task %s' => '%s a creat sarcina %s', + '%s closed the task %s' => '%s a închis sarcina %s', + '%s created a subtask for the task %s' => '%s a creat o sub-sarcină pentru sarcina %s', + '%s updated a subtask for the task %s' => '%s a actualizat o sub-sarcină pentru sarcina %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Atribuit lui %s cu o estimare de %s/%s h', + 'Not assigned, estimate of %sh' => 'Nimeni atribuit, estimare de %s h', + '%s updated a comment on the task %s' => '%s a actualizat u comentariu în sarcina %s', + '%s commented the task %s' => '%s a comentat în sarcina %s', + '%s\'s activity' => 'Activitatea pentru %s', + 'RSS feed' => 'Flux RSS', + '%s updated a comment on the task #%d' => '%s a actualizat un comentariu în sarcina #%d', + '%s commented on the task #%d' => '%s a comentat în sarcina #%d', + '%s updated a subtask for the task #%d' => '%s a actualizat o sub-sarcină în sarcina #%d', + '%s created a subtask for the task #%d' => '%s a creat o sub-sarcină în sarcina #%d', + '%s updated the task #%d' => '%s a actualizat sarcina #%d', + '%s created the task #%d' => '%s a creat sarcina #%d', + '%s closed the task #%d' => '%s a închis sarcina #%d', + '%s opened the task #%d' => '%s a deschis sarcina #%d', + 'Activity' => 'Activitate', + 'Default values are "%s"' => 'Valorile implicite sunt "%s"', + 'Default columns for new projects (Comma-separated)' => 'Coloane implicite pentru proiectele noi (Separate prin virgulă)', + 'Task assignee change' => 'Modificare persoană desemnată sarcinii', + '%s changed the assignee of the task #%d to %s' => '%s a schimbat persoana desemnată sarcinii #%d lui %s', + '%s changed the assignee of the task %s to %s' => '%s a schimbat persoana desemnată sarcinii %s lui %s', + 'New password for the user "%s"' => 'Parolă nouă pentru utilizatorul "%s"', + 'Choose an event' => 'Alege un eveniment', + 'Create a task from an external provider' => 'Creează o sarcină de la un furnizor extern', + 'Change the assignee based on an external username' => 'Schimbă persoana desemnată bazat pe un nume de utilizator extern', + 'Change the category based on an external label' => 'Schimbă categoria bazat pe o etichetă externă', + 'Reference' => 'Referință', + 'Label' => 'Etichetă', + 'Database' => 'Bază de date', + 'About' => 'Despre', + 'Database driver:' => 'Tip de bază de date:', + 'Board settings' => 'Preferințe bord', + 'Webhook settings' => 'Preferințe webhook', + 'Reset token' => 'Resetare token de securitate', + 'API endpoint:' => 'URL-ul pentru API:', + 'Refresh interval for personal board' => 'Interval de reîmprospătare pentru bord privat', + 'Refresh interval for public board' => 'Interval de reîmprospătare pentru bord public', + 'Task highlight period' => 'Perioada de evidențiere a sarcinii', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Durata în secunde pentru a considera o sarcină recent modificată (0 pentru dezactivare, 2 zile implicit)', + 'Frequency in second (60 seconds by default)' => 'Frecvență în secunde (60 secunde implicit)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frecvență în secunde (0 pentru dezactivare, 10 secunde implicit)', + 'Application URL' => 'URL-ul aplicației', + 'Token regenerated.' => 'Token de securitate regenerat.', + 'Date format' => 'Formatare dată', + 'ISO format is always accepted, example: "%s" and "%s"' => 'Formatul ISO este întotdeauna acceptat, exemplu: "%s" și "%s"', + 'New personal project' => 'Proiect privat nou', + 'This project is personal' => 'Acest proiect este privat', + 'Add' => 'Adaugă', + 'Start date' => 'Dată pornire', + 'Time estimated' => 'Timp estimat', + 'There is nothing assigned to you.' => 'Nu îți este atribuit nimic.', + 'My tasks' => 'Sarcinile mele', + 'Activity stream' => 'Flux de activitate', + 'Dashboard' => 'Bord', + 'Confirmation' => 'Confirmare', + 'Webhooks' => 'Webhook-uri', + 'API' => 'API', + 'Create a comment from an external provider' => 'Creează un comentariu de la un furnizor extern', + 'Project management' => 'Gestionare proiect', + 'Columns' => 'Coloane', + 'Task' => 'Sarcini', + 'Percentage' => 'Procentaj', + 'Number of tasks' => 'Număr de sarcini', + 'Task distribution' => 'Distribuția sarcinilor', + 'Analytics' => 'Analitică', + 'Subtask' => 'Sub-sarcină', + 'User repartition' => 'Repartizare utilizatori', + 'Clone this project' => 'Clonează proiectul', + 'Column removed successfully.' => 'Coloana a fost ștearsă.', + 'Not enough data to show the graph.' => 'Nu sunt destule date pentru afișarea graficului.', + 'Previous' => 'Anterior', + 'The id must be an integer' => 'ID-ul trebuie să fie un număr întreg', + 'The project id must be an integer' => 'ID-ul proiectului trebuie să fie un număr întreg', + 'The status must be an integer' => 'Starea trebuie să fie un număr întreg', + 'The subtask id is required' => 'ID-ul sub-sarcinii este obligatoriu', + 'The subtask id must be an integer' => 'ID-ul sub-sarcinii trebuie sa fie un număr întreg', + 'The task id is required' => 'ID-ul sarcinii este obligatoriu', + 'The task id must be an integer' => 'ID-ul sarcinii trebuie să fie un număr întreg', + 'The user id must be an integer' => 'ID-ul utilizatorului trebuie să fie un număr întreg', + 'This value is required' => 'Valoarea aceasta este obligatorie', + 'This value must be numeric' => 'Valoarea aceasta trebuie să fie numerică', + 'Unable to create this task.' => 'Nu am putut crea sarcina', + 'Cumulative flow diagram' => 'Diagrama fluxului cumulat', + 'Daily project summary' => 'Rezumatul zilnic al proiectului', + 'Daily project summary export' => 'Export rezumat zilnic al proiectului', + 'Exports' => 'Exporturi', + 'This export contains the number of tasks per column grouped per day.' => 'Acest export conține numărul de sarcini per coloană grupat pe zile.', + 'Active swimlanes' => 'Culoare active', + 'Add a new swimlane' => 'Adaugă culoar nou', + 'Default swimlane' => 'Culoar implicit', + 'Do you really want to remove this swimlane: "%s"?' => 'Vrei să ștergi culoarul: "%s" ?', + 'Inactive swimlanes' => 'Culoare inactive', + 'Remove a swimlane' => 'Șterge un culoar', + 'Swimlane modification for the project "%s"' => 'Modificarea culoarului pentru proiectul "%s"', + 'Swimlane removed successfully.' => 'Culoarul a fost șters', + 'Swimlanes' => 'Culoare', + 'Swimlane updated successfully.' => 'Culoar actualizat.', + 'Unable to remove this swimlane.' => 'Nu am putut șterge culoarul.', + 'Unable to update this swimlane.' => 'Nu am putut actualiza culoarul.', + 'Your swimlane has been created successfully.' => 'Culoarul a fost creat.', + 'Example: "Bug, Feature Request, Improvement"' => 'Exemplu: "Incident, Cere caracteristică, Îmbunătățire"', + 'Default categories for new projects (Comma-separated)' => 'Categorii implicite pentru proiecte noi (Separate prin virgulă)', + 'Integrations' => 'Integrări', + 'Integration with third-party services' => 'Integrări cu servicii externe', + 'Subtask Id' => 'ID sub-sarcină', + 'Subtasks' => 'Sub-sarcini', + 'Subtasks Export' => 'Export sub-sarcini', + 'Task Title' => 'Titlu sarcină', + 'Untitled' => 'Fără titlu', + 'Application default' => 'Implicit din aplicație', + 'Language:' => 'Limbă:', + 'Timezone:' => 'Fus orar:', + 'All columns' => 'Toate coloanele', + 'Next' => 'Următor', + '#%d' => '#%d', + 'All swimlanes' => 'Toate culoarele', + 'All colors' => 'Toate culorile', + 'Moved to column %s' => 'Mutat în coloana %s', + 'User dashboard' => 'Bordul utilizatorului', + 'Allow only one subtask in progress at the same time for a user' => 'Permite o singură sub-sarcină în derulare simultană pentru un utilizator', + 'Edit column "%s"' => 'Modifică coloana "%s"', + 'Select the new status of the subtask: "%s"' => 'Alege noua stare a sarcinii: "%s"', + 'Subtask timesheet' => 'Pontajul sub-sarcinii', + 'There is nothing to show.' => 'Nimic de afișat', + 'Time Tracking' => 'Urmărirea timpului', + 'You already have one subtask in progress' => 'Deja ai o sub-sarcină în desfășurare', + 'Which parts of the project do you want to duplicate?' => 'Care părți ale proiectului vrei să fie duplicate?', + 'Disallow login form' => 'Interzice formularul de autentificare', + 'Start' => 'Start', + 'End' => 'Final', + 'Task age in days' => 'Vârsta sarcinii în zile', + 'Days in this column' => 'Zile în această coloană', + '%dd' => '%d z', + 'Add a new link' => 'Adaugă legătură nouă', + 'Do you really want to remove this link: "%s"?' => 'Vrei să ștergi legătura: "%s" ?', + 'Do you really want to remove this link with task #%d?' => 'Vrei să ștergi legătura cu sarcina #%d ?', + 'Field required' => 'Câmp obligatoriu', + 'Link added successfully.' => 'Legătură adăugată.', + 'Link updated successfully.' => 'Legătură actualizată.', + 'Link removed successfully.' => 'Legătură ștearsă.', + 'Link labels' => 'Etichete de legături', + 'Link modification' => 'Modificare legătură', + 'Opposite label' => 'Etichetă opusă', + 'Remove a link' => 'Șterge o legătură', + 'The labels must be different' => 'Etichetele trebuie să difere', + 'There is no link.' => 'Nu există legătură.', + 'This label must be unique' => 'Eticheta trebuie să fie unică', + 'Unable to create your link.' => 'Nu pot crea legătura.', + 'Unable to update your link.' => 'Nu pot actualiza legătura.', + 'Unable to remove this link.' => 'Nu pot șterge legătura.', + 'relates to' => 'se leagă de', + 'blocks' => 'blochează', + 'is blocked by' => 'este blocat de', + 'duplicates' => 'duplichează', + 'is duplicated by' => 'este duplicat de', + 'is a child of' => 'este element moștenitor al', + 'is a parent of' => 'este element parental al', + 'targets milestone' => 'vizează țelul', + 'is a milestone of' => 'este un țel a', + 'fixes' => 'corectează', + 'is fixed by' => 'este corectat de', + 'This task' => 'Această sarcină', + '<1h' => '< 1 h', + '%dh' => '%d h', + 'Expand tasks' => 'Extinde sarcinile', + 'Collapse tasks' => 'Restrânge sarcinile', + 'Expand/collapse tasks' => 'Extinde/restrânge sarcinile', + 'Close dialog box' => 'Închide dialogul', + 'Submit a form' => 'Depune un formular', + 'Board view' => 'Vizualizare bord', + 'Keyboard shortcuts' => 'Scurtături de tastatură', + 'Open board switcher' => 'Deschide comutatorul de bord', + 'Application' => 'Aplicație', + 'Compact view' => 'Vizualizare restrânsă', + 'Horizontal scrolling' => 'Derulare orizontală', + 'Compact/wide view' => 'Vizualizare compactă/lată', + 'Currency' => 'Monedă', + 'Personal project' => 'Proiect privat', + 'AUD - Australian Dollar' => 'AUD - Dolar australian', + 'CAD - Canadian Dollar' => 'CAD - Dolar canadian', + 'CHF - Swiss Francs' => 'CHF - Franc elvetian', + 'Custom Stylesheet' => 'Stylesheet personalizat', + 'EUR - Euro' => 'EUR - Euro', + 'GBP - British Pound' => 'GBP - Liră sterlină', + 'INR - Indian Rupee' => 'INR - Rupie indiană', + 'JPY - Japanese Yen' => 'JPY - Yen japonez', + 'NZD - New Zealand Dollar' => 'NZD - Dolar neo-zeelandez', + 'PEN - Peruvian Sol' => 'PEN - Sol Peruvian', + 'RSD - Serbian dinar' => 'RSD - Dinar sârbesc', + 'CNY - Chinese Yuan' => 'CNY - Yuan chinez', + 'USD - US Dollar' => 'USD - Dolar american', + 'VES - Venezuelan Bolívar' => 'VES - Bolívar Venezuelan', + 'Destination column' => 'Coloana destinație', + 'Move the task to another column when assigned to a user' => 'Mută sarcina în altă coloană când este atribuită unui utilizator', + 'Move the task to another column when assignee is cleared' => 'Mută sarcina în altă coloană când este eliberată atribuirea', + 'Source column' => 'Coloana sursă', + 'Transitions' => 'Tranzitii', + 'Executer' => 'Executant', + 'Time spent in the column' => 'Timp petrecut în coloană', + 'Task transitions' => 'Tranzițiile sarcinilor', + 'Task transitions export' => 'Exportă tranzițiile sarcinilor', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Acest raport conține toate mutările de coloană pentru sarcinile cu data, utilizatorul și timpul petrecut pentru fiecare tranziție.', + 'Currency rates' => 'Rate de schimb', + 'Rate' => 'Rată', + 'Change reference currency' => 'Schimbă moneda de referință', + 'Reference currency' => 'Moneda de referință', + 'The currency rate has been added successfully.' => 'Rata de schimb a fost adăugată.', + 'Unable to add this currency rate.' => 'Nu am putut adăuga rata de schimb', + 'Webhook URL' => 'URL pentru webhook', + '%s removed the assignee of the task %s' => '%s a eliberat persoana atribuită sarcinii %s', + 'Information' => 'Informații', + 'Check two factor authentication code' => 'Verificare cod autentificare în doi factori', + 'The two factor authentication code is not valid.' => 'Codul de autentificare în doi factori nu este valid.', + 'The two factor authentication code is valid.' => 'Codul de autentificare în doi factori este valid.', + 'Code' => 'Cod', + 'Two factor authentication' => 'Autentificare în doi factori', + 'This QR code contains the key URI: ' => 'Codul QR contine URI-ul cheii: ', + 'Check my code' => 'Verifică codul meu', + 'Secret key: ' => 'Cheia secretă: ', + 'Test your device' => 'Testează dispozitivul tău', + 'Assign a color when the task is moved to a specific column' => 'Atribuie o culoare când sarcina este mutată într-o anumită coloană', + '%s via Kanboard' => '%s via Kanboard', + 'Burndown chart' => 'Graficul progresului', + 'This chart show the task complexity over the time (Work Remaining).' => 'Acest grafic arată complexitatea sarcinii de-a lungul timpului (munca rămasă).', + 'Screenshot taken %s' => 'Captură de ecran %s', + 'Add a screenshot' => 'Adaugă captură de ecran', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Faceți o captura de ecran si apăsați CTRL+V sau ⌘+V pentru a lipi aici.', + 'Screenshot uploaded successfully.' => 'Captura de ecran a fost încărcată.', + 'SEK - Swedish Krona' => 'SEK - coroană suedeză', + 'Identifier' => 'Identificator', + 'Disable two factor authentication' => 'Dezactivează autentificarea în doi factori', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Vrei să dezactivezi autentificarea în doi factori pentru utilizatorul: "%s" ?', + 'Edit link' => 'Modifică o legătură', + 'Start to type task title...' => 'Scrie titlul sarcinii…', + 'A task cannot be linked to itself' => 'O sarcină nu se poate lega de ea însăși', + 'The exact same link already exists' => 'O legătură identică există deja', + 'Recurrent task is scheduled to be generated' => 'Sarcina recurentă este programată să fie generată', + 'Score' => 'Complexitate', + 'The identifier must be unique' => 'Identificatorul trebuie să fie unic', + 'This linked task id doesn\'t exists' => 'ID-ul de sarcină legat nu există', + 'This value must be alphanumeric' => 'Valoarea trebuie să fie alfanumerică', + 'Edit recurrence' => 'Modifică recurența', + 'Generate recurrent task' => 'Generează sarcină recurentă', + 'Trigger to generate recurrent task' => 'Declanșator pentru generarea sarcinii recurente', + 'Factor to calculate new due date' => 'Factor de calculare a noii date scadente', + 'Timeframe to calculate new due date' => 'Interval de timp pentru calcularea noii date scadente', + 'Base date to calculate new due date' => 'Data de bază pentru calcularea noii date scadente', + 'Action date' => 'Data acțiunii', + 'Base date to calculate new due date: ' => 'Data de bază pentru calcularea noii date scadente: ', + 'This task has created this child task: ' => 'Sarcina aceasta a creat această sarcină-moștenitor: ', + 'Day(s)' => 'Zi(le)', + 'Existing due date' => 'Dată scadentă existentă', + 'Factor to calculate new due date: ' => 'Factor de calculare a noii date scadente: ', + 'Month(s)' => 'Lună(/i)', + 'This task has been created by: ' => 'Această sarcină a fost creată de:', + 'Recurrent task has been generated:' => 'Sarcina recurentă a fost generată:', + 'Timeframe to calculate new due date: ' => 'Interval de timp pentru calcularea noii date scadente: ', + 'Trigger to generate recurrent task: ' => 'Declanșator pentru generarea sarcinii recurente: ', + 'When task is closed' => 'Când sarcina este închisă', + 'When task is moved from first column' => 'Când sarcina este mutată din prima coloană', + 'When task is moved to last column' => 'Când sarcina este mutată în ultima coloană', + 'Year(s)' => 'An(i)', + 'Project settings' => 'Preferințe de proiect', + 'Automatically update the start date' => 'Actualizează automat data de start', + 'iCal feed' => 'Flux iCal', + 'Preferences' => 'Preferințe', + 'Security' => 'Securitate', + 'Two factor authentication disabled' => 'Autentificare în doi factori dezactivată', + 'Two factor authentication enabled' => 'Autentificare în doi factori activată', + 'Unable to update this user.' => 'Nu am putut actualiza utilizatorul.', + 'There is no user management for personal projects.' => 'Nu poți gestiona utilizatori pentru proiecte private.', + 'User that will receive the email' => 'Utilizatorul care va primi e-mail-ul', + 'Email subject' => 'Subiectul e-mail-ului', + 'Date' => 'Data', + 'Add a comment log when moving the task between columns' => 'Înregistrează un comentariu în jurnal când se mută sarcina între coloane', + 'Move the task to another column when the category is changed' => 'Mută sarcina în altă coloană când se schimbă categoria', + 'Send a task by email to someone' => 'Trimite o sarcină prin e-mail cuiva', + 'Reopen a task' => 'Redeschide o sarcină', + 'Notification' => 'Notificare', + '%s moved the task #%d to the first swimlane' => '%s a mutat sarcina #%d în primul culoar', + 'Swimlane' => 'Culoar', + '%s moved the task %s to the first swimlane' => '%s a mutat sarcina %s în primul culoar', + '%s moved the task %s to the swimlane "%s"' => '%s a mutat sarcina %s în culoarul "%s"', + 'This report contains all subtasks information for the given date range.' => 'Raportul conține toate informațiile sub-sarcinilor pentru intervalul dat.', + 'This report contains all tasks information for the given date range.' => 'Raportul conține toate informațiile sarcinilor pentru intervalul dat.', + 'Project activities for %s' => 'Activități de proiect pentru "%s"', + 'view the board on Kanboard' => 'vezi bordul în Kanboard', + 'The task has been moved to the first swimlane' => 'Sarcina a fost mutată în primul culoar', + 'The task has been moved to another swimlane:' => 'Sarcina a fost mutată în alt culoar:', + 'New title: %s' => 'Titlu nou: %s', + 'The task is not assigned anymore' => 'Sarcina nu mai este desemnată', + 'New assignee: %s' => 'Desemnat nou: %s', + 'There is no category now' => 'Nu mai există categorii acum', + 'New category: %s' => 'Categorie nouă: %s', + 'New color: %s' => 'Culoare nouă: %s', + 'New complexity: %d' => 'Complexitate nouă: %d', + 'The due date has been removed' => 'Data scadentă a fost ștearsă', + 'There is no description anymore' => 'Nu mai există o descriere', + 'Recurrence settings has been modified' => 'Preferințele de recurență au fost modificate', + 'Time spent changed: %sh' => 'Timpul petrecut s-a schimbat: %s h', + 'Time estimated changed: %sh' => 'Timpul estimat s-a schimbat: %s h', + 'The field "%s" has been updated' => 'Câmpul "%s" a fost actualizat', + 'The description has been modified:' => 'Descrierea a fost modificată', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Vrei să închizi sarcina "%s" inclusiv toate sub-sarcinile?', + 'I want to receive notifications for:' => 'Vreau sa primesc notificări pentru:', + 'All tasks' => 'Toate Sarcinile', + 'Only for tasks assigned to me' => 'Numai sarcinile atribuite mie', + 'Only for tasks created by me' => 'Numai sarcinile create de mine', + 'Only for tasks created by me and tasks assigned to me' => 'Numai sarcinile create de mine si atribuite mie.', + '%%Y-%%m-%%d' => '%%d/%%m/%%Y', + 'Total for all columns' => 'Totalul tuturor coloanelor', + 'You need at least 2 days of data to show the chart.' => 'Ai nevoie de cel putin 2 zile de date pentru afișarea graficului.', + '<15m' => '< 15 min', + '<30m' => '< 30 min', + 'Stop timer' => 'Oprește cronometrul', + 'Start timer' => 'Pornețe cronometrul', + 'My activity stream' => 'Fluxul meu de activități', + 'Search tasks' => 'Caută sarcini', + 'Reset filters' => 'Resetează filtre', + 'My tasks due tomorrow' => 'Sarcinile mele scadente mâine', + 'Tasks due today' => 'Sarcini scadente astăzi', + 'Tasks due tomorrow' => 'Sarcini scadente mâine', + 'Tasks due yesterday' => 'Sarcini scadente ieri', + 'Closed tasks' => 'Sarcini închise', + 'Open tasks' => 'Sarcini deschise', + 'Not assigned' => 'Fără atribuire', + 'View advanced search syntax' => 'Vezi sintaxa de căutare avansată', + 'Overview' => 'În ansamblu', + 'Board/Calendar/List view' => 'Bord/Calendar/Listă', + 'Switch to the board view' => 'Schimbă la bord', + 'Switch to the list view' => 'Schimbă la listă', + 'Go to the search/filter box' => 'Mergi la câmpul de căutare/filtre', + 'There is no activity yet.' => 'Nu există activitate încă.', + 'No tasks found.' => 'Nu s-au găsit sarcini.', + 'Keyboard shortcut: "%s"' => 'Scurtătură tastatură : "%s"', + 'List' => 'Listă', + 'Filter' => 'Filtru', + 'Advanced search' => 'Căutare avansată', + 'Example of query: ' => 'Exemplu de interogare: ', + 'Search by project: ' => 'Caută după proiect: ', + 'Search by column: ' => 'Caută după coloană: ', + 'Search by assignee: ' => 'Caută după desemnat: ', + 'Search by color: ' => 'Caută după culoare: ', + 'Search by category: ' => 'Caută după categorie: ', + 'Search by description: ' => 'Caută după descriere: ', + 'Search by due date: ' => 'Caută după dată scadentă: ', + 'Average time spent in each column' => 'Timp mediu petrecut în fiecare coloană', + 'Average time spent' => 'Timp mediu utilizat', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Acest grafic arată timpul mediu petrecut în fiecare coloană pentru ultimele %d sarcini.', + 'Average Lead and Cycle time' => 'Durată medie de Avans si Ciclu', + 'Average lead time: ' => 'Medie durată avans: ', + 'Average cycle time: ' => 'Medie durata ciclu: ', + 'Cycle Time' => 'Timp ciclu', + 'Lead Time' => 'Timp avans', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Acest grafic arată media duratelor de avans și ciclu pentru ultimele %d sarcini de-a lungul timpului.', + 'Average time into each column' => 'Durată medie în fiecare coloană', + 'Lead and cycle time' => 'Timpuri de avans și ciclu', + 'Lead time: ' => 'Timp avans: ', + 'Cycle time: ' => 'Timp ciclu: ', + 'Time spent in each column' => 'Timp petrecut prin fiecare coloană', + 'The lead time is the duration between the task creation and the completion.' => 'Timpul de avans este durata între crearea sarcinii și finalizarea.', + 'The cycle time is the duration between the start date and the completion.' => 'Timpul de ciclu este durata între data de pornire și finalizare.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Dacă sarcina nu este închisă data curentă este folosită în locul dății de finalizare.', + 'Set the start date automatically' => 'Setează automat data pornirii.', + 'Edit Authentication' => 'Modifică autentificarea', + 'Remote user' => 'Utilizator la distanță', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Utilizatorii la distanță nu păstrează parola în baza de date locală, de exemplu : conturi LDAP, GitHub sau Google.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Dacă bifezi "Interzice formularul de autentificare", acreditările introduse în dialogul de conectare vor fi ignorate.', + 'Default task color' => 'Culoarea implicită a sarcinii', + 'This feature does not work with all browsers.' => 'Această funcționalitate nu funcționează cu toate browserele.', + 'There is no destination project available.' => 'Nu este disponibil un proiect destinație.', + 'Trigger automatically subtask time tracking' => 'Declanșează automat cronometrarea timpului în sub-sarcini.', + 'Include closed tasks in the cumulative flow diagram' => 'Include sarcini închise în graficul fluxurilor cumulate', + 'Current swimlane: %s' => 'Culoarul curent: %s', + 'Current column: %s' => 'Coloana curentă: %s', + 'Current category: %s' => 'Categoria curentă: %s', + 'no category' => 'fără categorie', + 'Current assignee: %s' => 'Desemnat curent: %s', + 'not assigned' => 'fără desemnare', + 'Author:' => 'Autor:', + 'contributors' => 'contribuitori', + 'License:' => 'Licență:', + 'License' => 'Licență', + 'Enter the text below' => 'Introdu textul mai jos', + 'Start date:' => 'Data pornirii:', + 'Due date:' => 'Data scadentă:', + 'People who are project managers' => 'Persoane care gestionează proiectul', + 'People who are project members' => 'Persoane care participă la proiect', + 'NOK - Norwegian Krone' => 'NOK - Coroană norvegiană', + 'Show this column' => 'Arată această coloană', + 'Hide this column' => 'Ascunde această coloană', + 'End date' => 'Dată finalizare', + 'Users overview' => 'Prezentare generală utilizatori', + 'Members' => 'Membrii', + 'Shared project' => 'Proiect partajat', + 'Project managers' => 'Manageri de proiect', + 'Projects list' => 'Listă proiecte', + 'End date:' => 'Dată finalizare:', + 'Change task color when using a specific task link' => 'Schimbă culoarea sarcinii când se folosește o anumită legătură în sarcină', + 'Task link creation or modification' => 'Creare sau modificare legături sarcină', + 'Milestone' => 'Țel', + 'Reset the search/filter box' => 'Resetează dialogul de căutare/filtre', + 'Documentation' => 'Documentație', + 'Author' => 'Autor', + 'Version' => 'Versiuni', + 'Plugins' => 'Extensii', + 'There is no plugin loaded.' => 'Nu sunt încărcate extensii.', + 'My notifications' => 'Notificările mele', + 'Custom filters' => 'Filtre personalizate', + 'Your custom filter has been created successfully.' => 'Filtrul tău personalizat a fost creat.', + 'Unable to create your custom filter.' => 'Nu am putut crea filtrul tău personalizat.', + 'Custom filter removed successfully.' => 'Filtrul personalizat a fost șters.', + 'Unable to remove this custom filter.' => 'Nu am putut șterge filtrul presonalizat.', + 'Edit custom filter' => 'Modifică un filtru personalizat', + 'Your custom filter has been updated successfully.' => 'Filtrul tău personalizat a fost actualizat.', + 'Unable to update custom filter.' => 'Nu am putut actualiza filtrul personalizat.', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Atașament nou în sarcina #%d: %s', + 'New comment on task #%d' => 'Comentariu nou în sarcina #%d', + 'Comment updated on task #%d' => 'Comentariu actualizat în sarcina #%d', + 'New subtask on task #%d' => 'Sub-sarcină nouă în sarcina #%d', + 'Subtask updated on task #%d' => 'Sub-sarcină actualizată în sarcina #%d', + 'New task #%d: %s' => 'Sarcină nouă #%d: %s', + 'Task updated #%d' => 'Sarcina #%d a fost actualizată', + 'Task #%d closed' => 'Sarcina #%d a fost închisă', + 'Task #%d opened' => 'Sarcina #%d a fost deschisă', + 'Column changed for task #%d' => 'Coloană schimbată pentru sarcina #%d', + 'New position for task #%d' => 'Poziție nouă pentru sarcina #%d', + 'Swimlane changed for task #%d' => 'Culoarul schimbat pentru sarcina #%d', + 'Assignee changed on task #%d' => 'Persoana desemnată a fost schimbată în sarcina #%d', + '%d overdue tasks' => '%d sarcini întârziate', + 'No notification.' => 'Fără notificări.', + 'Mark all as read' => 'Marchează toate ca citite', + 'Mark as read' => 'Marchează citit', + 'Total number of tasks in this column across all swimlanes' => 'Număr total de sarcini în această coloană peste toate culoarele', + 'Collapse swimlane' => 'Restrânge culoarul', + 'Expand swimlane' => 'Extinde culoarul', + 'Add a new filter' => 'Adaugă un filtru nou', + 'Share with all project members' => 'Partajează cu toți membrii proiectului', + 'Shared' => 'Partajat', + 'Owner' => 'Proprietar', + 'Unread notifications' => 'Notificări necitite', + 'Notification methods:' => 'Metode de notificare:', + 'Unable to read your file' => 'Nu am putut citi fișierul', + '%d task(s) have been imported successfully.' => 'Sarcini importate cu succes: %d.', + 'Nothing has been imported!' => 'Nu s-a importat nimic!', + 'Import users from CSV file' => 'Importă utilizatori dintr-un fișier CSV', + '%d user(s) have been imported successfully.' => 'Utilizatori importați cu succes: %d.', + 'Comma' => 'Virgulă', + 'Semi-colon' => 'Punct și virgulă', + 'Tab' => 'Tab', + 'Vertical bar' => 'Bară verticală', + 'Double Quote' => 'Ghilimele duble', + 'Single Quote' => 'Ghilimele simple', + '%s attached a file to the task #%d' => '%s a atașat un fișier sarcinii #%d', + 'There is no column or swimlane activated in your project!' => 'Nu există vreo coloană sau culoar activ în proiectul tău!', + 'Append filter (instead of replacement)' => 'Adaugă filtru (în loc de înlocuire)', + 'Append/Replace' => 'Adaugă/Înlocuiește', + 'Append' => 'Adaugă', + 'Replace' => 'Înlocuiește', + 'Import' => 'Import', + 'Change sorting' => 'Schimbă ordonarea', + 'Tasks Importation' => 'Import de sarcini', + 'Delimiter' => 'Delimitare', + 'Enclosure' => 'Caractere de încadrare', + 'CSV File' => 'Fișier CSV', + 'Instructions' => 'Instrucțiuni', + 'Your file must use the predefined CSV format' => 'Trebuie să folosești formatarea CSV predefinită', + 'Your file must be encoded in UTF-8' => 'Fișierul trebuie să fie codificat în UTF-8', + 'The first row must be the header' => 'Primul rând trebuie să fie antetul', + 'Duplicates are not verified for you' => 'Nu este verificată existența duplicatelor', + 'The due date must use the ISO format: YYYY-MM-DD' => 'Data scadentă trebuie să folosească formatarea ISO : AAAA-LL-ZZ', + 'Download CSV template' => 'Descarcă modelul CSV', + 'No external integration registered.' => 'Nu a fost înregistrată o integrare externă.', + 'Duplicates are not imported' => 'Duplicatele nu sunt importate', + 'Usernames must be lowercase and unique' => 'Numele de utilizator trebuie să aibă caractere minuscule și să fie unice', + 'Passwords will be encrypted if present' => 'Parolele vor fi criptate dacă există', + '%s attached a new file to the task %s' => '%s a atașat un fișier nou la sarcina %s', + 'Link type' => 'Tip de legătură', + 'Assign automatically a category based on a link' => 'Desemnează categoria automat în funcție de legătură', + 'BAM - Konvertible Mark' => 'BAM - Marcă bosniană convertibilă', + 'Assignee Username' => 'Utilizatorul desemnat', + 'Assignee Name' => 'Numele desemnatului', + 'Groups' => 'Groupuri', + 'Members of %s' => 'Membri al %s', + 'New group' => 'Grup nou', + 'Group created successfully.' => 'Grupul a fost creat.', + 'Unable to create your group.' => 'Nu am putut crea grupul.', + 'Edit group' => 'Modifică grup', + 'Group updated successfully.' => 'Grupul a fost actualizat.', + 'Unable to update your group.' => 'Nu am putut actualiza grupul.', + 'Add group member to "%s"' => 'Adaugă membru de grup în "%s"', + 'Group member added successfully.' => 'Membru de grup adăugat.', + 'Unable to add group member.' => 'Nu am putut adăuga membrul de grup.', + 'Remove user from group "%s"' => 'Șterge utilizatorul din grupul "%s"', + 'User removed successfully from this group.' => 'Utilizatorul a fost șters din grup.', + 'Unable to remove this user from the group.' => 'Nu am putut șterge utilizatorul din grup.', + 'Remove group' => 'Șterge grupul', + 'Group removed successfully.' => 'Grupul a fost șters.', + 'Unable to remove this group.' => 'Nu am putut șterge grupul.', + 'Project Permissions' => 'Permisiunile proiectului', + 'Manager' => 'Gestionar', + 'Project Manager' => 'Șef de proiect', + 'Project Member' => 'Membru de proiect', + 'Project Viewer' => 'Vizualizator de proiect', + 'Your account is locked for %d minutes' => 'Contul tău este blocat %d minute', + 'Invalid captcha' => 'Captcha invalid', + 'The name must be unique' => 'Numele trebuie să fie unic', + 'View all groups' => 'Vezi toate grupurile', + 'There is no user available.' => 'Nu există utilizator disponibil', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Vrei să ștergi utilizatorul "%s" din grupul "%s" ?', + 'There is no group.' => 'Nu există grup.', + 'Add group member' => 'Adaugă membru în grup', + 'Do you really want to remove this group: "%s"?' => 'Vrei să ștergi acest grup: "%s" ?', + 'There is no user in this group.' => 'Nu există utilizatori în acest grup', + 'Permissions' => 'Permisiuni', + 'Allowed Users' => 'Utilizatori autorizați', + 'No specific user has been allowed.' => 'Nu a fost autorizat vreun utilizator.', + 'Role' => 'Rol', + 'Enter user name...' => 'Introdu numele de utilizator…', + 'Allowed Groups' => 'Grupuri autorizate', + 'No group has been allowed.' => 'Nu a fost autorizat vreun grup.', + 'Group' => 'Grup', + 'Group Name' => 'Nume grup', + 'Enter group name...' => 'Introdu numele de grup…', + 'Role:' => 'Rol:', + 'Project members' => 'Membri de proiect', + '%s mentioned you in the task #%d' => '%s te-a menționat în sarcina #%d', + '%s mentioned you in a comment on the task #%d' => '%s te-a menționat într-un comentariu în sarcina #%d', + 'You were mentioned in the task #%d' => 'Ai fost menționat în sarcina #%d', + 'You were mentioned in a comment on the task #%d' => 'Ai fost menționat într-un comentariu în sarcina #%d', + 'Estimated hours: ' => 'Ore estimate: ', + 'Actual hours: ' => 'Ore actuale: ', + 'Hours Spent' => 'Ore petrecute', + 'Hours Estimated' => 'Ore estimate', + 'Estimated Time' => 'Timp estimat', + 'Actual Time' => 'Timp actual', + 'Estimated vs actual time' => 'Timp estimat vs actual', + 'RUB - Russian Ruble' => 'RUB - Rublă rusească', + 'Assign the task to the person who does the action when the column is changed' => 'Atribuie sarcina persoanei care acționează când este schimbată coloana', + 'Close a task in a specific column' => 'Închide o sarcină într-o anumită coloană', + 'Time-based One-time Password Algorithm' => 'Parolă de unică folosință bazată pe timp', + 'Two-Factor Provider: ' => 'Furnizor autentificare în doi factori: ', + 'Disable two-factor authentication' => 'Dezactivează autentificarea în doi factori', + 'Enable two-factor authentication' => 'Activează autentificarea în doi factori', + 'There is no integration registered at the moment.' => 'Nu este vreo integrare înregistrată momentan.', + 'Password Reset for Kanboard' => 'Resetare parolă pentru Kanboard', + 'Forgot password?' => 'Parolă uitată?', + 'Enable "Forget Password"' => 'Activează "Parolă Uitată"', + 'Password Reset' => 'Resetare de parolă', + 'New password' => 'Parolă nouă', + 'Change Password' => 'Schimbă parola', + 'To reset your password click on this link:' => 'Pentru a reseta parola apasă pe acest link:', + 'Last Password Reset' => 'Ultima resetare de parolă', + 'The password has never been reinitialized.' => 'Parola nu a fost resetată vreodată.', + 'Creation' => 'Creare', + 'Expiration' => 'Expirare', + 'Password reset history' => 'Istoricul resetărilor de parolă', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Toate sarcinile coloanei "%s" si a culoarului "%s" au fost închise.', + 'Do you really want to close all tasks of this column?' => 'Vrei să închizi toate sarcinile acestei coloane?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d sarcini în coloana "%s" si culoarul "%s" vor fi închise.', + 'Close all tasks in this column and this swimlane' => 'Închide toate sarcinile acestei coloane', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Nici o extensie nu a înregistrat o metodă de notificare a proiectelor. Mai poți însă configura notificări individuale în profilul tău de utilizator.', + 'My dashboard' => 'Bordul meu', + 'My profile' => 'Profilul meu', + 'Project owner: ' => 'Responsabil de proiect: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'Identificatorul de proiect este opțional și trebuie să fie alfanumeric, exemplu: PROIECTULMEU.', + 'Project owner' => 'Responsabil de proiect', + 'Personal projects do not have users and groups management.' => 'Proiectele private nu au utilizatori și gestionare de grupuri.', + 'There is no project member.' => 'Nu există membri de proiect.', + 'Priority' => 'Prioritate', + 'Task priority' => 'Priorități de sarcini', + 'General' => 'General', + 'Dates' => 'Date', + 'Default priority' => 'Prioritate implicită', + 'Lowest priority' => 'Prioritate mică', + 'Highest priority' => 'Prioritate maximă', + 'Close a task when there is no activity' => 'Închide o sarcină când nu are activitate', + 'Duration in days' => 'Durată în zile', + 'Send email when there is no activity on a task' => 'Trimite e-mail când o sarcină nu are activitate', + 'Unable to fetch link information.' => 'Nu am putut aduce informația legăturii.', + 'Daily background job for tasks' => 'Funcția zilnică de fundal pentru sarcini', + 'Auto' => 'Auto', + 'Related' => 'Asociat', + 'Attachment' => 'Atașament', + 'Web Link' => 'Link web', + 'External links' => 'Legături externe', + 'Add external link' => 'Adaugă legătură externă', + 'Type' => 'Tip', + 'Dependency' => 'Dependență', + 'Add internal link' => 'Adaugă legătură internă', + 'Add a new external link' => 'Adaugă o legătură externă nouă', + 'Edit external link' => 'Modifică legătura externă', + 'External link' => 'Legătură externă', + 'Copy and paste your link here...' => 'Lipește link-ul tău aici…', + 'URL' => 'URL', + 'Internal links' => 'Legături interne', + 'Assign to me' => 'Atribuie mie', + 'Me' => 'Eu', + 'Do not duplicate anything' => 'Nu duplica nimic', + 'Projects management' => 'Gestionează proiecte', + 'Users management' => 'Gestionează utilizatori', + 'Groups management' => 'Gestionează grupuri', + 'Create from another project' => 'Creează din alt proiect', + 'open' => 'deschis', + 'closed' => 'închis', + 'Priority:' => 'Prioritate:', + 'Reference:' => 'Referință:', + 'Complexity:' => 'Complexitate:', + 'Swimlane:' => 'Culoar:', + 'Column:' => 'Coloană:', + 'Position:' => 'Poziție:', + 'Creator:' => 'Creator:', + 'Time estimated:' => 'Timp estimat:', + '%s hours' => '%s ore', + 'Time spent:' => 'Timp petrecut:', + 'Created:' => 'Creat de:', + 'Modified:' => 'Modificat:', + 'Completed:' => 'Finalizat:', + 'Started:' => 'Pornit:', + 'Moved:' => 'Mutat: ', + 'Task #%d' => 'Sarcina #%d', + 'Time format' => 'Format oră', + 'Start date: ' => 'Data pornirii: ', + 'End date: ' => 'Data finalizării: ', + 'New due date: ' => 'Dată scadentă nouă: ', + 'Start date changed: ' => 'Data pornirii modificată: ', + 'Disable personal projects' => 'Dezactivează proiectele private', + 'Do you really want to remove this custom filter: "%s"?' => 'Vrei să ștergi filtrul personalizat: "%s" ?', + 'Remove a custom filter' => 'Șterge un filtru personalizat', + 'User activated successfully.' => 'Utilizatorul a fost activat.', + 'Unable to enable this user.' => 'Nu am putut activa utilizatorul.', + 'User disabled successfully.' => 'Utilizatorul a fost dezactivat.', + 'Unable to disable this user.' => 'Nu am putut dezactiva utilizatorul.', + 'All files have been uploaded successfully.' => 'Toate fișierele au fost încărcate.', + 'The maximum allowed file size is %sB.' => 'Dimensiunea maximă a fișierului este %sB.', + 'Drag and drop your files here' => 'Trage fișierele tale aici', + 'choose files' => 'alege fișiere', + 'View profile' => 'Vezi profilul', + 'Two Factor' => 'Doi Factori', + 'Disable user' => 'Dezactivează utilizator', + 'Do you really want to disable this user: "%s"?' => 'Vrei să dezactivezi utilizatorul: "%s" ?', + 'Enable user' => 'Activează utilizator', + 'Do you really want to enable this user: "%s"?' => 'Vrei să activezi utilizatorul: "%s" ?', + 'Download' => 'Descarcă', + 'Uploaded: %s' => 'Încărcat: %s', + 'Size: %s' => 'Dimensiune: %s', + 'Uploaded by %s' => 'Încărcat de %s', + 'Filename' => 'Nume fișier', + 'Size' => 'Dimensiune', + 'Column created successfully.' => 'Coloana a fost creată.', + 'Another column with the same name exists in the project' => 'Există o coloană cu același nume în proiect', + 'Default filters' => 'Filtre implicite', + 'Your board doesn\'t have any columns!' => 'Bordul tău nu are coloane!', + 'Change column position' => 'Schimbă poziția coloanei', + 'Switch to the project overview' => 'Treci la vederea de ansamblu a proiectului', + 'User filters' => 'Filtre utilizatori', + 'Category filters' => 'Filtre categorii', + 'Upload a file' => 'Încarcă un fișier', + 'View file' => 'Vezi fișier', + 'Last activity' => 'Ultima activitate', + 'Change subtask position' => 'Schimbă poziția sub-sarcinii', + 'This value must be greater than %d' => 'Valoarea aceasta trebuie să fie mai mare decât %d', + 'Another swimlane with the same name exists in the project' => 'Există un culoar cu același nume în proiect', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Exemplu : https://exemplu.kanboard.org/ (utilizat în generarea URL-urilor absolute)', + 'Actions duplicated successfully.' => 'Acțiuni duplicate cu succes.', + 'Unable to duplicate actions.' => 'Nu am putut duplica acțiunile.', + 'Add a new action' => 'Adaugă o nouă acțiune', + 'Import from another project' => 'Importă din alt proiect', + 'There is no action at the moment.' => 'Nu există vreo acțiune momentan.', + 'Import actions from another project' => 'Importă acșiuni din alt proiect', + 'There is no available project.' => 'Nu există un proiect disponibil.', + 'Local File' => 'Fișier local', + 'Configuration' => 'Configurație', + 'PHP version:' => 'Versiune de PHP:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'Versiune sistem de operare:', + 'Database version:' => 'Versiune bază de date:', + 'Browser:' => 'Browser:', + 'Task view' => 'Detaliere sarcină', + 'Edit task' => 'Modifică sarcina', + 'Edit description' => 'Modifică descrierea', + 'New internal link' => 'Legătură internă nouă', + 'Display list of keyboard shortcuts' => 'Afișează lista de scurtături pe tastatură', + 'Avatar' => 'Avatar', + 'Upload my avatar image' => 'Încarcă o imagine de avatar', + 'Remove my image' => 'Șterge imaginea mea', + 'The OAuth2 state parameter is invalid' => 'Parametrul "Stare" din OAuth2 este invalid', + 'User not found.' => 'Utilizatorul nu a fost găsit.', + 'Search in activity stream' => 'Caută în fluxul de activități', + 'My activities' => 'Activitățile mele', + 'Activity until yesterday' => 'Activitate până ieri', + 'Activity until today' => 'Activitate până azi', + 'Search by creator: ' => 'Caută după creator: ', + 'Search by creation date: ' => 'Caută după data creării: ', + 'Search by task status: ' => 'Caută după starea sarcinii: ', + 'Search by task title: ' => 'Caută după titlul sarcinii: ', + 'Activity stream search' => 'Căutare în fluxul de activități', + 'Projects where "%s" is manager' => 'Proiecte unde "%s" este gestionar', + 'Projects where "%s" is member' => 'Proiecte unde "%s" este membru', + 'Open tasks assigned to "%s"' => 'Sarcini deschise atribuite lui "%s"', + 'Closed tasks assigned to "%s"' => 'Sarcini închise atribuite lui "%s"', + 'Assign automatically a color based on a priority' => 'Atribuie în mod automat o culoare în funcție de prioritate', + 'Overdue tasks for the project(s) "%s"' => 'Sarcini întârziate pentru proiect "%s"', + 'Upload files' => 'Încarcă fișiere', + 'Installed Plugins' => 'Extensii instalate', + 'Plugin Directory' => 'Director extensii', + 'Plugin installed successfully.' => 'Extensie instalată cu succes.', + 'Plugin updated successfully.' => 'Extensie actualizată cu succes.', + 'Plugin removed successfully.' => 'Extensie dezinstalată cu succes.', + 'Subtask converted to task successfully.' => 'Sub-sarcina a fost transformată în sarcină.', + 'Unable to convert the subtask.' => 'Nu am putut transforma sub-sarcina.', + 'Unable to extract plugin archive.' => 'Nu am putut extrage arhiva extensiei.', + 'Plugin not found.' => 'Extensia nu a fost găsită.', + 'You don\'t have the permission to remove this plugin.' => 'Nu ai dreptul să ștergi această extensie.', + 'Unable to download plugin archive.' => 'Nu am putut descărca arhiva extensiei.', + 'Unable to write temporary file for plugin.' => 'Nu am putut scrie fișierul temporar pentru extensie.', + 'Unable to open plugin archive.' => 'Nu am putut deschide arhiva extensiei.', + 'There is no file in the plugin archive.' => 'Nu există fișiere în arhiva extensiei.', + 'Create tasks in bulk' => 'Creează sarcini în vrac', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Instanța ta de Kanboard nu a fost configurată pentru a instala extensii din interfața de utilizator.', + 'There is no plugin available.' => 'Nu există extensii disponibile.', + 'Install' => 'Instalează', + 'Update' => 'Actualizează', + 'Up to date' => 'La zi', + 'Not available' => 'Indisponibil', + 'Remove plugin' => 'Șterge extensia', + 'Do you really want to remove this plugin: "%s"?' => 'Vrei să ștergi această extensie: "%s" ?', + 'Uninstall' => 'Dezinstalare', + 'Listing' => 'Listare', + 'Metadata' => 'Metadate', + 'Manage projects' => 'Gestionează proiecte', + 'Convert to task' => 'Transformă în sarcină', + 'Convert sub-task to task' => 'Transformă sub-sarcina în sarcină', + 'Do you really want to convert this sub-task to a task?' => 'Vrei să transformi sub-sarcina în sarcină?', + 'My task title' => 'Titlul pentru sarcină', + 'Enter one task by line.' => 'Introdu o sarcină per linie.', + 'Number of failed login:' => 'Număr de autentificări eșuate:', + 'Account locked until:' => 'Cont blocat până la:', + 'Email settings' => 'Preferințe e-mail', + 'Email sender address' => 'Adresa e-mail expeditor', + 'Email transport' => 'Transport e-mail', + 'Webhook token' => 'Token de securitate webhook', + 'Project tags management' => 'Gestionare etichete de proiect', + 'Tag created successfully.' => 'Etichetă creată.', + 'Unable to create this tag.' => 'Nu am putut crea eticheta.', + 'Tag updated successfully.' => 'Eticheta a fost actualizată.', + 'Unable to update this tag.' => 'Nu am putut actualiza eticheta.', + 'Tag removed successfully.' => 'Eticheta a fost ștearsă.', + 'Unable to remove this tag.' => 'Impossible de supprimer ce libellé.', + 'Global tags management' => 'Gestionare etichete globale', + 'Tags' => 'Etichete', + 'Tags management' => 'Gestionare etichete', + 'Add new tag' => 'Adaugă etichetă nouă', + 'Edit a tag' => 'Modifică o etichetă', + 'Project tags' => 'Etichetele proiectului', + 'There is no specific tag for this project at the moment.' => 'Proiectul nu are vreo etichetă specifică momentan.', + 'Tag' => 'Etichetă', + 'Remove a tag' => 'Șterge o etichetă', + 'Do you really want to remove this tag: "%s"?' => 'Vrei să ștergi eticheta: "%s" ?', + 'Global tags' => 'Etichete globale', + 'There is no global tag at the moment.' => 'Nu există etichete globale momentan.', + 'This field cannot be empty' => 'Acest câmp nu poate fi gol', + 'Close a task when there is no activity in a specific column' => 'Închide o sarcină când nu există activitate într-o anumită coloană', + '%s removed a subtask for the task #%d' => '%s a șters o sub-sarcină din sarcina#%d', + '%s removed a comment on the task #%d' => '%s a șters un comentariu din sarcina #%d', + 'Comment removed on task #%d' => 'Comentariu șters în sarcina #%d', + 'Subtask removed on task #%d' => 'Sub-sarcină ștearsă din sarcina #%d', + 'Hide tasks in this column in the dashboard' => 'Ascunde sarcinile din această coloană în bord', + '%s removed a comment on the task %s' => '%s a șters un comentariu din sarcina %s', + '%s removed a subtask for the task %s' => '%s a șters o sub-sarcină din sarcina %s', + 'Comment removed' => 'Comentariu șters', + 'Subtask removed' => 'Sub-sarcină ștearsă', + '%s set a new internal link for the task #%d' => '%s a definit o legătură internă nouă pentru sarcina #%d', + '%s removed an internal link for the task #%d' => '%s a șters o legătură internă pentru sarcina #%d', + 'A new internal link for the task #%d has been defined' => 'O nouă legătură internă a fost definită pentru sarcina #%d', + 'Internal link removed for the task #%d' => 'Legătură internă ștearsă pentru sarcina #%d', + '%s set a new internal link for the task %s' => '%s a definit o nouă legătură internă pentru sarcina %s', + '%s removed an internal link for the task %s' => '%s a șters o legătură internă pentru sarcina %s', + 'Automatically set the due date on task creation' => 'Definește automat data scadentă la crearea sarcinii', + 'Move the task to another column when closed' => 'Mută sarcina în altă coloană când este închisă', + 'Move the task to another column when not moved during a given period' => 'Mută sarcina în altă coloană dacă nu a fost mutată într-o anumită perioadă', + 'Dashboard for %s' => 'Bordul pentru %s', + 'Tasks overview for %s' => 'Prezentare generală sarcini pentru %s', + 'Subtasks overview for %s' => 'Prezentare generală sub-sarcini pentru %s', + 'Projects overview for %s' => 'Prezentare generală proiecte pentru %s', + 'Activity stream for %s' => 'Flux de activități pentru %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Atribuie o culoare când sarcina este mutată într-un anumit culoar', + 'Assign a priority when the task is moved to a specific swimlane' => 'Atribuie o prioritate când sarcina este mutată într-un anumit culoar', + 'User unlocked successfully.' => 'Utilizatorul a fost deblocat.', + 'Unable to unlock the user.' => 'Nu am putut debloca utilizatorul.', + 'Move a task to another swimlane' => 'Mută sarcina în alt culoar', + 'Creator Name' => 'Nume creator', + 'Time spent and estimated' => 'Timp petrecut si estimat', + 'Move position' => 'Mută poziția', + 'Move task to another position on the board' => 'Mută sarcina pe altă poziție pe bord', + 'Insert before this task' => 'Inserează înaintea sarcinii', + 'Insert after this task' => 'Inserează după sarcină', + 'Unlock this user' => 'Deblochează utilizatorul', + 'Custom Project Roles' => 'Roluri personalizate în proiect', + 'Add a new custom role' => 'Adaugă rol personalizat nou', + 'Restrictions for the role "%s"' => 'Restricții pentru rolul "%s"', + 'Add a new project restriction' => 'Adaugă o restricție nouă de proiect', + 'Add a new drag and drop restriction' => 'Adaugă o restricție nouă de mutare', + 'Add a new column restriction' => 'Adaugă o restricție nouă de coloană', + 'Edit this role' => 'Modifică rolul', + 'Remove this role' => 'Șterge rolul', + 'There is no restriction for this role.' => 'Nu există restricții pe acest rol.', + 'Only moving task between those columns is permitted' => 'Sarcina poate fi mutată numai între aceste coloane', + 'Close a task in a specific column when not moved during a given period' => 'Închide o sarcină într-o anumită coloană când nu a fost mutată într-o anumită perioadă', + 'Edit columns' => 'Modifică coloane', + 'The column restriction has been created successfully.' => 'Restricția pe coloane a fost creată.', + 'Unable to create this column restriction.' => 'Nu am putut crea restricția pe coloană.', + 'Column restriction removed successfully.' => 'Restricția pe coloană a fost ștearsă.', + 'Unable to remove this restriction.' => 'Nu am putut șterge restricția.', + 'Your custom project role has been created successfully.' => 'Rolul personalizat a fost creat.', + 'Unable to create custom project role.' => 'Nu am putut crea rolul personalizat.', + 'Your custom project role has been updated successfully.' => 'Rolul personalizat a fost actualizat.', + 'Unable to update custom project role.' => 'Nu am putut actualiza rolul personalizat.', + 'Custom project role removed successfully.' => 'Rolul personalizat a fost șters.', + 'Unable to remove this project role.' => 'Nu am putut șterge rolul.', + 'The project restriction has been created successfully.' => 'Restricția pe proiect a fost creată.', + 'Unable to create this project restriction.' => 'Nu am putut crea restricția pe proiect.', + 'Project restriction removed successfully.' => 'Restricția pe proiect a fost ștearsă.', + 'You cannot create tasks in this column.' => 'Nu poți crea sarcini în această coloană.', + 'Task creation is permitted for this column' => 'Crearea sarcinilor este permisă în această coloană', + 'Closing or opening a task is permitted for this column' => 'Închiderea sau deschiderea sarcinilor este permisă în această coloană', + 'Task creation is blocked for this column' => 'Crearea sarcinilor este blocată în această coloană', + 'Closing or opening a task is blocked for this column' => 'Închiderea sau deschiderea sarcinilor este blocată în această coloană', + 'Task creation is not permitted' => 'Crearea sarcinilor nu este permisă', + 'Closing or opening a task is not permitted' => 'Închiderea sau deschiderea sarcinilor nu este permisă', + 'New drag and drop restriction for the role "%s"' => 'Restricție nouă de mutare pentru rolul "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Persoanele cu acest rol vor putea muta sarcini numai între coloana sursă și destinație.', + 'Remove a column restriction' => 'Șterge o restricție pe coloană', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Vrei să ștergi această restricție pe coloană: "%s" la "%s" ?', + 'New column restriction for the role "%s"' => 'Restricție pe coloană nouă pentru rolul "%s"', + 'Rule' => 'Reguli', + 'Do you really want to remove this column restriction?' => 'Vrei să ștergi această restricție pe coloană?', + 'Custom roles' => 'Roluri personalizate', + 'New custom project role' => 'Nou rol personalizat pe proiect', + 'Edit custom project role' => 'Modifică rol personalizat pe proiect', + 'Remove a custom role' => 'Șterge rol personalizat pe proiect', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Vrei să ștergi acest rol personalizat "%s" ? Toți membrii cu acest rol vor deveni membri de proiect.', + 'There is no custom role for this project.' => 'Nu există roluri personalizate în acest proiect.', + 'New project restriction for the role "%s"' => 'Restricție de proiect nouă pentru rolul "%s"', + 'Restriction' => 'Restricție', + 'Remove a project restriction' => 'Șterge o restricție de proiect', + 'Do you really want to remove this project restriction: "%s"?' => 'Vrei să ștergi această restricție de proiect: "%s" ?', + 'Duplicate to multiple projects' => 'Duplichează în mai multe proiecte', + 'This field is required' => 'Câmpul este obligatoriu', + 'Moving a task is not permitted' => 'Nu este permisă mutarea sarcinii', + 'This value must be in the range %d to %d' => 'Valoarea trebuie să se afle între %d și %d', + 'You are not allowed to move this task.' => 'Nu ai permisiunea să muți această sarcină.', + 'API User Access' => 'Acces utilizator in API', + 'Preview' => 'Previzualizare', + 'Write' => 'Scrie', + 'Write your text in Markdown' => 'Scrie-ți textul în Markdown', + 'No personal API access token registered.' => 'Nu este înregistrat vreun token personal de acces API.', + 'Your personal API access token is "%s"' => 'Token-ul tău personal de acces API este "%s"', + 'Remove your token' => 'Șterge token-ul tău', + 'Generate a new token' => 'Generează un nou token', + 'Showing %d-%d of %d' => 'Afișez %d - %d din %d', + 'Outgoing Emails' => 'E-mail-uri trimise', + 'Add or change currency rate' => 'Adaugă sau modifică rată de schimb', + 'Reference currency: %s' => 'Moneda de referință: %s', + 'Add custom filters' => 'Adaugă filtre personalizate', + 'Export' => 'Export', + 'Add link label' => 'Adaugă etichetă de legătură', + 'Incompatible Plugins' => 'Extensii incompatibile', + 'Compatibility' => 'Compatibilitate', + 'Permissions and ownership' => 'Permisii si proprietate', + 'Priorities' => 'Priorități', + 'Close this window' => 'Închide fereastra', + 'Unable to upload this file.' => 'Nu pot încărca fișierul acesta.', + 'Import tasks' => 'Importă sarcini', + 'Choose a project' => 'Alege un proiect', + 'Profile' => 'Profil', + 'Application role' => 'Rolul aplicației', + '%d invitations were sent.' => '%d invitații au fost trimise.', + '%d invitation was sent.' => '%d invitație a fost trimisă.', + 'Unable to create this user.' => 'Nu am putut crea acest utilizator.', + 'Kanboard Invitation' => 'Invitație în Kanboard', + 'Visible on dashboard' => 'Vizibil pe bord', + 'Created at:' => 'Creat la:', + 'Updated at:' => 'Actualizat la:', + 'There is no custom filter.' => 'Nu există filtru personalizat.', + 'New User' => 'Utilizator nou', + 'Authentication' => 'Autentificare', + 'If checked, this user will use a third-party system for authentication.' => 'Dacă este bifat, acest utilizator va folosi un sistem terț pentru autentificare.', + 'The password is necessary only for local users.' => 'Parola este obligatorie pentru membrii locali.', + 'You have been invited to register on Kanboard.' => 'Ai fost invitat să te înregistrezi pe Kanboard.', + 'Click here to join your team' => 'Apasă aici pentru a te alătura echipei tale', + 'Invite people' => 'Invită persoane', + 'Emails' => 'E-mail-uri', + 'Enter one email address by line.' => 'Introdu o adresă de e-mail per linie.', + 'Add these people to this project' => 'Adaugă aceste persoane în proiect', + 'Add this person to this project' => 'Adaugă această persoană în proiect', + 'Sign-up' => 'Înregistrare', + 'Credentials' => 'Acreditări', + 'New user' => 'Utilizator nou', + 'This username is already taken' => 'Acest nume de utilizator a fost luat deja', + 'Your profile must have a valid email address.' => 'Profilul tău trebuie să aibă o adresă de e-mail validă.', + 'TRL - Turkish Lira' => 'TRL - Liră turcească', + 'The project email is optional and could be used by several plugins.' => 'E-mail-ul proiectului este opțional si ar putea fi folosit de mai multe extensii.', + 'The project email must be unique across all projects' => 'E-mail-ul proiectului trebuie să fie diferit de toate celelalte proiecte.', + 'The email configuration has been disabled by the administrator.' => 'Configurarea de e-mail-uri a fost dezactivată de administrator.', + 'Close this project' => 'Închide acest proiect', + 'Open this project' => 'Deschide acest proiect', + 'Close a project' => 'Închide un proiect', + 'Do you really want to close this project: "%s"?' => 'Vrei să închizi proiectul: "%s" ?', + 'Reopen a project' => 'Redeschide un proiect', + 'Do you really want to reopen this project: "%s"?' => 'Vrei să redeschizi proiectul: "%s" ?', + 'This project is open' => 'Proiectul este deschis', + 'This project is closed' => 'Proiectul este închis', + 'Unable to upload files, check the permissions of your data folder.' => 'Nu am putut încărca fișierele, verifică permisiunile directorului de date.', + 'Another category with the same name exists in this project' => 'Altă categorie cu același nume există deja în proiect', + 'Comment sent by email successfully.' => 'Comentariul a fost trimis prin e-mail.', + 'Sent by email to "%s" (%s)' => 'Trimite prin e-mail la "%s" (%s)', + 'Unable to read uploaded file.' => 'Nu pot citi fișierul încărcat.', + 'Database uploaded successfully.' => 'Baza de date a fost încărcată cu succes.', + 'Task sent by email successfully.' => 'Sarcina a fost trimisă prin e-mail.', + 'There is no category in this project.' => 'Nu există categorii în acest proiect.', + 'Send by email' => 'Trimis prin e-mail', + 'Create and send a comment by email' => 'Creat și trimis un comentariu prin e-mail', + 'Subject' => 'Subiect', + 'Upload the database' => 'Încarcă baza de date', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Ai putea încărca baza de date Sqlite descărcată anterior (format Gzip).', + 'Database file' => 'Fișierul bazei de date', + 'Upload' => 'Încarcă', + 'Your project must have at least one active swimlane.' => 'Proiectul tău trebuie să aibă măcar un culoar activ.', + 'Project: %s' => 'Proiect: %s', + 'Automatic action not found: "%s"' => 'Acțiunea automatizată nu a fost gasită: "%s"', + '%d projects' => '%d proiecte', + '%d project' => '%d proiect', + 'There is no project.' => 'Nu există proiect.', + 'Sort' => 'Sortare', + 'Project ID' => 'ID-ul proiectului', + 'Project name' => 'Numele proiectului', + 'Public' => 'Public', + 'Personal' => 'Privat', + '%d tasks' => '%d sarcini', + '%d task' => '%d sarcină', + 'Task ID' => 'ID-ul sarcinii', + 'Assign automatically a color when due date is expired' => 'Atribuie automat o culoare când data scadentă a expirat', + 'Total score in this column across all swimlanes' => 'Scorul total în această coloană pe toate culoarele', + 'HRK - Kuna' => 'HRK - Kuna croată', + 'ARS - Argentine Peso' => 'ARS - Peso argentinian', + 'COP - Colombian Peso' => 'COP - Peso columbian', + '%d groups' => '%d grupuri', + '%d group' => '%d grup', + 'Group ID' => 'ID-ul grupului', + 'External ID' => 'ID extern', + '%d users' => '%d utilizatori', + '%d user' => '%d utilizator', + 'Hide subtasks' => 'Ascunde sub-sarcinile', + 'Show subtasks' => 'Arată sub-sarcinile', + 'Authentication Parameters' => 'Parametri de autentificare', + 'API Access' => 'Acces API', + 'No users found.' => 'Nici un utilizator găsit.', + 'User ID' => 'ID utilizator', + 'Notifications are activated' => 'Notificările sunt activate', + 'Notifications are disabled' => 'Notificările sunt dezactivate', + 'User disabled' => 'Utilizator dezactivat', + '%d notifications' => '%d notificări', + '%d notification' => '%d notificare', + 'There is no external integration installed.' => 'Nu este instalată vreo integrare externă.', + 'You are not allowed to update tasks assigned to someone else.' => 'Nu ai voie să actualizezi sarcini atribuite altcuiva.', + 'You are not allowed to change the assignee.' => 'Nu ai voie să schimbi persoana atribuită.', + 'Task suppression is not permitted' => 'Suprimarea sarcinii nu este permisă', + 'Changing assignee is not permitted' => 'Schimbarea persoanei atribuite nu este permisă', + 'Update only assigned tasks is permitted' => 'Numai actualizarea sarcinilor atribuite este permisă', + 'Only for tasks assigned to the current user' => 'Numai pentru sarcini atribuite utilizatorului curent', + 'My projects' => 'Proiectele mele', + 'You are not a member of any project.' => 'Nu faci parte din vreun proiect', + 'My subtasks' => 'Sub-sarcinile mele', + '%d subtasks' => '%d sub-sarcini', + '%d subtask' => '%d sub-sarcină', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Este permisă mutarea sarcinii între acele coloane pentru sarcinile atribuite utilizatorului curent', + '[DUPLICATE]' => '[DUPLICAT]', + 'DKK - Danish Krona' => 'DKK - Coroană daneză', + 'Remove user from group' => 'Șterge utilizatorul din grup', + 'Assign the task to its creator' => 'Atribuie sarcina creatorului ei', + 'This task was sent by email to "%s" with subject "%s".' => 'Această sarcină a fost trimisă prin e-mail lui "%s" cu subiectul "%s".', + 'Predefined Email Subjects' => 'Subiecte predefinite in e-mail', + 'Write one subject by line.' => 'Scrie un singur subiect per linie.', + 'Create another link' => 'Creează altă legătură', + 'BRL - Brazilian Real' => 'BRL - Real brazilian', + 'Add a new Kanboard task' => 'Adaugă o nouă sarcină Kanboard', + 'Subtask not started' => 'Sub-sarcina nu este pornită', + 'Subtask currently in progress' => 'Sub-sarcină în derulare', + 'Subtask completed' => 'Sub-sarcină finalizată', + 'Subtask added successfully.' => 'Sub-sarcină adăugată.', + '%d subtasks added successfully.' => '%d sub-sarcini au fost adăugate.', + 'Enter one subtask by line.' => 'Introdu o sub-sarcină pe linie.', + 'Predefined Contents' => 'Conținut predefinit', + 'Predefined contents' => 'Conținut predefinit', + 'Predefined Task Description' => 'Descrierea predefinită a sarcinii', + 'Do you really want to remove this template? "%s"' => 'Vrei să ștergi acest model? "%s"', + 'Add predefined task description' => 'Adaugă descriere predefinită a sarcinii', + 'Predefined Task Descriptions' => 'Descrieri predefinite ale sarcinilor', + 'Template created successfully.' => 'Model creat.', + 'Unable to create this template.' => 'Nu am putut crea modelul.', + 'Template updated successfully.' => 'Model actualizat.', + 'Unable to update this template.' => 'Nu am putut actualiza modelul.', + 'Template removed successfully.' => 'Model șters.', + 'Unable to remove this template.' => 'Nu am putut șterge modelul.', + 'Template for the task description' => 'Șablon pentru descrierea sarcinii', + 'The start date is greater than the end date' => 'Data de început este mai mare decât data de sfârșit', + 'Tags must be separated by a comma' => 'Etichetele trebuie separate prin virgulă', + 'Only the task title is required' => 'Doar titlul sarcinii este obligatoriu', + 'Creator Username' => 'Nume de utilizator creator', + 'Color Name' => 'Nume culoare', + 'Column Name' => 'Nume coloană', + 'Swimlane Name' => 'Nume culoar', + 'Time Estimated' => 'Timp estimat', + 'Time Spent' => 'Timp petrecut', + 'External Link' => 'Link extern', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Această funcție activează fluxul iCal, fluxul RSS și vizualizarea publică a tabelului.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Opriți cronometrul tuturor subsarcinilor când mutați o sarcină într-o altă coloană', + 'Subtask Title' => 'Titlu subsarcină', + 'Add a subtask and activate the timer when moving a task to another column' => 'Adaugă o subsarcină și activează cronometrul când muți o sarcină într-o altă coloană', + 'days' => 'zile', + 'minutes' => 'minute', + 'seconds' => 'secunde', + 'Assign automatically a color when preset start date is reached' => 'Atribuiți automat o culoare când se atinge data de începere prestabilită', + 'Move the task to another column once a predefined start date is reached' => 'Mutați sarcina într-o altă coloană odată ce se atinge o dată de începere predefinită', + 'This task is now linked to the task %s with the relation "%s"' => 'Această sarcină este acum legată de sarcina %s cu relația "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'Legătura cu relația "%s" la sarcina %s a fost eliminată', + 'Custom Filter:' => 'Filtru personalizat:', + 'Unable to find this group.' => 'Nu găsesc acest grup.', + '%s moved the task #%d to the column "%s"' => '%s a mutat sarcina #%d în coloana "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => '%s a mutat sarcina #%d pe poziția %d în coloana "%s"', + '%s moved the task #%d to the swimlane "%s"' => '%s a mutat sarcina #%d în culoarul "%s"', + '%sh spent' => '%sh petrecute', + '%sh estimated' => '%sh estimate', + 'Select All' => 'Selectează tot', + 'Unselect All' => 'Deselectează tot', + 'Apply action' => 'Aplică acțiunea', + 'Move selected tasks to another column or swimlane' => 'Mutați sarcinile selectate într-o altă coloană sau un alt culoar', + 'Edit tasks in bulk' => 'Editați sarcinile în masă', + 'Choose the properties that you would like to change for the selected tasks.' => 'Alegeți proprietățile pe care doriți să le schimbați pentru sarcinile selectate.', + 'Configure this project' => 'Configurează acest proiect', + 'Start now' => 'Începe acum', + '%s removed a file from the task #%d' => '%s a eliminat un fișier de la sarcina #%d', + 'Attachment removed from task #%d: %s' => 'Atașament eliminat de la sarcina #%d: %s', + 'No color' => 'Fără culoare', + 'Attachment removed "%s"' => 'Atașament eliminat "%s"', + '%s removed a file from the task %s' => '%s a eliminat un fișier de la sarcina %s', + 'Move the task to another swimlane when assigned to a user' => 'Mutați sarcina într-un alt culoar când este atribuită unui utilizator', + 'Destination swimlane' => 'Culoar destinație', + 'Assign a category when the task is moved to a specific swimlane' => 'Atribuiți o categorie când sarcina este mutată într-un anumit culoar', + 'Move the task to another swimlane when the category is changed' => 'Mutați sarcina într-un alt culoar când categoria este schimbată', + 'Reorder this column by priority (ASC)' => 'Rearanjați această coloană după prioritate (ASC)', + 'Reorder this column by priority (DESC)' => 'Rearanjați această coloană după prioritate (DESC)', + 'Reorder this column by assignee and priority (ASC)' => 'Rearanjați această coloană după responsabil și prioritate (ASC)', + 'Reorder this column by assignee and priority (DESC)' => 'Rearanjați această coloană după responsabil și prioritate (DESC)', + 'Reorder this column by assignee (A-Z)' => 'Rearanjați această coloană după responsabil (A-Z)', + 'Reorder this column by assignee (Z-A)' => 'Rearanjați această coloană după responsabil (Z-A)', + 'Reorder this column by due date (ASC)' => 'Rearanjați această coloană după data scadenței (ASC)', + 'Reorder this column by due date (DESC)' => 'Rearanjați această coloană după data scadenței (DESC)', + 'Reorder this column by id (ASC)' => 'Rearanjați această coloană după id (ASC)', + 'Reorder this column by id (DESC)' => 'Rearanjați această coloană după id (DESC)', + '%s moved the task #%d "%s" to the project "%s"' => '%s a mutat sarcina #%d "%s" la proiectul "%s"', + 'Task #%d "%s" has been moved to the project "%s"' => 'Sarcina #%d "%s" a fost mutată la proiectul "%s"', + 'Move the task to another column when the due date is less than a certain number of days' => 'Mutați sarcina într-o altă coloană când data scadenței este mai mică de un anumit număr de zile', + 'Automatically update the start date when the task is moved away from a specific column' => 'Actualizați automat data de începere când sarcina este mutată din o anumită coloană', + 'HTTP Client:' => 'Client HTTP:', + 'Assigned' => 'Atribuit', + 'Task limits apply to each swimlane individually' => 'Limite sarcini se aplică fiecărui culoar individual', + 'Column task limits apply to each swimlane individually' => 'Limite sarcini coloană se aplică fiecărui culoar individual', + 'Column task limits are applied to each swimlane individually' => 'Limite sarcini coloană sunt aplicate fiecărui culoar individual', + 'Column task limits are applied across swimlanes' => 'Limite sarcini coloană sunt aplicate pe toate culoarele', + 'Task limit: ' => 'Limita sarcini:', + 'Change to global tag' => 'Schimbă în etichetă globală', + 'Do you really want to make the tag "%s" global?' => 'Sigur doriți să faceți eticheta "%s" globală?', + 'Enable global tags for this project' => 'Activează etichetele globale pentru acest proiect', + 'Group membership(s):' => 'Apartenența la grup(uri):', + '%s is a member of the following group(s): %s' => '%s este membru al următorului(lor) grup(uri): %s', + '%d/%d group(s) shown' => '%d/%d grup(uri) afișate', + 'Subtask creation or modification' => 'Creare sau modificare subsarcină', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Atribuiți sarcina unui utilizator specific când sarcina este mutată într-un anumit culoar', + 'Comment' => 'Comentariu', + 'Collapse vertically' => 'Strânge vertical', + 'Expand vertically' => 'Extinde vertical', + 'MXN - Mexican Peso' => 'MXN - Peso Mexican', + 'Estimated vs actual time per column' => 'Timp estimat vs timp real pe coloană', + 'HUF - Hungarian Forint' => 'HUF - Forint Maghiar', + 'XBT - Bitcoin' => 'XBT - Bitcoin', + 'You must select a file to upload as your avatar!' => 'Trebuie să selectați un fișier pentru a-l încărca drept avatar!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'Fișierul încărcat nu este o imagine validă! (Doar *.gif, *.jpg, *.jpeg și *.png sunt permise!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Setează automat data scadenței când sarcina este mutată dintr-o anumită coloană', + 'No other projects found.' => 'Nu s-au găsit alte proiecte.', + 'Tasks copied successfully.' => 'Sarcini copiate cu succes.', + 'Unable to copy tasks.' => 'Nu se pot copia sarcinile.', + 'Theme' => 'Temă', + 'Theme:' => 'Temă:', + 'Light theme' => 'Temă deschisă', + 'Dark theme' => 'Temă închisă', + 'Automatic theme - Sync with system' => 'Temă automată - Sincronizare cu sistemul', + 'Application managers or more' => 'Manageri aplicație sau mai mult', + 'Administrators' => 'Administratori', + 'Visibility:' => 'Vizibilitate:', + 'Standard users' => 'Utilizatori standard', + 'Visibility is required' => 'Vizibilitatea este obligatorie', + 'The visibility should be an app role' => 'Vizibilitatea ar trebui să fie o rolă a aplicației', + 'Reply' => 'Răspunde', + '%s wrote: ' => '%s a scris: ', + 'Number of visible tasks in this column and swimlane' => 'Număr de sarcini vizibile în această coloană și culoar', + 'Number of tasks in this swimlane' => 'Număr de sarcini în acest culoar', + 'Unable to find another subtask in progress, you can close this window.' => 'Nu s-a găsit altă subsarcină în progres, puteți închide această fereastră.', + 'This theme is invalid' => 'Această temă este nevalidă', + 'This role is invalid' => 'Acest rol este nevalid', + 'This timezone is invalid' => 'Acest fus orar este nevalid', + 'This language is invalid' => 'Această limbă este nevalidă', + 'This URL is invalid' => 'Această adresă URL este nevalidă', + 'Date format invalid' => 'Format dată nevalid', + 'Time format invalid' => 'Format oră nevalid', + 'Invalid Mail transport' => 'Transport Mail nevalid', + 'Color invalid' => 'Culoare nevalidă', + 'This value must be greater or equal to %d' => 'Această valoare trebuie să fie mai mare sau egală cu %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Adăugați un BOM la începutul fișierului (necesar pentru Microsoft Excel)', + 'Just add these tag(s)' => 'Doar adăugați aceste etichetă(e)', + 'Remove internal link(s)' => 'Elimină legătură(i) internă(e)', + 'Import tasks from another project' => 'Importă sarcini dintr-un alt proiect', + 'Select the project to copy tasks from' => 'Selectați proiectul din care să copiați sarcinile', + 'The total maximum allowed attachments size is %sB.' => 'Dimensiunea totală maximă permisă pentru atașamente este %sB.', + 'Add attachments' => 'Adaugă atașamente', + 'Task #%d "%s" is overdue' => 'Sarcina #%d "%s" este întârziată', + 'Enable notifications by default for all new users' => 'Activează notificările implicit pentru toți utilizatorii noi', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Atribuie sarcina creatorului ei pentru coloane specifice dacă nu este setat manual niciun responsabil', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Atribuie sarcina utilizatorului autentificat la schimbarea coloanei către coloana specificată dacă nu este atribuit niciun utilizator', +]; diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php new file mode 100644 index 0000000..f5486ca --- /dev/null +++ b/app/Locale/ru_RU/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => ' ', + 'None' => 'Отсутствует', + 'Edit' => 'Изменить', + 'Remove' => 'Удалить', + 'Yes' => 'Да', + 'No' => 'Нет', + 'cancel' => 'Отменить', + 'or' => 'или', + 'Yellow' => 'Желтый', + 'Blue' => 'Синий', + 'Green' => 'Зеленый', + 'Purple' => 'Фиолетовый', + 'Red' => 'Красный', + 'Orange' => 'Оранжевый', + 'Grey' => 'Серый', + 'Brown' => 'Коричневый', + 'Deep Orange' => 'Темно-оранжевый', + 'Dark Grey' => 'Темно-серый', + 'Pink' => 'Розовый', + 'Teal' => 'Бирюзовый', + 'Cyan' => 'Голубой', + 'Lime' => 'Лимонный', + 'Light Green' => 'Светло-зеленый', + 'Amber' => 'Янтарный', + 'Save' => 'Сохранить', + 'Login' => 'Вход', + 'Official website:' => 'Официальный сайт:', + 'Unassigned' => 'Не назначена', + 'View this task' => 'Посмотреть задачу', + 'Remove user' => 'Удалить пользователя', + 'Do you really want to remove this user: "%s"?' => 'Вы хотите удалить пользователя: «%s»?', + 'All users' => 'Все пользователи', + 'Username' => 'Логин', + 'Password' => 'Пароль', + 'Administrator' => 'Администратор', + 'Sign in' => 'Войти', + 'Users' => 'Пользователи', + 'Forbidden' => 'Запрещено', + 'Access Forbidden' => 'Доступ запрещён', + 'Edit user' => 'Изменить пользователя', + 'Logout' => 'Выйти', + 'Bad username or password' => 'Неверное имя пользователя или пароль', + 'Edit project' => 'Изменить проект', + 'Name' => 'Имя', + 'Projects' => 'Проекты', + 'No project' => 'Нет проекта', + 'Project' => 'Проект', + 'Status' => 'Статус', + 'Tasks' => 'Задачи', + 'Board' => 'Доска', + 'Actions' => 'Действия', + 'Inactive' => 'Неактивен', + 'Active' => 'Активен', + 'Unable to update this board.' => 'Не удалось обновить эту доску.', + 'Disable' => 'Отключить', + 'Enable' => 'Включить', + 'New project' => 'Новый проект', + 'Do you really want to remove this project: "%s"?' => 'Вы точно хотите удалить проект: "%s"?', + 'Remove project' => 'Удалить проект', + 'Edit the board for "%s"' => 'Изменить доску для "%s"', + 'Add a new column' => 'Добавить новую колонку', + 'Title' => 'Название', + 'Assigned to %s' => 'Назначено %s', + 'Remove a column' => 'Удалить колонку', + 'Unable to remove this column.' => 'Не удалось удалить эту колонку.', + 'Do you really want to remove this column: "%s"?' => 'Вы точно хотите удалить эту колонку: "%s" ?', + 'Settings' => 'Настройки', + 'Application settings' => 'Настройки приложения', + 'Language' => 'Язык', + 'Webhook token:' => 'Webhooks токен :', + 'API token:' => 'API токен :', + 'Database size:' => 'Размер базы данных :', + 'Download the database' => 'Скачать базу данных', + 'Optimize the database' => 'Оптимизировать базу данных', + '(VACUUM command)' => '(Команда VACUUM)', + '(Gzip compressed Sqlite file)' => '(Сжать GZip файл SQLite)', + 'Close a task' => 'Закрыть задачу', + 'Column' => 'Колонка', + 'Color' => 'Цвет', + 'Assignee' => 'Назначена', + 'Create another task' => 'Создать другую задачу', + 'New task' => 'Новая задача', + 'Open a task' => 'Открыть задачу', + 'Do you really want to open this task: "%s"?' => 'Вы уверены, что хотите открыть задачу: "%s" ?', + 'Back to the board' => 'Вернуться на доску', + 'There is nobody assigned' => 'Никто не назначен', + 'Column on the board:' => 'Колонка на доске: ', + 'Close this task' => 'Закрыть задачу', + 'Open this task' => 'Открыть задачу', + 'There is no description.' => 'Нет описания.', + 'Add a new task' => 'Добавить новую задачу', + 'The username is required' => 'Необходимо имя пользователя', + 'The maximum length is %d characters' => 'Максимальная длина - %d знаков', + 'The minimum length is %d characters' => 'Минимальная длина - %d знаков', + 'The password is required' => 'Необходим пароль', + 'This value must be an integer' => 'Это значение должно быть целым числом', + 'The username must be unique' => 'Имя пользователя должно быть уникально', + 'The user id is required' => 'Необходим ID пользователя', + 'Passwords don\'t match' => 'Пароли не совпадают', + 'The confirmation is required' => 'Необходимо подтверждение', + 'The project is required' => 'Необходимо указать проект', + 'The id is required' => 'Необходим ID', + 'The project id is required' => 'Необходим ID проекта', + 'The project name is required' => 'Необходимо имя проекта', + 'The title is required' => 'Необходим заголовок', + 'Settings saved successfully.' => 'Параметры успешно сохранены.', + 'Unable to save your settings.' => 'Невозможно сохранить параметры.', + 'Database optimization done.' => 'База данных оптимизирована.', + 'Your project has been created successfully.' => 'Ваш проект успешно создан.', + 'Unable to create your project.' => 'Не удалось создать проект.', + 'Project updated successfully.' => 'Проект успешно обновлен.', + 'Unable to update this project.' => 'Не удалось обновить проект.', + 'Unable to remove this project.' => 'Не удалось удалить проект.', + 'Project removed successfully.' => 'Проект удалён.', + 'Project activated successfully.' => 'Проект активирован.', + 'Unable to activate 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.' => 'Не удалось обновить задачу.', + 'Task updated successfully.' => 'Задача обновлена.', + 'Unable to create your task.' => 'Не удалось создать задачу.', + 'Task created successfully.' => 'Задача создана.', + 'User created successfully.' => 'Пользователь создан.', + 'Unable to create your user.' => 'Не удалось создать пользователя.', + 'User updated successfully.' => 'Пользователь обновлён.', + 'User removed successfully.' => 'Пользователь удалён.', + 'Unable to remove this user.' => 'Не удалось удалить пользователя.', + 'Board updated successfully.' => 'Доска успешно обновлена.', + 'Ready' => 'Согласованные', + 'Backlog' => 'Ожидающие', + 'Work in progress' => 'В процессе', + 'Done' => 'Выполнено', + 'Application version:' => 'Версия приложения:', + 'Id' => 'ID', + 'Public link' => 'Ссылка для просмотра', + 'Timezone' => 'Часовой пояс', + 'Sorry, I didn\'t find this information in my database!' => 'К сожалению, информация в базе данных не найдена!', + 'Page not found' => 'Страница не найдена', + 'Complexity' => 'Сложность', + 'Task limit' => 'Лимит задач', + 'Task count' => 'Количество задач', + 'User' => 'Пользователь', + 'Comments' => 'Комментарии', + 'Comment is required' => 'Нужен комментарий', + 'Comment added successfully.' => 'Комментарий успешно добавлен.', + 'Unable to create your comment.' => 'Невозможно создать комментарий.', + 'Due Date' => 'Дата завершения', + 'Invalid date' => 'Неверная дата', + 'Automatic actions' => 'Автоматические действия', + 'Your automatic action has been created successfully.' => 'Автоматизированное действие успешно настроено.', + 'Unable to create your automatic action.' => 'Не удалось создать автоматизированное действие.', + 'Remove an action' => 'Удалить действие', + 'Unable to remove this action.' => 'Не удалось удалить действие', + 'Action removed successfully.' => 'Действие удалено.', + 'Automatic actions for the project "%s"' => 'Автоматические действия для проекта « %s »', + 'Add an action' => 'Добавить действие', + 'Event name' => 'Имя события', + 'Action' => 'Действие', + 'Event' => 'Событие', + 'When the selected event occurs execute the corresponding action.' => 'Когда случится ВЫБРАННОЕ событие выполняется СООТВЕТСТВУЮЩЕЕ действие.', + 'Next step' => 'Следующий шаг', + 'Define action parameters' => 'Задать параметры действия', + 'Do you really want to remove this action: "%s"?' => 'Вы точно хотите удалить это действие: « %s » ?', + 'Remove an automatic action' => 'Удалить автоматическое действие', + 'Assign the task to a specific user' => 'Назначить задачу определённому пользователю', + 'Assign the task to the person who does the action' => 'Назначить задачу тому кто выполнит действие', + 'Duplicate the task to another project' => 'Создать дубликат задачи в другом проекте', + 'Move a task to another column' => 'Переместить задачу в другую колонку', + 'Task modification' => 'Изменение задачи', + 'Task creation' => 'Создание задачи', + 'Closing a task' => 'Завершение задачи', + 'Assign a color to a specific user' => 'Назначить определённый цвет пользователю', + 'Position' => 'Расположение', + 'Duplicate to project' => 'Клонировать в другой проект', + 'Duplicate' => 'Клонировать', + 'Link' => 'Cсылка', + 'Comment updated successfully.' => 'Комментарий обновлён.', + 'Unable to update your comment.' => 'Не удалось обновить ваш комментарий.', + 'Remove a comment' => 'Удалить комментарий', + 'Comment removed successfully.' => 'Комментарий удалён.', + 'Unable to remove this comment.' => 'Не удалось удалить этот комментарий.', + 'Do you really want to remove this comment?' => 'Вы точно хотите удалить этот комментарий?', + 'Current password for the user "%s"' => 'Текущий пароль для пользователя "%s"', + 'The current password is required' => 'Требуется текущий пароль', + 'Wrong password' => 'Неверный пароль', + 'Unknown' => 'Неизвестно', + 'Last logins' => 'Последние посещения', + 'Login date' => 'Дата входа', + 'Authentication method' => 'Способ аутентификации', + 'IP address' => 'IP адрес', + 'User agent' => 'User agent', + 'Persistent connections' => 'Постоянные соединения', + 'No session.' => 'Нет сеанса', + 'Expiration date' => 'Дата окончания', + 'Remember Me' => 'Запомнить меня', + 'Creation date' => 'Дата создания', + 'Everybody' => 'Все', + 'Open' => 'Открытый', + 'Closed' => 'Закрытый', + 'Search' => 'Поиск', + 'Nothing found.' => 'Ничего не найдено.', + 'Due date' => 'Срок', + 'Description' => 'Описание', + '%d comments' => '%d комментариев', + '%d comment' => '%d комментарий', + 'Email address invalid' => 'Некорректный e-mail адрес', + 'Your external account is not linked anymore to your profile.' => 'Ваш внешний аккаунт больше не связан с Вашим профилем.', + 'Unable to unlink your external account.' => 'Не удалось отвязать Ваш внешний аккаунт.', + 'External authentication failed' => 'Внешняя авторизация не удалась', + 'Your external account is linked to your profile successfully.' => 'Ваш внешний аккаунт успешно подключён к профилю.', + 'Email' => 'E-mail', + 'Task removed successfully.' => 'Задача удалена.', + 'Unable to remove this task.' => 'Не удалось удалить эту задачу.', + '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' => 'Автоматически назначить категорию по цвету', + 'Task creation or modification' => 'Создание или изменение задачи', + 'Category' => 'Категория', + 'Category:' => 'Категория:', + 'Categories' => 'Категории', + 'Your category has been created successfully.' => 'Категория создана.', + 'This category has been updated successfully.' => 'Категория обновлена.', + 'Unable to update this category.' => 'Не удалось обновить категорию.', + 'Remove a category' => 'Удалить категорию', + 'Category removed successfully.' => 'Категория удалена.', + 'Unable to remove this category.' => 'Не удалось удалить категорию.', + 'Category modification for the project "%s"' => 'Изменение категории для проекта « %s »', + 'Category Name' => 'Название категории', + 'Add a new category' => 'Добавить новую категорию', + 'Do you really want to remove this category: "%s"?' => 'Вы точно хотите удалить категорию « %s » ?', + 'All categories' => 'Все категории', + 'No category' => 'Нет категории', + 'The name is required' => 'Требуется название', + 'Remove a file' => 'Удалить файл', + 'Unable to remove this file.' => 'Не удалось удалить файл.', + 'File removed successfully.' => 'Файл удалён.', + 'Attach a document' => 'Прикрепить файл', + 'Do you really want to remove this file: "%s"?' => 'Вы точно хотите удалить этот файл « %s » ?', + 'Attachments' => 'Вложения', + 'Edit the task' => 'Изменить задачу', + 'Add a comment' => 'Добавить комментарий', + 'Edit a comment' => 'Изменить комментарий', + 'Summary' => 'Сводка', + 'Time tracking' => 'Отслеживание времени', + 'Estimate:' => 'Запланировано:', + 'Spent:' => 'Затрачено:', + 'Do you really want to remove this sub-task?' => 'Вы точно хотите удалить подзадачу?', + 'Remaining:' => 'Осталось:', + 'hours' => 'часов', + 'estimated' => 'расчётное', + 'Sub-Tasks' => 'Подзадачи', + 'Add a sub-task' => 'Добавить подзадачу', + 'Original estimate' => 'Запланировано', + 'Create another sub-task' => 'Создать другую подзадачу', + 'Time spent' => 'Времени затрачено', + 'Edit a sub-task' => 'Изменить подзадачу', + 'Remove a sub-task' => 'Удалить подзадачу', + 'The time must be a numeric value' => 'Время должно быть числом!', + 'Todo' => 'К исполнению', + 'In progress' => 'В процессе', + 'Sub-task removed successfully.' => 'Подзадача удалена.', + 'Unable to remove this sub-task.' => 'Не удалось удалить подзадачу.', + 'Sub-task updated successfully.' => 'Подзадача обновлена.', + 'Unable to update your sub-task.' => 'Не удалось обновить подзадачу.', + 'Unable to create your sub-task.' => 'Не удалось создать подзадачу.', + 'Maximum size: ' => 'Максимальный размер: ', + 'Display another project' => 'Показать другой проект', + 'Created by %s' => 'Создано %s', + 'Tasks Export' => 'Экспорт задач', + 'Start Date' => 'Дата начала', + 'Execute' => 'Выполнить', + 'Task Id' => 'ID задачи', + 'Creator' => 'Автор', + 'Modification date' => 'Дата изменения', + 'Completion date' => 'Дата завершения', + 'Clone' => 'Клонировать', + 'Project cloned successfully.' => 'Проект клонирован.', + 'Unable to clone this project.' => 'Не удалось клонировать проект.', + 'Enable email notifications' => 'Включить уведомления по e-mail', + 'Task position:' => 'Позиция задачи:', + 'The task #%d has been opened.' => 'Задача #%d была открыта.', + 'The task #%d has been closed.' => 'Задача #%d была закрыта.', + 'Sub-task updated' => 'Подзадача обновлена', + 'Title:' => 'Название:', + 'Status:' => 'Статус:', + 'Assignee:' => 'Назначена:', + 'Time tracking:' => 'Отслеживание времени:', + 'New sub-task' => 'Новая подзадача', + 'New attachment added "%s"' => 'Добавлено вложение « %s »', + 'New comment posted by %s' => 'Новый комментарий добавлен « %s »', + 'New comment' => 'Новый комментарий', + 'Comment updated' => 'Комментарий обновлён', + 'New subtask' => 'Новая подзадача', + 'I only want to receive notifications for these projects:' => 'Я хочу получать уведомления только по этим проектам:', + 'view the task on Kanboard' => 'посмотреть задачу на Kanboard', + 'Public access' => 'Общий доступ', + 'Disable public access' => 'Отключить общий доступ', + 'Enable public access' => 'Включить общий доступ', + 'Public access disabled' => 'Общий доступ отключён', + 'Move the task to another project' => 'Переместить задачу в другой проект', + 'Move to project' => 'Переместить в другой проект', + 'Do you really want to duplicate this task?' => 'Вы точно хотите клонировать задачу?', + 'Duplicate a task' => 'Клонировать задачу', + 'External accounts' => 'Внешняя аутентификация', + 'Account type' => 'Тип профиля', + 'Local' => 'Локальный', + 'Remote' => 'Удалённый', + 'Enabled' => 'Включён', + 'Disabled' => 'Выключены', + 'Login:' => 'Логин:', + 'Full Name:' => 'Имя:', + 'Email:' => 'E-mail:', + 'Notifications:' => 'Уведомления:', + 'Notifications' => 'Уведомления', + 'Account type:' => 'Тип профиля:', + 'Edit profile' => 'Редактировать профиль', + 'Change password' => 'Сменить пароль', + 'Password modification' => 'Изменение пароля', + 'External authentications' => 'Внешняя аутентификация', + 'Never connected.' => 'Ранее не соединялось.', + 'No external authentication enabled.' => 'Нет активной внешней аутентификации.', + 'Password modified successfully.' => 'Пароль изменён.', + 'Unable to change the password.' => 'Не удалось сменить пароль.', + 'Change category' => 'Смена категории', + '%s updated the task %s' => '%s обновил задачу %s', + '%s opened the task %s' => '%s открыл задачу %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s переместил задачу %s на позицию #%d в колонке "%s"', + '%s moved the task %s to the column "%s"' => '%s переместил задачу %s в колонку "%s"', + '%s created the task %s' => '%s создал задачу %s', + '%s closed the task %s' => '%s закрыл задачу %s', + '%s created a subtask for the task %s' => '%s создал подзадачу для задачи %s', + '%s updated a subtask for the task %s' => '%s обновил подзадачу для задачи %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Назначено %s с окончанием %s/%sh', + 'Not assigned, estimate of %sh' => 'Не назначено, окончание %sh', + '%s updated a comment on the task %s' => '%s обновил комментарий к задаче %s', + '%s commented the task %s' => '%s прокомментировал задачу %s', + '%s\'s activity' => '%s активность', + 'RSS feed' => 'RSS лента', + '%s updated a comment 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', + '%s created the task #%d' => '%s создал задачу #%d', + '%s closed the task #%d' => '%s закрыл задачу #%d', + '%s opened the task #%d' => '%s открыл задачу #%d', + 'Activity' => 'Активность', + 'Default values are "%s"' => 'Колонки по умолчанию: "%s"', + 'Default columns for new projects (Comma-separated)' => 'Колонки по умолчанию для новых проектов (разделять запятой)', + 'Task assignee change' => 'Изменён назначенный', + '%s changed the assignee of the task #%d to %s' => '%s сменил назначенного для задачи #%d на %s', + '%s changed the assignee of the task %s to %s' => '%s сменил назначенного для задачи %s на %s', + 'New password for the user "%s"' => 'Новый пароль для пользователя "%s"', + 'Choose an event' => 'Выберите событие', + 'Create a task from an external provider' => 'Создать задачу из внешнего источника', + 'Change the assignee based on an external username' => 'Изменить назначенного основываясь на внешнем имени пользователя', + 'Change the category based on an external label' => 'Изменить категорию основываясь на внешнем ярлыке', + 'Reference' => 'Ссылка', + 'Label' => 'Ярлык', + 'Database' => 'База данных', + 'About' => 'Информация', + 'Database driver:' => 'Драйвер базы данных', + 'Board settings' => 'Настройки доски', + 'Webhook settings' => 'Параметры Webhook', + 'Reset token' => 'Перезагрузить токен', + 'API endpoint:' => 'API endpoint:', + 'Refresh interval for personal board' => 'Период обновления для персональных досок', + 'Refresh interval for public board' => 'Период обновления для публичных досок', + 'Task highlight period' => 'Время подсвечивания задачи', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Период (в секундах) в течении которого задача считается недавно изменённой (0 для выключения, 2 дня по умолчанию)', + 'Frequency in second (60 seconds by default)' => 'Частота в секундах (60 секунд по умолчанию)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Частота в секундах (0 для выключения, 10 секунд по умолчанию)', + 'Application URL' => 'URL приложения', + 'Token regenerated.' => 'Токен пересоздан', + 'Date format' => 'Формат даты', + 'ISO format is always accepted, example: "%s" and "%s"' => 'Время должно быть в ISO-формате, например: "%s" или "%s"', + 'New personal project' => 'Новый персональный проект', + 'This project is personal' => 'Это персональный проект', + 'Add' => 'Добавить', + 'Start date' => 'Дата начала', + 'Time estimated' => 'Запланировано', + 'There is nothing assigned to you.' => 'Вам ничего не назначено', + 'My tasks' => 'Мои задачи', + 'Activity stream' => 'Текущая активность', + 'Dashboard' => 'Панель управления', + 'Confirmation' => 'Подтверждение пароля', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Create a comment from an external provider' => 'Создать комментарий из внешнего источника', + 'Project management' => 'Управление проектами', + 'Columns' => 'Колонки', + 'Task' => 'Задача', + 'Percentage' => 'Процент', + 'Number of tasks' => 'Количество задач', + 'Task distribution' => 'Распределение задач', + 'Analytics' => 'Аналитика', + 'Subtask' => 'Подзадача', + 'User repartition' => 'Перераспределение пользователей', + 'Clone this project' => 'Клонировать проект', + 'Column removed successfully.' => 'Колонка успешно удалена.', + 'Not enough data to show the graph.' => 'Недостаточно данных, чтобы показать график.', + 'Previous' => 'Предыдущий', + 'The id must be an integer' => 'Этот id должен быть целочисленным', + 'The project id must be an integer' => 'Id проекта должен быть целочисленным', + 'The status must be an integer' => 'Статус должен быть целочисленным', + 'The subtask id is required' => 'Id подзадачи обязателен', + 'The subtask id must be an integer' => 'Id подзадачи должен быть целочисленным', + 'The task id is required' => 'Id задачи обязателен', + 'The task id must be an integer' => 'Id задачи должен быть целочисленным', + 'The user id must be an integer' => 'Id пользователя должен быть целочисленным', + 'This value is required' => 'Это значение обязательно', + 'This value must be numeric' => 'Это значение должно быть цифровым', + 'Unable to create this task.' => 'Невозможно создать задачу.', + 'Cumulative flow diagram' => 'Накопительная диаграмма', + 'Daily project summary' => 'Ежедневное состояние проекта', + 'Daily project summary export' => 'Экспорт ежедневного резюме проекта', + 'Exports' => 'Экспорт', + 'This export contains the number of tasks per column grouped per day.' => 'Этот экспорт содержит ряд задач в колонках, сгруппированные по дням.', + 'Active swimlanes' => 'Активные дорожки', + 'Add a new swimlane' => 'Добавить новую дорожку', + 'Default swimlane' => 'Стандартная дорожка', + 'Do you really want to remove this swimlane: "%s"?' => 'Вы действительно хотите удалить дорожку "%s"?', + 'Inactive swimlanes' => 'Неактивные дорожки', + 'Remove a swimlane' => 'Удалить дорожку', + 'Swimlane modification for the project "%s"' => 'Редактирование дорожки для проекта "%s"', + 'Swimlane removed successfully.' => 'Дорожка успешно удалена', + 'Swimlanes' => 'Дорожки', + 'Swimlane updated successfully.' => 'Дорожка успешно обновлена.', + 'Unable to remove this swimlane.' => 'Невозможно удалить дорожку.', + 'Unable to update this swimlane.' => 'Невозможно обновить дорожку.', + 'Your swimlane has been created successfully.' => 'Ваша дорожка была успешно создана.', + 'Example: "Bug, Feature Request, Improvement"' => 'Например: "Баг, Фича, Улучшение"', + 'Default categories for new projects (Comma-separated)' => 'Стандартные категории для нового проекта (разделяются запятыми)', + 'Integrations' => 'Интеграции', + 'Integration with third-party services' => 'Интеграция со сторонними сервисами', + 'Subtask Id' => 'Id подзадачи', + 'Subtasks' => 'Подзадачи', + 'Subtasks Export' => 'Экспортировать подзадачи', + 'Task Title' => 'Заголовок задачи', + 'Untitled' => 'Заголовок отсутствует', + 'Application default' => 'Значение по умолчанию из приложения', + 'Language:' => 'Язык:', + 'Timezone:' => 'Временная зона:', + 'All columns' => 'Все колонки', + 'Next' => 'Следующий', + '#%d' => '#%d', + 'All swimlanes' => 'Все дорожки', + 'All colors' => 'Все цвета', + 'Moved to column %s' => 'Перемещена в колонку %s', + 'User dashboard' => 'Панель управления', + 'Allow only one subtask in progress at the same time for a user' => 'Разрешена только одна подзадача в разработке одновременно для одного пользователя', + 'Edit column "%s"' => 'Редактировать колонку "%s"', + '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?' => 'Какие части проекта должны быть дублированы?', + 'Disallow login form' => 'Запретить форму входа', + 'Start' => 'Начало', + 'End' => 'Конец', + 'Task age in days' => 'Возраст задачи в днях', + 'Days in this column' => 'Дней в этой колонке', + '%dd' => '%dd', + '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' => 'Обновление ссылки', + 'Opposite label' => 'Ярлык напротив', + 'Remove a link' => 'Удалить ссылку', + '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' => '<1ч', + '%dh' => '%dh', + 'Expand tasks' => 'Развернуть задачи', + 'Collapse tasks' => 'Свернуть задачи', + 'Expand/collapse tasks' => 'Развернуть/свернуть задачи', + 'Close dialog box' => 'Закрыть диалог', + 'Submit a form' => 'Отправить форму', + 'Board view' => 'Просмотр доски', + 'Keyboard shortcuts' => 'Горячие клавиши', + 'Open board switcher' => 'Открыть переключатель доски', + 'Application' => 'Приложение', + 'Compact view' => 'Компактный вид', + 'Horizontal scrolling' => 'Широкий вид', + 'Compact/wide view' => 'Компактный/широкий вид', + 'Currency' => 'Валюта', + 'Personal project' => 'Персональный проект', + 'AUD - Australian Dollar' => 'AUD - Австралийский доллар', + 'CAD - Canadian Dollar' => 'CAD - Канадский доллар', + 'CHF - Swiss Francs' => 'CHF - Швейцарский франк', + 'Custom Stylesheet' => 'Пользовательский стиль', + 'EUR - Euro' => 'EUR - Евро', + 'GBP - British Pound' => 'GBP - Британский фунт', + 'INR - Indian Rupee' => 'INR - Индийский рупий', + 'JPY - Japanese Yen' => 'JPY - Японская йена', + 'NZD - New Zealand Dollar' => 'NZD - Новозеландский доллар', + 'PEN - Peruvian Sol' => 'PEN - Перуанский соль', + 'RSD - Serbian dinar' => 'RSD - Сербский динар', + 'CNY - Chinese Yuan' => 'CNY - Китайский юань', + 'USD - US Dollar' => 'USD - Американский доллар', + 'VES - Venezuelan Bolívar' => 'VES - Венесуэльский боливар', + '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' => 'Исходная колонка', + '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' => 'Изменить справочник валют', + 'Reference currency' => 'Справочник валют', + 'The currency rate has been added successfully.' => 'Курс валюты был успешно добавлен.', + 'Unable to add this currency rate.' => 'Невозможно добавить этот курс валюты.', + 'Webhook URL' => 'Webhook URL', + '%s removed the assignee of the task %s' => '%s удалил назначенного на задачу %s', + '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' => 'Двухфакторная авторизация', + 'This QR code contains the key URI: ' => 'Это QR-код содержит ключевую URI:', + 'Check my code' => 'Проверить мой код', + 'Secret key: ' => 'Секретный ключ: ', + 'Test your device' => 'Проверьте свое устройство', + 'Assign a color when the task is moved to a specific column' => 'Назначить цвет, когда задача перемещается в определенную колонку', + '%s via Kanboard' => '%s через Kanboard', + 'Burndown chart' => 'Диаграмма сгорания', + 'This chart show the task complexity over the time (Work Remaining).' => 'Эта диаграмма показывает сложность задачи по времени (оставшейся работы).', + 'Screenshot taken %s' => 'Скриншот сделан %s', + 'Add a screenshot' => 'Прикрепить картинку', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Сделайте скриншот и нажмите CTRL+V или ⌘+V для вложения', + 'Screenshot uploaded successfully.' => 'Скриншот успешно загружен', + 'SEK - Swedish Krona' => 'SEK - Шведская крона', + 'Identifier' => 'Идентификатор', + 'Disable two factor authentication' => 'Выключить двухфакторную авторизацию', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Вы действительно хотите выключить двухфакторную авторизацию для пользователя "%s"?', + 'Edit link' => 'Редактировать ссылку', + 'Start to type task title...' => 'Начните вводить название задачи...', + 'A task cannot be linked to itself' => 'Задача не может быть связана с собой же', + 'The exact same link already exists' => 'Такая ссылка уже существует', + 'Recurrent task is scheduled to be generated' => 'Периодическая задача запланирована к созданию', + 'Score' => 'Оценка', + 'The identifier must be unique' => 'Идентификатор должен быть уникальным', + 'This linked task id doesn\'t exists' => 'Этот ID связанной задачи не существует', + 'This value must be alphanumeric' => 'Это значение должно быть буквенно-цифровым', + 'Edit recurrence' => 'Редактировать повторы', + 'Generate recurrent task' => 'Создать повторяющуюся задачу', + 'Trigger to generate recurrent task' => 'Триггер для генерации периодической задачи', + 'Factor to calculate new due date' => 'Коэффициент для расчёта новой даты завершения', + 'Timeframe to calculate new due date' => 'Временные рамки для расчёта новой даты завершения', + 'Base date to calculate new due date' => 'Базовая дата вычисления новой даты завершения', + 'Action date' => 'Дата действия', + 'Base date to calculate new due date: ' => 'Базовая дата вычисления новой даты завершения: ', + 'This task has created this child task: ' => 'Эта задача создала эту дочернюю задачу:', + 'Day(s)' => 'День(й)', + 'Existing due date' => 'Существующая дата завершения', + 'Factor to calculate new due date: ' => 'Коэффициент для расчёта новой даты завершения: ', + 'Month(s)' => 'Месяц(а)', + 'This task has been created by: ' => 'Эта задача была создана: ', + 'Recurrent task has been generated:' => 'Периодическая задача была сформирована:', + 'Timeframe to calculate new due date: ' => 'Временные рамки для расчёта новой даты завершения: ', + 'Trigger to generate recurrent task: ' => 'Триггер для генерации периодической задачи: ', + 'When task is closed' => 'Когда задача закрывается', + 'When task is moved from first column' => 'Когда задача перемещается из первой колонки', + 'When task is moved to last column' => 'Когда задача перемещается в последнюю колонку', + 'Year(s)' => 'Год(а)', + 'Project settings' => 'Настройки проекта', + 'Automatically update the start date' => 'Автоматическое обновление даты начала', + 'iCal feed' => 'iCal данные', + 'Preferences' => 'Предпочтения', + 'Security' => 'Безопасность', + 'Two factor authentication disabled' => 'Двухфакторная аутентификация отключена', + 'Two factor authentication enabled' => 'Включена двухфакторная аутентификация', + 'Unable to update this user.' => 'Не удаётся обновить этого пользователя.', + 'There is no user management for personal projects.' => 'Для персональных проектов управление пользователями не предусмотрено.', + 'User that will receive the email' => 'Пользователь, который будет получать e-mail', + 'Email subject' => 'Тема e-mail', + 'Date' => 'Дата', + 'Add a comment log when moving the task between columns' => 'Добавлять запись при перемещении задачи между колонками', + 'Move the task to another column when the category is changed' => 'Переносить задачи в другую колонку при изменении категории', + 'Send a task by email to someone' => 'Отправить задачу по email', + 'Reopen a task' => 'Переоткрыть задачу', + 'Notification' => 'Уведомления', + '%s moved the task #%d to the first swimlane' => '%s переместил задачу #%d на первую дорожку', + 'Swimlane' => 'Дорожки', + '%s moved the task %s to the first swimlane' => '%s переместил задачу %s на первую дорожку', + '%s moved the task %s to the swimlane "%s"' => '%s переместил задачу %s на дорожку "%s"', + 'This report contains all subtasks information for the given date range.' => 'Этот отчёт содержит всю информацию подзадач в заданном диапазоне дат.', + 'This report contains all tasks information for the given date range.' => 'Этот отчёт содержит всю информацию для задачи в заданном диапазоне дат.', + 'Project activities for %s' => 'Активность проекта для %s', + 'view the board on Kanboard' => 'посмотреть доску на Kanboard', + 'The task has been moved to the first swimlane' => 'Эта задача была перемещена в первую дорожку', + 'The task has been moved to another swimlane:' => 'Эта задача была перемещена в другую дорожку:', + 'New title: %s' => 'Новый заголовок: %s', + 'The task is not assigned anymore' => 'Задача больше не назначена', + 'New assignee: %s' => 'Новый назначенный: %s', + 'There is no category now' => 'В настоящее время здесь нет категорий', + 'New category: %s' => 'Новая категория: %s', + 'New color: %s' => 'Новый цвет: %s', + 'New complexity: %d' => 'Новая сложность: %d', + 'The due date has been removed' => 'Дата завершения была удалена', + 'There is no description anymore' => 'Здесь больше нет описания', + 'Recurrence settings has been modified' => 'Настройки повтора были изменены', + 'Time spent changed: %sh' => 'Изменение количества затраченного времени: %sh', + 'Time estimated changed: %sh' => 'Ожидаемый срок изменён: %sh', + 'The field "%s" has been updated' => 'Поле "%s", было изменено', + 'The description has been modified:' => 'Описание было изменено', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Вы действительно хотите закрыть задачу "%s", а также все подзадачи?', + 'I want to receive notifications for:' => 'Я хочу получать уведомления для:', + 'All tasks' => 'Все задачи', + 'Only for tasks assigned to me' => 'Только для задач, назначенных мне', + 'Only for tasks created by me' => 'Только для задач, созданных мной', + 'Only for tasks created by me and tasks assigned to me' => 'Только для задач, созданных мной и назначенных мне', + '%%Y-%%m-%%d' => '%%Y-%%m-%%d', + 'Total for all columns' => 'Суммарно для всех колонок', + 'You need at least 2 days of data to show the chart.' => 'Для отображения диаграммы нужно по крайней мере 2 дня.', + '<15m' => '<15м', + '<30m' => '<30м', + 'Stop timer' => 'Остановить таймер', + 'Start timer' => 'Запустить таймер', + 'My activity stream' => 'Лента моей активности', + 'Search tasks' => 'Поиск задачи', + 'Reset filters' => 'Сбросить фильтры', + 'My tasks due tomorrow' => 'Мои задачи на завтра', + 'Tasks due today' => 'Задачи, завершающиеся сегодня', + 'Tasks due tomorrow' => 'Задачи, завершающиеся завтра', + 'Tasks due yesterday' => 'Задачи, завершившиеся вчера', + 'Closed tasks' => 'Закрытые задачи', + 'Open tasks' => 'Открытые задачи', + 'Not assigned' => 'Не назначенные', + 'View advanced search syntax' => 'Просмотр расширенного синтаксиса поиска', + 'Overview' => 'Обзор', + 'Board/Calendar/List view' => 'Просмотр Доска/Календарь/Список', + 'Switch to the board view' => 'Переключиться в режим доски', + 'Switch to the list view' => 'Переключиться в режим списка', + 'Go to the search/filter box' => 'Перейти в поиск/фильтр', + 'There is no activity yet.' => 'Активности еще не было', + 'No tasks found.' => 'Задач не найдено.', + 'Keyboard shortcut: "%s"' => 'Сочетание клавиш: "%s"', + 'List' => 'Список', + 'Filter' => 'Фильтр', + 'Advanced search' => 'Расширенный поиск', + 'Example of query: ' => 'Пример запроса: ', + 'Search by project: ' => 'Поиск по проекту: ', + 'Search by column: ' => 'Поиск по колонкам: ', + 'Search by assignee: ' => 'Поиск по назначенному: ', + 'Search by color: ' => 'Поиск по цвету: ', + 'Search by category: ' => 'Поиск по категориям: ', + 'Search by description: ' => 'Поиск по описанию: ', + 'Search by due date: ' => 'Поиск по дате завершения: ', + 'Average time spent in each column' => 'Затрачено времени в среднем в каждой колонке', + 'Average time spent' => 'Затрачено времени в среднем', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Эта диаграмма показывает среднее время, проведенное в каждой колонке для последних %d задач.', + 'Average Lead and Cycle time' => 'Среднее время выполнения и цикла', + 'Average lead time: ' => 'Среднее время выполнения: ', + 'Average cycle time: ' => 'Среднее время цикла: ', + 'Cycle Time' => 'Время цикла', + 'Lead Time' => 'Время выполнения', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Эта диаграмма показывает среднее время выполнения и цикла для последних %d задач.', + 'Average time into each column' => 'Среднее время в каждом столбце', + 'Lead and cycle time' => 'Время выполнения и цикла', + 'Lead time: ' => 'Время выполнения:', + 'Cycle time: ' => 'Время цикла:', + 'Time spent in each column' => 'Время, проведенное в каждой колонке', + 'The lead time is the duration between the task creation and the completion.' => 'Время выполнения - период между созданием задачи и её завершением.', + 'The cycle time is the duration between the start date and the completion.' => 'Время цикла - период времени между датой начала и завершения.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Если задача не закрыта, то текущая дата будет указана в дате завершения задачи.', + 'Set the start date automatically' => 'Установить дату начала автоматически', + 'Edit Authentication' => 'Редактировать авторизацию', + 'Remote user' => 'Удаленный пользователь', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Учётные данные для входа через LDAP, Google и Github не будут сохранены в Kanboard.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Если вы установите флажок "Запретить форму входа", учётные данные, введенные в форму входа будет игнорироваться.', + 'Default task color' => 'Стандартные цвета задач', + 'This feature does not work with all browsers.' => 'Эта функция доступна не во всех браузерах.', + 'There is no destination project available.' => 'Нет доступного для назначения проекта.', + 'Trigger automatically subtask time tracking' => 'Триггер автоматического отслеживания времени подзадач', + 'Include closed tasks in the cumulative flow diagram' => 'Включить в диаграмму закрытые задачи', + 'Current swimlane: %s' => 'Текущая дорожка: %s', + 'Current column: %s' => 'Текущая колонка: %s', + 'Current category: %s' => 'Текущая категория: %s', + 'no category' => 'без категории', + 'Current assignee: %s' => 'Текущее назначенное лицо: %s', + 'not assigned' => 'не назначен', + 'Author:' => 'Автор:', + 'contributors' => 'соавторы', + 'License:' => 'Лицензия:', + 'License' => 'Лицензия', + 'Enter the text below' => 'Введите текст ниже', + 'Start date:' => 'Дата начала:', + 'Due date:' => 'Дата завершения:', + 'People who are project managers' => 'Люди, которые являются менеджерами проекта', + 'People who are project members' => 'Люди, которые являются участниками проекта', + 'NOK - Norwegian Krone' => 'НК - Норвежская крона', + 'Show this column' => 'Показать эту колонку', + 'Hide this column' => 'Спрятать эту колонку', + 'End date' => 'Дата завершения', + 'Users overview' => 'Обзор пользователей', + 'Members' => 'Участники', + 'Shared project' => 'Общие/публичные проекты', + 'Project managers' => 'Менеджер проекта', + 'Projects list' => 'Список проектов', + 'End date:' => 'Дата завершения:', + 'Change task color when using a specific task link' => 'Изменение цвета задач при использовании ссылки на определенные задачи', + 'Task link creation or modification' => 'Ссылка на создание или модификацию задачи', + 'Milestone' => 'Веха', + 'Reset the search/filter box' => 'Сбросить поиск/фильтр', + 'Documentation' => 'Документация', + 'Author' => 'Автор', + 'Version' => 'Версия', + 'Plugins' => 'Плагины', + 'There is no plugin loaded.' => 'Нет установленных плагинов.', + 'My notifications' => 'Мои уведомления', + 'Custom filters' => 'Пользовательские фильтры', + 'Your custom filter has been created successfully.' => 'Фильтр был успешно создан.', + 'Unable to create your custom filter.' => 'Невозможно создать фильтр.', + 'Custom filter removed successfully.' => 'Пользовательский фильтр был успешно удален.', + 'Unable to remove this custom filter.' => 'Невозможно удалить фильтр.', + 'Edit custom filter' => 'Изменить пользовательский фильтр', + 'Your custom filter has been updated successfully.' => 'Пользовательский фильтр был успешно обновлен.', + 'Unable to update custom filter.' => 'Невозможно обновить фильтр.', + 'Web' => 'Интернет', + 'New attachment on task #%d: %s' => 'Новое вложение для задачи #%d: %s', + 'New comment on task #%d' => 'Новый комментарий для задачи #%d', + 'Comment updated on task #%d' => 'Обновлен комментарий у задачи #%d', + 'New subtask on task #%d' => 'Новая подзадача у задачи #%d', + 'Subtask updated on task #%d' => 'Подзадача обновлена у задачи #%d', + 'New task #%d: %s' => 'Новая задача #%d: %s', + 'Task updated #%d' => 'Обновлена задача #%d', + 'Task #%d closed' => 'Задача #%d закрыта', + 'Task #%d opened' => 'Задача #%d открыта', + 'Column changed for task #%d' => 'Обновлена колонка у задачи #%d', + 'New position for task #%d' => 'Новая позиция для задачи #%d', + 'Swimlane changed for task #%d' => 'Изменена дорожка у задачи #%d', + 'Assignee changed on task #%d' => 'Изменён назначенный у задачи #%d', + '%d overdue tasks' => '%d просроченных задач', + 'No notification.' => 'Нет новых уведомлений.', + 'Mark all as read' => 'Пометить все прочитанными', + 'Mark as read' => 'Пометить прочитанным', + 'Total number of tasks in this column across all swimlanes' => 'Общее число задач в этой колонке на всех дорожках', + 'Collapse swimlane' => 'Свернуть дорожку', + 'Expand swimlane' => 'Развернуть дорожку', + 'Add a new filter' => 'Добавить новый фильтр', + 'Share with all project members' => 'Сделать общим для всех участников проекта', + 'Shared' => 'Общие', + 'Owner' => 'Владелец', + 'Unread notifications' => 'Непрочитанные уведомления', + 'Notification methods:' => 'Способы уведомления:', + 'Unable to read your file' => 'Невозможно прочитать файл', + '%d task(s) have been imported successfully.' => '%d задач было успешно импортировано.', + 'Nothing has been imported!' => 'Ничего не было импортировано!', + 'Import users from CSV file' => 'Импорт пользователей из CSV-файла', + '%d user(s) have been imported successfully.' => '%d пользователей было успешно импортировано.', + 'Comma' => 'Запятая', + 'Semi-colon' => 'Точка с запятой', + 'Tab' => 'Пробел (Tab)', + 'Vertical bar' => 'Вертикальная черта (|)', + 'Double Quote' => 'Одинарные кавычки', + 'Single Quote' => 'Двойные кавычки', + '%s attached a file to the task #%d' => '%s добавил файл к задаче #%d', + 'There is no column or swimlane activated in your project!' => 'В вашей задаче нет активных колонок или дорожек!', + 'Append filter (instead of replacement)' => 'Добавляющий фильтр (не заменяющий)', + 'Append/Replace' => 'Добавление/Замена', + 'Append' => 'Добавление', + 'Replace' => 'Замена', + 'Import' => 'Импорт', + 'Change sorting' => 'изменить сортировку', + 'Tasks Importation' => 'Импортирование задач', + 'Delimiter' => 'Разделитель', + 'Enclosure' => 'Тип кавычек', + 'CSV File' => 'CSV-файл', + 'Instructions' => 'Инструкции', + 'Your file must use the predefined CSV format' => 'Ваш файл должен использовать структуру формата CSV', + 'Your file must be encoded in UTF-8' => 'Ваш файл должен иметь кодировку UTF-8', + 'The first row must be the header' => 'В первой строке должны быть заголовки столбцов', + 'Duplicates are not verified for you' => 'Проверка на дубликаты не осуществляется', + 'The due date must use the ISO format: YYYY-MM-DD' => 'Дата завершения должна быть в формате ISO: ГГГГ-ММ-ДД', + 'Download CSV template' => 'Скачать шаблон CSV-файла', + 'No external integration registered.' => 'Нет зарегистрированных внешних интеграций.', + 'Duplicates are not imported' => 'Дубликаты не импортируются', + 'Usernames must be lowercase and unique' => 'Логины пользователей должны быть строчными и уникальными', + 'Passwords will be encrypted if present' => 'Пароли будут зашифрованы (если указаны)', + '%s attached a new file to the task %s' => '%s добавил новый файл к задаче %s', + 'Link type' => 'Тип ссылки', + 'Assign automatically a category based on a link' => 'Автоматически назначить категории на основе ссылки', + 'BAM - Konvertible Mark' => 'BAM - Конвертируемая марка', + 'Assignee Username' => 'Логин назначенного', + 'Assignee Name' => 'Имя назначенного', + 'Groups' => 'Группы', + 'Members of %s' => 'Участник группы %s', + 'New group' => 'Новая группа', + 'Group created successfully.' => 'Группа успешно создана.', + 'Unable to create your group.' => 'Невозможно создать группу.', + 'Edit group' => 'Изменить группу', + 'Group updated successfully.' => 'Группы успешно обновлена.', + 'Unable to update your group.' => 'Невозможно обновить группу.', + 'Add group member to "%s"' => 'Добавить участника в "%s"', + 'Group member added successfully.' => 'Участник группы успешно добавлен.', + 'Unable to add group member.' => 'Невозможно добавить участника.', + 'Remove user from group "%s"' => 'Удалить пользователя из группы "%s"', + 'User removed successfully from this group.' => 'Пользователь успешно удален из группы.', + 'Unable to remove this user from the group.' => 'Невозможно удалить пользователя из группы.', + 'Remove group' => 'Удалить группу', + 'Group removed successfully.' => 'Группа успешно удалена.', + 'Unable to remove this group.' => 'Невозможно удалить группу.', + 'Project Permissions' => 'Разрешения проекта', + 'Manager' => 'Менеджер', + 'Project Manager' => 'Менеджер проекта', + 'Project Member' => 'Участник проекта', + 'Project Viewer' => 'Наблюдатель проекта', + 'Your account is locked for %d minutes' => 'Ваш аккаунт заблокирован на %d минут', + 'Invalid captcha' => 'Неверный код подтверждения', + 'The name must be unique' => 'Имя должно быть уникальным', + 'View all groups' => 'Просмотр всех групп', + 'There is no user available.' => 'Нет доступных пользователей.', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Вы действительно хотите удалить пользователя "%s" из группы "%s"?', + 'There is no group.' => 'Нет созданных групп.', + 'Add group member' => 'Добавить участника в группу', + 'Do you really want to remove this group: "%s"?' => 'Вы действительно хотите удалить группу "%s"?', + 'There is no user in this group.' => 'В этой группе нет участников.', + 'Permissions' => 'Разрешения', + 'Allowed Users' => 'Разрешенные пользователи', + 'No specific user has been allowed.' => 'Нет заданных разрешений для пользователей.', + 'Role' => 'Роль', + 'Enter user name...' => 'Введите логин пользователя...', + 'Allowed Groups' => 'Разрешенные группы', + 'No group has been allowed.' => 'Нет заданных разрешений для групп.', + 'Group' => 'Группа', + 'Group Name' => 'Имя группы', + 'Enter group name...' => 'Введите имя группы...', + 'Role:' => 'Роль:', + 'Project members' => 'Участники проекта', + '%s mentioned you in the task #%d' => '%s упомянул вас в задаче #%d', + '%s mentioned you in a comment on the task #%d' => '%s упомянул вас в комментарии к задаче #%d', + 'You were mentioned in the task #%d' => 'Вы упомянуты в задаче #%d', + 'You were mentioned in a comment on the task #%d' => 'Вы упомянуты в комментарии к задаче #%d', + 'Estimated hours: ' => 'Запланировано часов: ', + 'Actual hours: ' => 'Реально затрачено часов: ', + 'Hours Spent' => 'Затрачено часов', + 'Hours Estimated' => 'Запланировано часов', + 'Estimated Time' => 'Запланировано времени', + 'Actual Time' => 'Затрачено времени', + 'Estimated vs actual time' => 'Запланировано и реально затрачено времени', + 'RUB - Russian Ruble' => 'РУБ - Российский рубль', + 'Assign the task to the person who does the action when the column is changed' => 'Назначить задачу пользователю, который произвёл изменение в колонке', + 'Close a task in a specific column' => 'Закрыть задачу в выбранной колонке', + 'Time-based One-time Password Algorithm' => 'Time-based One-time Password Algorithm', + 'Two-Factor Provider: ' => 'Провайдер двухфакторной авторизации: ', + 'Disable two-factor authentication' => 'Отключить двухфакторную авторизацию', + 'Enable two-factor authentication' => 'Включить двухфакторную авторизацию', + 'There is no integration registered at the moment.' => 'Интеграции в данный момент не зарегистрированы.', + 'Password Reset for Kanboard' => 'Сброс пароля для Kanboard', + 'Forgot password?' => 'Забыли пароль?', + 'Enable "Forget Password"' => 'Включить возможность восстановления пароля', + 'Password Reset' => 'Сброс пароля', + 'New password' => 'Новый пароль', + 'Change Password' => 'Изменить пароль', + 'To reset your password click on this link:' => 'Чтобы изменить пароль нажмите на эту ссылку:', + 'Last Password Reset' => 'Последний сброс пароля', + 'The password has never been reinitialized.' => 'Пароль никогда не был сброшен.', + 'Creation' => 'Создан', + 'Expiration' => 'Истекает', + 'Password reset history' => 'История сброса пароля', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Все задачи для колонки "%s" и дорожки "%s" были успешно закрыты.', + 'Do you really want to close all tasks of this column?' => 'Вы действительно хотите закрыть все задачи из этой колонки?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d задач в колонке "%s" и дорожке "%s" будут закрыты.', + 'Close all tasks in this column and this swimlane' => 'Закрыть все задачи в этой колонке', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Нет плагинов уведомлений проекта. Вы можете настроить индивидуальные уведомления в вашем профиле пользователя.', + 'My dashboard' => 'Моя панель управления', + 'My profile' => 'Мой профиль', + 'Project owner: ' => 'Владелец проекта:', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'Идентификатор проекта не обязателен и должен содержать буквенно-цифровые символы, пример: MYPROJECT', + 'Project owner' => 'Владелец проекта', + 'Personal projects do not have users and groups management.' => 'Персональные проекты не имеют управления пользователями и группами.', + 'There is no project member.' => 'Нет участников проекта.', + 'Priority' => 'Приоритет', + 'Task priority' => 'Приоритет задачи', + 'General' => 'Общее', + 'Dates' => 'Даты', + 'Default priority' => 'Приоритет по умолчанию', + 'Lowest priority' => 'Наименьший приоритет', + 'Highest priority' => 'Наивысший приоритет', + 'Close a task when there is no activity' => 'Закрывать задачу, когда нет активности', + 'Duration in days' => 'Длительность в днях', + 'Send email when there is no activity on a task' => 'Отправлять email, когда активность по задаче отсутствует', + 'Unable to fetch link information.' => 'Не удалось получить информацию о ссылке', + 'Daily background job for tasks' => 'Ежедневные фоновые работы для задач', + 'Auto' => 'Авто', + 'Related' => 'Связано', + 'Attachment' => 'Вложение', + 'Web Link' => 'Web-ссылка', + 'External links' => 'Внешние ссылки', + 'Add external link' => 'Добавить внешнюю ссылку', + 'Type' => 'Тип', + 'Dependency' => 'Зависимость', + 'Add internal link' => 'Добавить внутреннюю ссылку', + 'Add a new external link' => 'Добавить новую внешнюю ссылку', + 'Edit external link' => 'Изменить внешнюю ссылку', + 'External link' => 'Внешняя ссылка', + 'Copy and paste your link here...' => 'Скопируйте и вставьте вашу ссылку здесь', + 'URL' => 'URL', + 'Internal links' => 'Внутренние ссылки', + 'Assign to me' => 'Связать со мной', + 'Me' => 'Мне', + 'Do not duplicate anything' => 'Не дублировать ничего', + 'Projects management' => 'Управление проектами', + 'Users management' => 'Управление пользователями', + 'Groups management' => 'Управление группами', + 'Create from another project' => 'Создать из другого проекта', + 'open' => 'открыто', + 'closed' => 'закрыто', + 'Priority:' => 'Приоритет:', + 'Reference:' => 'Ссылка:', + 'Complexity:' => 'Сложность:', + 'Swimlane:' => 'Дорожка:', + 'Column:' => 'Колонка:', + 'Position:' => 'Позиция:', + 'Creator:' => 'Создатель:', + 'Time estimated:' => 'Оценочное время:', + '%s hours' => '%s часов', + 'Time spent:' => 'Времени потрачено:', + 'Created:' => 'Создана:', + 'Modified:' => 'Изменена:', + 'Completed:' => 'Завершена:', + 'Started:' => 'Начата:', + 'Moved:' => 'Перемещена:', + 'Task #%d' => 'Задача #%d', + 'Time format' => 'Формат времени', + 'Start date: ' => 'Дата начала: ', + 'End date: ' => 'Дата окончания: ', + 'New due date: ' => 'Новая дата завершения: ', + 'Start date changed: ' => 'Дата начала изменена:', + 'Disable personal projects' => 'Выключить персональные проекты', + 'Do you really want to remove this custom filter: "%s"?' => 'Вы точно ходите удалить этот пользовательский фильтр: "%s"?', + 'Remove a custom filter' => 'Удалить пользовательский фильтр', + 'User activated successfully.' => 'Пользователь успешно активирован.', + 'Unable to enable this user.' => 'Не удалось включить этого пользователя.', + 'User disabled successfully.' => 'Пользователь был успешно выключен.', + 'Unable to disable this user.' => 'Не удалось выключить пользователя.', + 'All files have been uploaded successfully.' => 'Все файлы были успешно загружены.', + 'The maximum allowed file size is %sB.' => 'Максимально допустимый размер файла: %sB.', + 'Drag and drop your files here' => 'Переместите ваши файлы сюда', + 'choose files' => 'выбор файлов', + 'View profile' => 'Просмотр профиля', + 'Two Factor' => 'Двухфакторный', + 'Disable user' => 'Выключить пользователя', + 'Do you really want to disable this user: "%s"?' => 'Вы точно хотите выключить этого пользователя: "%s"?', + 'Enable user' => 'Включить пользователя', + 'Do you really want to enable this user: "%s"?' => 'Вы точно хотите включить этого пользователя: "%s"?', + 'Download' => 'Загрузка', + 'Uploaded: %s' => 'Загружено: %s', + 'Size: %s' => 'Размер: %s', + 'Uploaded by %s' => 'Загружено пользователем: %s', + 'Filename' => 'Имя файла', + 'Size' => 'Размер', + 'Column created successfully.' => 'Колонка успешно создана.', + 'Another column with the same name exists in the project' => 'Столбец с таким именем уже существует в этом проекте', + 'Default filters' => 'Стандартные фильтры', + 'Your board doesn\'t have any columns!' => 'Ваша доска не имеет ни одного столбца!', + 'Change column position' => 'Смена позиции столбца', + 'Switch to the project overview' => 'Переключение на обзор проекта', + 'User filters' => 'Фильтры по пользователям', + 'Category filters' => 'Фильтры по категориям', + 'Upload a file' => 'Загрузить файл', + 'View file' => 'Просмотр файла', + 'Last activity' => 'Последняя активность', + 'Change subtask position' => 'Смена позиции подзадачи', + 'This value must be greater than %d' => 'Это значение должно быть больше, чем %d', + 'Another swimlane with the same name exists in the project' => 'Другая дорожка с таким же именем уже существует в этом проекте', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Пример: https://example.kanboard.org/ (используется для генерации абсолютных URLs)', + 'Actions duplicated successfully.' => 'Дублирование действий прошло успешно', + 'Unable to duplicate actions.' => 'Дублирование действий закончилось неудачно', + 'Add a new action' => 'Добавить новое действие', + 'Import from another project' => 'Импорт из другого проекта', + 'There is no action at the moment.' => 'Действия на данный момент отсутствуют', + 'Import actions from another project' => 'Импорт действий из другого проекта', + 'There is no available project.' => 'Нет доступного проекта', + 'Local File' => 'Локальный файл', + 'Configuration' => 'Конфигурация', + 'PHP version:' => 'Версия PHP:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'Версия ОС:', + 'Database version:' => 'Версия БД:', + 'Browser:' => 'Браузер:', + 'Task view' => 'Просмотр задачи', + 'Edit task' => 'Изменение задачи', + 'Edit description' => 'Изменение описания', + 'New internal link' => 'Новая внутренняя ссылка', + 'Display list of keyboard shortcuts' => 'Показать список клавиатурных сокращений', + 'Avatar' => 'Аватар', + 'Upload my avatar image' => 'Загрузить моё изображение для аватара', + 'Remove my image' => 'Удалить моё изображение', + 'The OAuth2 state parameter is invalid' => 'Параметр состояние OAuth2 неправильный', + 'User not found.' => 'Пользователь не найден', + 'Search in activity stream' => 'Поиск в потоке активности', + 'My activities' => 'Мои активности', + 'Activity until yesterday' => 'Активности до вчерашнего дня', + 'Activity until today' => 'Активности до сегодня', + 'Search by creator: ' => 'Поиск по создателю: ', + 'Search by creation date: ' => 'Поиск по дате создания: ', + 'Search by task status: ' => 'Поиск по статусу задачи: ', + 'Search by task title: ' => 'Поиск по заголовку задачи: ', + 'Activity stream search' => 'Поиск в потоке активности', + 'Projects where "%s" is manager' => 'Проекты, где менеджером является "%s"', + 'Projects where "%s" is member' => 'Проекты, где членом является "%s"', + 'Open tasks assigned to "%s"' => 'Открытые задачи, назначенные на "%s"', + 'Closed tasks assigned to "%s"' => 'Закрытые задачи, назначенные на "%s"', + 'Assign automatically a color based on a priority' => 'Автоматически назначить цвет в зависимости от категории', + 'Overdue tasks for the project(s) "%s"' => 'Просроченные задачи для проекта(ов) "%s"', + 'Upload files' => 'Загрузить файлы', + 'Installed Plugins' => 'Установленные плагины', + 'Plugin Directory' => 'Доступные плагины', + 'Plugin installed successfully.' => 'Плагин успешно установлен.', + 'Plugin updated successfully.' => 'Плагин успешно обновлен.', + 'Plugin removed successfully.' => 'Плагин успешно удален.', + 'Subtask converted to task successfully.' => 'Подзадача успешно преобразована в задачу.', + 'Unable to convert the subtask.' => 'Невозможно преобразовать подзадачу.', + 'Unable to extract plugin archive.' => 'Невозможно распаковать архив с плагином.', + 'Plugin not found.' => 'Плагин не найден.', + 'You don\'t have the permission to remove this plugin.' => 'У Вас нет прав на удаление этого плагина.', + 'Unable to download plugin archive.' => 'Невозможно загрузить архив с плагином.', + 'Unable to write temporary file for plugin.' => 'Невозможно записать временный файл для плагина.', + 'Unable to open plugin archive.' => 'Невозможно открыть архив плагина.', + 'There is no file in the plugin archive.' => 'В архиве плагина нет файлов.', + 'Create tasks in bulk' => 'Массовое создание задач', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Ваш Kanboard не сконфигурирован для установки плагинов через пользовательский интерфейс.', + 'There is no plugin available.' => 'Нет доступных плагинов.', + 'Install' => 'Установить', + 'Update' => 'Обновить', + 'Up to date' => 'Самый новый', + 'Not available' => 'Недоступен', + 'Remove plugin' => 'Удалить плагин', + 'Do you really want to remove this plugin: "%s"?' => 'Вы действительно хотите удалить плагин: "%s"?', + 'Uninstall' => 'Деинсталлировать', + 'Listing' => 'Список', + 'Metadata' => 'Метаданные', + 'Manage projects' => 'Управление проектами', + 'Convert to task' => 'Преобразовать в задачу', + 'Convert sub-task to task' => 'Преобразовать подзадачу в задачу', + 'Do you really want to convert this sub-task to a task?' => 'Вы действительно хотите преобразовать эту подзадачу в задачу?', + 'My task title' => 'Заголовок задачи', + 'Enter one task by line.' => 'Указывайте одну задачу на строке', + 'Number of failed login:' => 'Число неудачных попыток входа:', + 'Account locked until:' => 'Аккаунт заблокирован до:', + 'Email settings' => 'Настройки почты', + 'Email sender address' => 'Адрес отправителя', + 'Email transport' => 'Почтовый транспорт', + 'Webhook token' => 'Webhook токены', + 'Project tags management' => 'Управление метками проекта', + 'Tag created successfully.' => 'Метка успешно создана.', + 'Unable to create this tag.' => 'Невозможно создать эту метку.', + 'Tag updated successfully.' => 'Метка успешно обновлена.', + 'Unable to update this tag.' => 'Невозможно обновить эту метку.', + 'Tag removed successfully.' => 'Метка успешно удалена.', + 'Unable to remove this tag.' => 'Невозможно удалить эту метку.', + 'Global tags management' => 'Управление глобальными метками', + 'Tags' => 'Метки', + 'Tags management' => 'Управление метками', + 'Add new tag' => 'Добавить новую метку', + 'Edit a tag' => 'Редактировать метку', + 'Project tags' => 'Метки проекта', + 'There is no specific tag for this project at the moment.' => 'Нет меток для этого проекта.', + 'Tag' => 'Метка', + 'Remove a tag' => 'Удалить метку', + 'Do you really want to remove this tag: "%s"?' => 'Вы действительно хотите удалить метку: "%s"?', + 'Global tags' => 'Глобальные метки', + 'There is no global tag at the moment.' => 'Нет глобальных меток.', + 'This field cannot be empty' => 'Это поле не может быть пустым', + 'Close a task when there is no activity in a specific column' => 'Закрыть задачу при отсутствии активности в определенной колонке', + '%s removed a subtask for the task #%d' => '%s удалил подзадачу для #%d', + '%s removed a comment on the task #%d' => '%s удалил комментарий к задаче #%d', + 'Comment removed on task #%d' => 'Комментарий удален в задаче #%d', + 'Subtask removed on task #%d' => 'Подзадача удалена в задаче #%d', + 'Hide tasks in this column in the dashboard' => 'Не показывать задачи из этой колонки в панели управления', + '%s removed a comment on the task %s' => '%s удалил комментарии к задаче %s', + '%s removed a subtask for the task %s' => '%s удалил подзадачу для %s', + 'Comment removed' => 'Комментарий удален', + 'Subtask removed' => 'Подзадача удалена', + '%s set a new internal link for the task #%d' => '%s добавил внутреннюю ссылку для задачи #%d', + '%s removed an internal link for the task #%d' => '%s удалил внутреннюю ссылку для задачи #%d', + 'A new internal link for the task #%d has been defined' => 'Внешняя ссылка для задачи #%d была установлена', + 'Internal link removed for the task #%d' => 'Внутренняя ссылка удалена для задачи #%d', + '%s set a new internal link for the task %s' => '%s добавил внутреннюю ссылку для задачи %s', + '%s removed an internal link for the task %s' => '%s удалил внутреннюю ссылку для задачи %s', + 'Automatically set the due date on task creation' => 'Автоматически устанавливать дату завершения задачи при её создании', + 'Move the task to another column when closed' => 'Переместить задачу в другую колонку при закрытии', + 'Move the task to another column when not moved during a given period' => 'Переместить задачу в другую колонку если она не был перемещен в указанный период', + 'Dashboard for %s' => 'Панель управления для %s', + 'Tasks overview for %s' => 'Обзор задач для %s', + 'Subtasks overview for %s' => 'Обзор подзадач для %s', + 'Projects overview for %s' => 'Обзор проектов для %s', + 'Activity stream for %s' => 'Лента активности для %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Назначить цвет, когда задача будет перемещена в указанную дорожку', + 'Assign a priority when the task is moved to a specific swimlane' => 'Назначить приоритет, когда задача будет перемещена в указанную дорожку', + 'User unlocked successfully.' => 'Пользователь успешно разблокирован.', + 'Unable to unlock the user.' => 'Не удаётся разблокировать пользователя.', + 'Move a task to another swimlane' => 'Переместить задачу в другую дорожку', + 'Creator Name' => 'Создатель', + 'Time spent and estimated' => 'Предполагаемое и затраченное время', + 'Move position' => 'Переместить позицию', + 'Move task to another position on the board' => 'Переместить задачу на другую позицию на доске', + 'Insert before this task' => 'Вставить перед этой задачей', + 'Insert after this task' => 'Вставить после этой задачи', + 'Unlock this user' => 'Разблокировать пользователя', + 'Custom Project Roles' => 'Пользовательские проектные роли', + 'Add a new custom role' => 'Добавить новую роль', + 'Restrictions for the role "%s"' => 'Ограничения для роли "%s"', + 'Add a new project restriction' => 'Добавить ограничение на проект', + 'Add a new drag and drop restriction' => 'Добавить ограничение на перемещение', + 'Add a new column restriction' => 'Добавить ограничение на колонку', + 'Edit this role' => 'Изменить роль', + 'Remove this role' => 'Удалить роль', + 'There is no restriction for this role.' => 'Для этой роли ограничения не заданы.', + 'Only moving task between those columns is permitted' => 'Разрешено перемещение только между этими колонками', + 'Close a task in a specific column when not moved during a given period' => 'Закрывать задачу в указанной колонке, если она не была перемещена в течение указанного периода', + 'Edit columns' => 'Изменить колонки', + 'The column restriction has been created successfully.' => 'Ограничение на колонку успешно создано.', + 'Unable to create this column restriction.' => 'Невозможно создать ограничение на колонку.', + 'Column restriction removed successfully.' => 'Ограничение на колонку успешно удалено.', + 'Unable to remove this restriction.' => 'Не удаётся удалить ограничение.', + 'Your custom project role has been created successfully.' => 'Проектная роль была успешно создана.', + 'Unable to create custom project role.' => 'Невозможно создать проектную роль.', + 'Your custom project role has been updated successfully.' => 'Проектная роль была успешно обновлена.', + 'Unable to update custom project role.' => 'Не удаётся обновить проектную роль.', + 'Custom project role removed successfully.' => 'Проектная роль была успешно удалена.', + 'Unable to remove this project role.' => 'Не удаётся удалить проектную роль.', + 'The project restriction has been created successfully.' => 'Ограничение на проект было успешно создано.', + 'Unable to create this project restriction.' => 'Не удаётся создать ограничение на проект.', + 'Project restriction removed successfully.' => 'Ограничение на проект успешно удалено.', + 'You cannot create tasks in this column.' => 'Вы не можете создавать задачи в этой колонке.', + 'Task creation is permitted for this column' => 'Разрешено создание задач в этой колонке', + 'Closing or opening a task is permitted for this column' => 'Разрешено открытие и закрытие задач в этой колонке', + 'Task creation is blocked for this column' => 'Заблокировано создание задач в этой колонке', + 'Closing or opening a task is blocked for this column' => 'Заблокировано открытие и закрытие задач в этой колонке', + 'Task creation is not permitted' => 'Создание задач не разрешено', + 'Closing or opening a task is not permitted' => 'Открытие и закрытие задач не разрешено', + 'New drag and drop restriction for the role "%s"' => 'Новое ограничение на перемещение для роли "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Пользователи с этой ролью смогут перемещать задачи только между указанными колонками.', + 'Remove a column restriction' => 'Удаление ограничения на колонку', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Вы действительно хотите удалить это ограничение на перемещение: "%s" в "%s"?', + 'New column restriction for the role "%s"' => 'Новое ограничение на колонку для роли "%s"', + 'Rule' => 'Правило', + 'Do you really want to remove this column restriction?' => 'Вы действительно хотите удалить это ограничение на колонку?', + 'Custom roles' => 'Проектные роли', + 'New custom project role' => 'Создание пользовательской проектной роли', + 'Edit custom project role' => 'Изменение проектной роли', + 'Remove a custom role' => 'Удаление проектной роли', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Вы действительно хотите удалить проектную роль "%s"? Все пользователи с этой ролью станут обычными участниками проекта.', + 'There is no custom role for this project.' => 'Для этого проекта пользовательские роли не заданы', + 'New project restriction for the role "%s"' => 'Новое проектное ограничение для роли "%s"', + 'Restriction' => 'Ограничение', + 'Remove a project restriction' => 'Удаление ограничения на проект', + 'Do you really want to remove this project restriction: "%s"?' => 'Вы действительно хотите удалить ограничение на проект: "%s"?', + 'Duplicate to multiple projects' => 'Дублировать в несколько проектов', + 'This field is required' => 'Заполните это поле', + 'Moving a task is not permitted' => 'Перемещение задачи не разрешено', + 'This value must be in the range %d to %d' => 'Значение должно находиться в диапазоне от %d до %d', + 'You are not allowed to move this task.' => 'Вам не разрешено перемещать эту задачу.', + 'API User Access' => 'Доступ к API', + 'Preview' => 'Предпросмотр', + 'Write' => 'Редактирование', + 'Write your text in Markdown' => 'Добавьте Ваше описание в формате Markdown', + 'No personal API access token registered.' => 'Персональные токены доступа к API не созданы.', + 'Your personal API access token is "%s"' => 'Ваш персональный токен доступа к API: "%s"', + 'Remove your token' => 'Удалить токен', + 'Generate a new token' => 'Сгенерировать новый токен', + 'Showing %d-%d of %d' => 'Показывается %d-%d из %d', + 'Outgoing Emails' => 'Исходящие e-mail', + 'Add or change currency rate' => 'Добавить или изменить курс валют', + 'Reference currency: %s' => 'Базовая валюта: %s', + 'Add custom filters' => 'Добавить пользовательские фильтры', + 'Export' => 'Экспорт', + 'Add link label' => 'Добавить связь в задаче', + 'Incompatible Plugins' => 'Несовместимые плагины', + 'Compatibility' => 'Совместимость', + 'Permissions and ownership' => 'Разрешения и владение проектом', + 'Priorities' => 'Приоритеты', + 'Close this window' => 'Закрыть окно', + 'Unable to upload this file.' => 'Не удаётся загрузить файл.', + 'Import tasks' => 'Импорт задач', + 'Choose a project' => 'Выберите проект', + 'Profile' => 'Профиль', + 'Application role' => 'Роль в приложении', + '%d invitations were sent.' => '%d приглашений было отправлено.', + '%d invitation was sent.' => '%d приглашение было отправлено.', + 'Unable to create this user.' => 'Не удалось создать этого пользователя.', + 'Kanboard Invitation' => 'Приглашение в Kanboard', + 'Visible on dashboard' => 'Отображается в панели управления', + 'Created at:' => 'Создана:', + 'Updated at:' => 'Обновлена:', + 'There is no custom filter.' => 'Пользовательские фильтры отсутствуют.', + 'New User' => 'Добавление пользователя', + 'Authentication' => 'Данные входа', + 'If checked, this user will use a third-party system for authentication.' => 'Если включено, то пользователь будет использовать стороннюю систему для авторизации.', + 'The password is necessary only for local users.' => 'Пароль необходим только для локальных пользователей.', + 'You have been invited to register on Kanboard.' => 'Вы были приглашены зарегистрироваться в Kanboard.', + 'Click here to join your team' => 'Откройте эту ссылку для вступления в вашу команду', + 'Invite people' => 'Приглашение пользователей', + 'Emails' => 'Адреса e-mail', + 'Enter one email address by line.' => 'Вводите по одному e-mail на строку.', + 'Add these people to this project' => 'Добавить приглашенных в проект', + 'Add this person to this project' => 'Добавить эту персону в проект', + 'Sign-up' => 'Регистрация', + 'Credentials' => 'Данные для входа', + 'New user' => 'Добавить пользователя', + 'This username is already taken' => 'Это имя пользователя занято', + 'Your profile must have a valid email address.' => 'В вашем профиле должен быть указан корректный адрес e-mail.', + 'TRL - Turkish Lira' => 'TRL - Турецкая лира', + 'The project email is optional and could be used by several plugins.' => 'E-mail проекта является необязательным атрибутом и может быть использован некоторыми плагинами', + 'The project email must be unique across all projects' => 'E-mail проекта должен быть уникальным для каждого проекта', + 'The email configuration has been disabled by the administrator.' => 'Настройка e-mail была отключена администратором.', + 'Close this project' => 'Закрыть этот проект', + 'Open this project' => 'Открыть этот проект', + 'Close a project' => 'Закрыть проект', + 'Do you really want to close this project: "%s"?' => 'Вы действительно хотите закрыть проект: "%s"?', + 'Reopen a project' => 'Открыть проект', + 'Do you really want to reopen this project: "%s"?' => 'Вы действительно хотите открыть проект: "%s"?', + 'This project is open' => 'Этот проект открыт', + 'This project is closed' => 'Этот проект закрыт', + 'Unable to upload files, check the permissions of your data folder.' => 'Невозможно загрузить файлы, проверьте права доступа на папку "data".', + 'Another category with the same name exists in this project' => 'Другая категория с таким же именем уже существует в этом проекте', + 'Comment sent by email successfully.' => 'Комментарий успешно отправлен по e-mail.', + 'Sent by email to "%s" (%s)' => 'Отправлен по e-mail для "%s" (%s)', + 'Unable to read uploaded file.' => 'Не удаётся прочитать загруженный файл.', + 'Database uploaded successfully.' => 'База данных успешно импортирована.', + 'Task sent by email successfully.' => 'Задача успешно отправлена по e-mail.', + 'There is no category in this project.' => 'Для этого проекта не задана категория.', + 'Send by email' => 'Отправить по e-mail', + 'Create and send a comment by email' => 'Создать и отправить комментарий на e-mail', + 'Subject' => 'Тема', + 'Upload the database' => 'Импорт базы данных', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Вы можете импортировать предварительно созданный файл выгрузки базы данных SQLite (формат Gzip).', + 'Database file' => 'Файл выгрузки БД', + 'Upload' => 'Импорт', + 'Your project must have at least one active swimlane.' => 'Ваш проект должен иметь хотя бы одну активную дорожку.', + 'Project: %s' => 'Проект: %s', + 'Automatic action not found: "%s"' => 'Автоматическое действие не найдено: "%s"', + '%d projects' => '%d проектов', + '%d project' => '%d проект', + 'There is no project.' => 'Нет проектов.', + 'Sort' => 'Сортировка', + 'Project ID' => 'ID проекта', + 'Project name' => 'Имя проекта', + 'Public' => 'Публичный', + 'Personal' => 'Персональный', + '%d tasks' => '%d задач', + '%d task' => '%d задачу', + 'Task ID' => 'ID задачи', + 'Assign automatically a color when due date is expired' => 'Автоматически назначать цвет после истечения срока задачи', + 'Total score in this column across all swimlanes' => 'Общая оценка в этой колонке среди всех дорожек', + 'HRK - Kuna' => 'HRK - Хорватская куна', + 'ARS - Argentine Peso' => 'ARS - Аргентинский песо', + 'COP - Colombian Peso' => 'COP - Колумбийский песо', + '%d groups' => '%d групп', + '%d group' => '%d группа', + 'Group ID' => 'ID группы', + 'External ID' => 'Внешний ID', + '%d users' => '%d пользователей', + '%d user' => '%d пользователь', + 'Hide subtasks' => 'Скрыть подзадачи', + 'Show subtasks' => 'Показать подзадачи', + 'Authentication Parameters' => 'Параметры аутентификации', + 'API Access' => 'Доступ к API', + 'No users found.' => 'Пользователи не найдены.', + 'User ID' => 'ID пользователя', + 'Notifications are activated' => 'Уведомления активированы', + 'Notifications are disabled' => 'Уведомления выключены', + 'User disabled' => 'Пользователь выключен', + '%d notifications' => '%d уведомлений', + '%d notification' => '%d уведомление', + 'There is no external integration installed.' => 'Внешние интеграции не установлены.', + 'You are not allowed to update tasks assigned to someone else.' => 'Вы не можете обновлять задачи, назначенные другому пользователю.', + 'You are not allowed to change the assignee.' => 'Вы не можете изменить назначенного на эту задачу.', + 'Task suppression is not permitted' => 'Удаление задач не разрешено', + 'Changing assignee is not permitted' => 'Изменение назначенного не разрешено', + 'Update only assigned tasks is permitted' => 'Разрешено обновление только назначенных задач', + 'Only for tasks assigned to the current user' => 'Только для задач, назначенных текущему пользователю', + 'My projects' => 'Мои проекты', + 'You are not a member of any project.' => 'Вы не являетесь участником какого-либо проекта.', + 'My subtasks' => 'Мои подзадачи', + '%d subtasks' => '%d подзадач', + '%d subtask' => '%d подзадача', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Текущий пользователь может перемещать назначенные ему задачи только между этими колонками', + '[DUPLICATE]' => '[КОПИЯ]', + 'DKK - Danish Krona' => 'DKK - Датская крона', + 'Remove user from group' => 'Удалить пользователя из группы', + 'Assign the task to its creator' => 'Назначать задачу её создателю', + 'This task was sent by email to "%s" with subject "%s".' => 'Эта задача была отправлена по e-mail на "%s" с темой "%s".', + 'Predefined Email Subjects' => 'Предустановленные темы для e-mail', + 'Write one subject by line.' => 'Записываются по одной теме на строку.', + 'Create another link' => 'Создать другую ссылку', + 'BRL - Brazilian Real' => 'BRL - бразильский реал', + 'Add a new Kanboard task' => 'Добавить новую задачу на Kanboard', + 'Subtask not started' => 'Подзадача не начата', + 'Subtask currently in progress' => 'Подзадача в процессе выполнения', + 'Subtask completed' => 'Подзадача завершена', + 'Subtask added successfully.' => 'Подзадача успешно добавлена.', + '%d subtasks added successfully.' => '%d подзадач(а) успешно добавлено.', + 'Enter one subtask by line.' => 'Записывайте по одной подзадаче на строку.', + 'Predefined Contents' => 'Шаблоны содержимого', + 'Predefined contents' => 'Шаблоны содержимого', + 'Predefined Task Description' => 'Шаблон описания задачи', + 'Do you really want to remove this template? "%s"' => 'Вы действительно хотите удалить этот шаблон? "%s"', + 'Add predefined task description' => 'Добавить шаблон описания задачи', + 'Predefined Task Descriptions' => 'Шаблоны описания задач', + 'Template created successfully.' => 'Шаблон создан успешно.', + 'Unable to create this template.' => 'Произошла ошибка при создании шаблона.', + 'Template updated successfully.' => 'Шаблон успешно обновлен.', + 'Unable to update this template.' => 'Произошла ошибка при обновлении шаблона.', + 'Template removed successfully.' => 'Шаблон успешно удалён.', + 'Unable to remove this template.' => 'Произошла ошибка при удалении шаблона.', + 'Template for the task description' => 'Шаблон для описания задачи', + 'The start date is greater than the end date' => 'Дата начала не должна быть позже даты завершения', + 'Tags must be separated by a comma' => 'Метки разделяются запятой', + 'Only the task title is required' => 'Название задачи обязательно для заполнения', + 'Creator Username' => 'Логин создателя', + 'Color Name' => 'Цвет', + 'Column Name' => 'Колонка', + 'Swimlane Name' => 'Дорожка', + 'Time Estimated' => 'Расчётное время', + 'Time Spent' => 'Времени потрачено', + 'External Link' => 'Внешняя ссылка', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Эта опция включит ленту iCal и RSS-поток, а также публичный доступ к доске.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Остановить таймеры всех подзадач при перемещении задачи в другую колонку', + 'Subtask Title' => 'Название подзадачи', + 'Add a subtask and activate the timer when moving a task to another column' => 'Добавлять подзадачу и активировать таймер при перемещении задачи в другую колонку', + 'days' => 'дней', + 'minutes' => 'минут', + 'seconds' => 'секунд', + 'Assign automatically a color when preset start date is reached' => 'Автоматически назначить цвет, когда дата начала достигнута', + 'Move the task to another column once a predefined start date is reached' => 'Переместить задачу в другую колонку, когда дата начала достигнута', + 'This task is now linked to the task %s with the relation "%s"' => 'Эта задача теперь связана с %s с отношением "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'Удалена связь с отношением "%s" к задаче %s', + 'Custom Filter:' => 'Настраиваемый фильтр:', + 'Unable to find this group.' => 'Невозможно найти эту группу.', + '%s moved the task #%d to the column "%s"' => '%s перенёс задачу #%d в колонку "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => '%s перенёс задачу #%d на позицию %d в колонку "%s"', + '%s moved the task #%d to the swimlane "%s"' => '%s перенёс задачу #%d на дорожку "%s"', + '%sh spent' => '%sч затрачено', + '%sh estimated' => '%sч запланировано', + 'Select All' => 'Выделить все', + 'Unselect All' => 'Снять выделение со всех', + 'Apply action' => 'Применить действие', + 'Move selected tasks to another column or swimlane' => 'Переместить выбранные задачи в другую колонку', + 'Edit tasks in bulk' => 'Пакетное редактирование задач', + 'Choose the properties that you would like to change for the selected tasks.' => 'Выберите свойства, которые вы хотите изменить для выбранных задач', + 'Configure this project' => 'Настроить этот проект', + 'Start now' => 'Начать сейчас', + '%s removed a file from the task #%d' => '%s удалил файл из задачи #%d', + 'Attachment removed from task #%d: %s' => 'Вложение удалено из задачи #%d: %s', + 'No color' => 'Без цвета', + 'Attachment removed "%s"' => 'Вложение удалено "%s"', + '%s removed a file from the task %s' => '%s удалил файл из задачи %s', + 'Move the task to another swimlane when assigned to a user' => 'Переместить задачу в другую дорожку, когда она присваивается другому пользователю', + 'Destination swimlane' => 'Целевая дорожка', + 'Assign a category when the task is moved to a specific swimlane' => 'Присвоить категорию, если задача перемещена в определенную дорожку', + 'Move the task to another swimlane when the category is changed' => 'Переместить задачу в другую дорожку, если категория изменена', + 'Reorder this column by priority (ASC)' => 'Упорядочить колонку по приоритету (ASC)', + 'Reorder this column by priority (DESC)' => 'Упорядочить колонку по приоритету (DESC)', + 'Reorder this column by assignee and priority (ASC)' => 'Упорядочить колонку по назначенному пользователю и приоритету (ASC)', + 'Reorder this column by assignee and priority (DESC)' => 'Упорядочить колонку по назначенному пользователю и приоритету (DESC)', + 'Reorder this column by assignee (A-Z)' => 'Упорядочить колонку по назначенному пользователю (А-Я)', + 'Reorder this column by assignee (Z-A)' => 'Упорядочить колонку по назначенному пользователю (Я-А)', + 'Reorder this column by due date (ASC)' => 'Упорядочить колонку по дате окончания (ASC)', + 'Reorder this column by due date (DESC)' => 'Упорядочить колонку по дате окончания (DESC)', + 'Reorder this column by id (ASC)' => 'Упорядочить колонку по ID (ASC)', + 'Reorder this column by id (DESC)' => 'Упорядочить колонку по ID (DESC)', + '%s moved the task #%d "%s" to the project "%s"' => '%s переместил задачу #%d "%s" в проект "%s"', + 'Task #%d "%s" has been moved to the project "%s"' => 'Задача #%d "%s" перемещена в проект "%s"', + 'Move the task to another column when the due date is less than a certain number of days' => 'Переместить задачу в другую колонку, если дата окончания меньше заданного количества дней', + 'Automatically update the start date when the task is moved away from a specific column' => 'Автоматически обновить дату начала, если задача перемещена из указанной колонки', + 'HTTP Client:' => 'HTTP Клиент:', + 'Assigned' => 'Назначенные', + 'Task limits apply to each swimlane individually' => 'Лимиты задач применяются к каждой дорожке отдельно', + 'Column task limits apply to each swimlane individually' => 'Лимиты задач в колонках применяются к каждой дорожке отдельно', + 'Column task limits are applied to each swimlane individually' => 'Лимиты задач в колонках применены к каждой дорожке отдельно', + 'Column task limits are applied across swimlanes' => 'Лимиты задач применены по всем дорожкам', + 'Task limit: ' => 'Лимит задач:', + 'Change to global tag' => 'Сменить на глобальную метку', + 'Do you really want to make the tag "%s" global?' => 'Вы действительно хотите сделать метку "%s" глобальной?', + 'Enable global tags for this project' => 'Разрешить глобальные метки в этом проекте', + 'Group membership(s):' => 'Состоит в группах:', + '%s is a member of the following group(s): %s' => '%s является членом следующих групп: %s', + '%d/%d group(s) shown' => '%d/%d групп показано', + 'Subtask creation or modification' => 'Создание или изменение подзадач', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Назначить задачу определённому пользователю, если задача перемещена в указанную дорожку', + 'Comment' => 'Комментарий', + 'Collapse vertically' => 'Свернуть вертикально', + 'Expand vertically' => 'Развернуть по вертикали', + 'MXN - Mexican Peso' => 'MXN - Мексиканское песо', + 'Estimated vs actual time per column' => 'Оценочное и фактическое время по колонке', + 'HUF - Hungarian Forint' => 'HUF - Венгерский форинт', + 'XBT - Bitcoin' => 'XBT - Биткоин', + 'You must select a file to upload as your avatar!' => 'Вы должны выбрать файл для загрузки в качестве аватара!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'Загруженный вами файл не является допустимым изображением! (Разрешены только *.gif, *.jpg, *.jpeg и *.png!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Автоматически устанавливать срок, когда задача перемещается из определенной колонки', + 'No other projects found.' => 'Другие проекты не найдены.', + 'Tasks copied successfully.' => 'Задачи успешно скопированы.', + 'Unable to copy tasks.' => 'Не удалось скопировать задачи.', + 'Theme' => 'Тема', + 'Theme:' => 'Тема:', + 'Light theme' => 'Светлая тема', + 'Dark theme' => 'Темная тема', + 'Automatic theme - Sync with system' => 'Автоматическая тема — синхронизировать с системой', + 'Application managers or more' => 'Менеджеры приложения или выше', + 'Administrators' => 'Администраторы', + 'Visibility:' => 'Видимость:', + 'Standard users' => 'Обычные пользователи', + 'Visibility is required' => 'Требуется видимость', + 'The visibility should be an app role' => 'Видимость должна быть ролью приложения', + 'Reply' => 'Ответить', + '%s wrote: ' => '%s написал: ', + 'Number of visible tasks in this column and swimlane' => 'Количество видимых задач в этой колонке и дорожке', + 'Number of tasks in this swimlane' => 'Количество задач в этой дорожке', + 'Unable to find another subtask in progress, you can close this window.' => 'Не удалось найти другую подзадачу в процессе, вы можете закрыть это окно.', + 'This theme is invalid' => 'Эта тема недействительна', + 'This role is invalid' => 'Эта роль недействительна', + 'This timezone is invalid' => 'Этот часовой пояс недействителен', + 'This language is invalid' => 'Этот язык недействителен', + 'This URL is invalid' => 'Этот URL недействителен', + 'Date format invalid' => 'Неверный формат даты', + 'Time format invalid' => 'Неверный формат времени', + 'Invalid Mail transport' => 'Недопустимый почтовый транспорт', + 'Color invalid' => 'Недопустимый цвет', + 'This value must be greater or equal to %d' => 'Это значение должно быть больше или равно %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Добавьте BOM в начало файла (требуется для Microsoft Excel)', + 'Just add these tag(s)' => 'Добавьте только эти теги', + 'Remove internal link(s)' => 'Удалить внутренние ссылки', + 'Import tasks from another project' => 'Импортировать задачи из другого проекта', + 'Select the project to copy tasks from' => 'Выберите проект, из которого нужно скопировать задачи', + 'The total maximum allowed attachments size is %sB.' => 'Общий максимально допустимый размер вложений — %sB.', + 'Add attachments' => 'Добавить вложения', + 'Task #%d "%s" is overdue' => 'Задача #%d "%s" просрочена', + 'Enable notifications by default for all new users' => 'Включить уведомления по умолчанию для всех новых пользователей', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Назначать задачу её создателю для определённых колонок, если исполнитель не задан вручную', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Назначать задачу текущему пользователю при перемещении в указанную колонку, если никто не назначен', +]; diff --git a/app/Locale/sk_SK/translations.php b/app/Locale/sk_SK/translations.php new file mode 100644 index 0000000..fd183e6 --- /dev/null +++ b/app/Locale/sk_SK/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => ' ', + 'None' => 'Žiadne', + 'Edit' => 'Upraviť', + 'Remove' => 'Odstrániť', + 'Yes' => 'Áno', + 'No' => 'Nie', + 'cancel' => 'zrušiť', + 'or' => 'alebo', + 'Yellow' => 'Žltá', + 'Blue' => 'Modrá', + 'Green' => 'Zelená', + 'Purple' => 'Fialová', + 'Red' => 'Červená', + 'Orange' => 'Oranžová', + 'Grey' => 'Sivá', + 'Brown' => 'Hnedá', + 'Deep Orange' => 'Sýto oranžová', + 'Dark Grey' => 'Tmavo sivá', + 'Pink' => 'Ružová', + 'Teal' => 'Modro zelená', + 'Cyan' => 'Azúrová', + 'Lime' => 'Limetková', + 'Light Green' => 'Svetlo zelená', + 'Amber' => 'Jantárová', + 'Save' => 'Uložiť', + 'Login' => 'Prihlásiť', + 'Official website:' => 'Oficiálna stránka:', + 'Unassigned' => 'Nepridelené', + 'View this task' => 'Zobraziť túto úlohu', + 'Remove user' => 'Odstrániť používateľa', + 'Do you really want to remove this user: "%s"?' => 'Naozaj chcete odstrániť používateľa: „%s”?', + 'All users' => 'Všetci používatelia', + 'Username' => 'Prihlasovacie meno', + 'Password' => 'Heslo', + 'Administrator' => 'Administrátor', + 'Sign in' => 'Prihlásiť sa', + 'Users' => 'Používatelia', + 'Forbidden' => 'Zakázané', + 'Access Forbidden' => 'Prístup zakázaný', + 'Edit user' => 'Upraviť používateľa', + 'Logout' => 'Odhlásiť', + 'Bad username or password' => 'Zlé používateľské meno alebo heslo', + 'Edit project' => 'Upraviť projekt', + 'Name' => 'Meno', + 'Projects' => 'Projekty', + 'No project' => 'Žiadne projekty', + 'Project' => 'Projekt', + 'Status' => 'Stav', + 'Tasks' => 'Úlohy', + 'Board' => 'Nástenka', + 'Actions' => 'Akcie', + 'Inactive' => 'Neaktívne', + 'Active' => 'Aktívne', + 'Unable to update this board.' => 'Nemožno aktualizovať nástenku.', + 'Disable' => 'Zakázať', + 'Enable' => 'Zapnúť', + 'New project' => 'Nový projekt', + 'Do you really want to remove this project: "%s"?' => 'Naozaj chcete odstrániť projekt: „%s”?', + 'Remove project' => 'Odstrániť projekt', + 'Edit the board for "%s"' => 'Upraviť nástenku „%s”', + 'Add a new column' => 'Pridať nový stĺpec', + 'Title' => 'Názov', + 'Assigned to %s' => 'Pridelené používateľovi %s', + 'Remove a column' => 'Odstrániť stĺpec', + 'Unable to remove this column.' => 'Nemožno odstrániť tento stĺpec.', + 'Do you really want to remove this column: "%s"?' => 'Naozaj chcete odstrániť stĺpec „%s”?', + 'Settings' => 'Nastavenia', + 'Application settings' => 'Nastavenia aplikácie', + 'Language' => 'Jazyk', + 'Webhook token:' => 'Token webového háku:', + 'API token:' => 'Token API:', + 'Database size:' => 'Veľkosť databázy:', + 'Download the database' => 'Stiahnuť databázu', + 'Optimize the database' => 'Optimalizovať databázu', + '(VACUUM command)' => '(príkaz VACUUM)', + '(Gzip compressed Sqlite file)' => 'Súbor SQLite, komprimovaný gzip)', + 'Close a task' => 'Ukončiť úlohu', + 'Column' => 'Stĺpec', + 'Color' => 'Farba', + 'Assignee' => 'Pridelené', + 'Create another task' => 'Vytvoriť ďalšiu úlohu', + 'New task' => 'Nová úloha', + 'Open a task' => 'Otvoriť úlohu', + 'Do you really want to open this task: "%s"?' => 'Naozaj chcete otvoriť úlohu: „%s”?', + 'Back to the board' => 'Späť na nástenku', + 'There is nobody assigned' => 'Nie je pridelené nikomu', + 'Column on the board:' => 'Stĺpec nástenky:', + 'Close this task' => 'Ukončiť túto úlohu', + 'Open this task' => 'Otvoriť túto úlohu', + 'There is no description.' => 'Nemá popis.', + 'Add a new task' => 'Pridať novú úlohu', + 'The username is required' => 'Prihl. meno je povinné', + 'The maximum length is %d characters' => 'Maximálna dĺžka je %d znakov', + 'The minimum length is %d characters' => 'Minimálna dĺžka je %d znakov', + 'The password is required' => 'Heslo je povinné', + 'This value must be an integer' => 'Táto hodnota musí byť celé číslo', + 'The username must be unique' => 'Prihl. meno musí byť jedinečné', + 'The user id is required' => 'ID používateľa je povinné', + 'Passwords don\'t match' => 'Heslá sa nezhodujú', + 'The confirmation is required' => 'Potvrdenie je povinné', + 'The project is required' => 'Projekt je povinný', + 'The id is required' => 'ID je povinné', + 'The project id is required' => 'ID projektu je povinné', + 'The project name is required' => 'Meno projektu je povinné', + 'The title is required' => 'Názov je povinný', + 'Settings saved successfully.' => 'Nastavenia úspešne uložené.', + 'Unable to save your settings.' => 'Nemožno uložiť nastavenia.', + 'Database optimization done.' => 'Optimalizácia databázy dokončená.', + 'Your project has been created successfully.' => 'Projekt úspešne vytvorený.', + 'Unable to create your project.' => 'Nemožno vytvoriť projekt.', + 'Project updated successfully.' => 'Projekt úspešne aktualizovaný.', + 'Unable to update this project.' => 'Nemožno aktualizovať projekt.', + 'Unable to remove this project.' => 'Nemožno odstrániť projekt.', + 'Project removed successfully.' => 'Projekt úspešne odstránený.', + 'Project activated successfully.' => 'Projekt úspešne aktivovaný.', + 'Unable to activate this project.' => 'Nemožno aktivovať projekt.', + 'Project disabled successfully.' => 'Projekt úspešne vypnutý.', + 'Unable to disable this project.' => 'Nemožno vypnúť projekt.', + 'Unable to open this task.' => 'Nemožno otvoriť úlohu.', + 'Task opened successfully.' => 'Úloha úspešne otvorená.', + 'Unable to close this task.' => 'Nemožno ukončiť túto úlohu.', + 'Task closed successfully.' => 'Úloha úspešne ukončená.', + 'Unable to update your task.' => 'Nemožno aktualizovať úlohu.', + 'Task updated successfully.' => 'Úloha úspešne aktualizovaná.', + 'Unable to create your task.' => 'Nemožno vytvoriť úlohu.', + 'Task created successfully.' => 'Úloha úspešne vytvorená.', + 'User created successfully.' => 'Používateľ úspešne vytvorený.', + 'Unable to create your user.' => 'Nemožno vytvoriť používateľa.', + 'User updated successfully.' => 'Používateľ úspešne aktualizovaný.', + 'User removed successfully.' => 'Používateľ úspešne odstránený.', + 'Unable to remove this user.' => 'Nemožno odstrániť používateľa.', + 'Board updated successfully.' => 'Nástenka úspešne aktualizovaná.', + 'Ready' => 'Pripravené', + 'Backlog' => 'Nevybavené', + 'Work in progress' => 'V riešení', + 'Done' => 'Dokončené', + 'Application version:' => 'Verzia aplikácie:', + 'Id' => 'ID', + 'Public link' => 'Verejný odkaz', + 'Timezone' => 'Časové pásmo', + 'Sorry, I didn\'t find this information in my database!' => 'Prepáčte, túto informáciu som v databáze nenašiel!', + 'Page not found' => 'Stránka neexistuje', + 'Complexity' => 'Zložitosť', + 'Task limit' => 'Limit úloh', + 'Task count' => 'Počet úloh', + 'User' => 'Používateľ', + 'Comments' => 'Komentáre', + 'Comment is required' => 'Komentár je povinný', + 'Comment added successfully.' => 'Komentár úspešne pridaný.', + 'Unable to create your comment.' => 'Nemožno vytvoriť komentár.', + 'Due Date' => 'Dátum splnenia', + 'Invalid date' => 'Neplatný dátum', + 'Automatic actions' => 'Automatické akcie', + 'Your automatic action has been created successfully.' => 'Automatická akcia úspešne vytvorená.', + 'Unable to create your automatic action.' => 'Nemožno vytvoriť automatickú akciu.', + 'Remove an action' => 'Odstrániť akciu', + 'Unable to remove this action.' => 'Nemožno odstrániť akciu.', + 'Action removed successfully.' => 'Akcia úspešne odstránená.', + 'Automatic actions for the project "%s"' => 'Automatické akcie projektu „%s”', + 'Add an action' => 'Pridať akciu', + 'Event name' => 'Názov udalosti', + 'Action' => 'Akcia', + 'Event' => 'Udalosť', + 'When the selected event occurs execute the corresponding action.' => 'Keď nastane zvolená udalosť, vykonať príslušnú akciu.', + 'Next step' => 'Ďalší krok', + 'Define action parameters' => 'Definujte parametre akcie', + 'Do you really want to remove this action: "%s"?' => 'Naozaj chcete odstrániť akciu: „%s”?', + 'Remove an automatic action' => 'Odstrániť automatickú akciu', + 'Assign the task to a specific user' => 'Prideliť úlohu zadanému používateľovi', + 'Assign the task to the person who does the action' => 'Prideliť úlohu osobe, ktorá vykonala akciu', + 'Duplicate the task to another project' => 'Duplikovať akciu do iného projektu', + 'Move a task to another column' => 'Presun úlohy do iného stĺpca', + 'Task modification' => 'Úprava úlohy', + 'Task creation' => 'Vytvorenie úlohy', + 'Closing a task' => 'Ukončenie úlohy', + 'Assign a color to a specific user' => 'Priradiť farbu zadanému používateľovi', + 'Position' => 'Pozícia', + 'Duplicate to project' => 'Duplikovať do iného projektu', + 'Duplicate' => 'Duplikovať', + 'Link' => 'Odkaz', + 'Comment updated successfully.' => 'Komentár úspešne aktualizovaný.', + 'Unable to update your comment.' => 'Nemožno aktualizovať komentár.', + 'Remove a comment' => 'Odstrániť komentár', + 'Comment removed successfully.' => 'Komentár úspešne odstránený.', + 'Unable to remove this comment.' => 'Nemožno odstrániť tento komentár.', + 'Do you really want to remove this comment?' => 'Naozaj chcete odstrániť tento komentár?', + 'Current password for the user "%s"' => 'Aktuálne heslo používateľa „%s”', + 'The current password is required' => 'Aktuálne heslo je povinné', + 'Wrong password' => 'Zlé heslo', + 'Unknown' => 'Neznáme', + 'Last logins' => 'Posledné prihlásenia', + 'Login date' => 'Dátum prihlásenia', + 'Authentication method' => 'Metóda autentifikácie', + 'IP address' => 'Adresa IP', + 'User agent' => 'User-Agent', + 'Persistent connections' => 'Trvalé spojenia', + 'No session.' => 'Bez relácie.', + 'Expiration date' => 'Dátum platnosti', + 'Remember Me' => 'Zapamätať si ma', + 'Creation date' => 'Dátum vytvorenia', + 'Everybody' => 'Každý', + 'Open' => 'Otvorené', + 'Closed' => 'Ukončené', + 'Search' => 'Hľadať', + 'Nothing found.' => 'Nič nenájdené.', + 'Due date' => 'Termín splnenia', + 'Description' => 'Popis', + '%d comments' => '%d komentáre(ov)', + '%d comment' => '%d komentár', + 'Email address invalid' => 'Neplatná emailová adresa', + 'Your external account is not linked anymore to your profile.' => 'Externý účet už nie je prepojený s Vašim profilom.', + 'Unable to unlink your external account.' => 'Nemožno zrušiť prepojenie s externým účtom.', + 'External authentication failed' => 'Externá autentifikácia zlyhala', + 'Your external account is linked to your profile successfully.' => 'Externý účet úspešne prepojený s profilom.', + 'Email' => 'Email', + 'Task removed successfully.' => 'Úloha úspešne odstránená.', + 'Unable to remove this task.' => 'Nemožno odstrániť úlohu.', + 'Remove a task' => 'Odstrániť úlohu', + 'Do you really want to remove this task: "%s"?' => 'Naozaj chcete odstrániť úlohu: „%s”?', + 'Assign automatically a color based on a category' => 'Automaticky nastaviť farbu na základe kategórie', + 'Assign automatically a category based on a color' => 'Automaticky nastaviť kategóriu na základe farby', + 'Task creation or modification' => 'Vytvorenie alebo úprava úlohy', + 'Category' => 'Kategória', + 'Category:' => 'Kategória:', + 'Categories' => 'Kategórie', + 'Your category has been created successfully.' => 'Kategória úspešne vytvorená.', + 'This category has been updated successfully.' => 'Kategória úspešne aktualizovaná.', + 'Unable to update this category.' => 'Nemožno aktualizovať kategóriu.', + 'Remove a category' => 'Odstrániť kategóriu', + 'Category removed successfully.' => 'Kategória úspešne odstránená.', + 'Unable to remove this category.' => 'Nemožno odstrániť kategóriu.', + 'Category modification for the project "%s"' => 'Úprava kategórie projektu „%s”', + 'Category Name' => 'Meno kategórie', + 'Add a new category' => 'Pridať novú kategóriu', + 'Do you really want to remove this category: "%s"?' => 'Naozaj chcete odstrániť kategóriu: „%s”?', + 'All categories' => 'Všetky kategórie', + 'No category' => 'Bez kategórie', + 'The name is required' => 'Meno je povinné', + 'Remove a file' => 'Odstrániť súbor', + 'Unable to remove this file.' => 'Nemožno odstrániť súbor.', + 'File removed successfully.' => 'Súbor úspešne odstránený.', + 'Attach a document' => 'Pripojiť dokument', + 'Do you really want to remove this file: "%s"?' => 'Naozaj chcete odstrániť súbor: „%s”?', + 'Attachments' => 'Prílohy', + 'Edit the task' => 'Upraviť úlohu', + 'Add a comment' => 'Pridať komentár', + 'Edit a comment' => 'Upraviť komentár', + 'Summary' => 'Zhrnutie', + 'Time tracking' => 'Sledovanie času', + 'Estimate:' => 'Očakávané:', + 'Spent:' => 'Strávené:', + 'Do you really want to remove this sub-task?' => 'Naozaj chcete odstrániť túto podúlohu?', + 'Remaining:' => 'Ostáva:', + 'hours' => 'hodiny', + 'estimated' => 'očakávané', + 'Sub-Tasks' => 'Podúlohy', + 'Add a sub-task' => 'Pridať podúlohu', + 'Original estimate' => 'Predpokladaný čas', + 'Create another sub-task' => 'Vytvoriť ďalšiu podúlohu', + 'Time spent' => 'Strávený čas', + 'Edit a sub-task' => 'Upraviť podúlohu', + 'Remove a sub-task' => 'Odstrániť podúlohu', + 'The time must be a numeric value' => 'Čas musí byť číselná hodnota', + 'Todo' => 'Čaká', + 'In progress' => 'V riešení', + 'Sub-task removed successfully.' => 'Podúloha úspešne odstránená.', + 'Unable to remove this sub-task.' => 'Nemožno odstrániť podúlohu.', + 'Sub-task updated successfully.' => 'Podúloha úspešne aktualizovaná.', + 'Unable to update your sub-task.' => 'Nemožno aktualizovať podúlohu.', + 'Unable to create your sub-task.' => 'Nemožno vytvoriť podúlohu.', + 'Maximum size: ' => 'Maximálna veľkosť: ', + 'Display another project' => 'Zobraziť iný projekt', + 'Created by %s' => 'Vytvoril %s', + 'Tasks Export' => 'Export úloh', + 'Start Date' => 'Dátum začiatku', + 'Execute' => 'Vykonať', + 'Task Id' => 'ID úlohy', + 'Creator' => 'Vytvoril', + 'Modification date' => 'Dátum úpravy', + 'Completion date' => 'Dátum ukončenia', + 'Clone' => 'Naklonovať', + 'Project cloned successfully.' => 'Projekt úspešne naklonovaný.', + 'Unable to clone this project.' => 'Nemožno naklonovať tento projekt.', + 'Enable email notifications' => 'Zapnúť upozornenia emailom', + 'Task position:' => 'Pozícia úlohy:', + 'The task #%d has been opened.' => 'Úloha #%d bola otvorená.', + 'The task #%d has been closed.' => 'Úloha #%d bola ukončená.', + 'Sub-task updated' => 'Podúloha aktualizovaná', + 'Title:' => 'Názov:', + 'Status:' => 'Stav:', + 'Assignee:' => 'Pridelené:', + 'Time tracking:' => 'Sledovanie času:', + 'New sub-task' => 'Nová podúloha', + 'New attachment added "%s"' => 'Nová príloha pridaná %s', + 'New comment posted by %s' => 'Nový komentár poslaný %s', + 'New comment' => 'Nový komentár', + 'Comment updated' => 'Komentár aktualizovaný', + 'New subtask' => 'Nová podúloha', + 'I only want to receive notifications for these projects:' => 'Chcem dostávať upozornenia len z týchto projektov:', + 'view the task on Kanboard' => 'zobraziť úlohu v Kanboard', + 'Public access' => 'Verejný prístup', + 'Disable public access' => 'Zakázať verejný prístup', + 'Enable public access' => 'Zapnúť verejný prístup', + 'Public access disabled' => 'Verejný prístup zakázaný', + 'Move the task to another project' => 'Presunúť úlohu do iného projektu', + 'Move to project' => 'Presunúť do projektu', + 'Do you really want to duplicate this task?' => 'Naozaj chcete duplikovať túto úlohu?', + 'Duplicate a task' => 'Duplikovať úlohu', + 'External accounts' => 'Externé účty', + 'Account type' => 'Typ účtu', + 'Local' => 'Lokálny', + 'Remote' => 'Vzdialený', + 'Enabled' => 'Vypnuté', + 'Disabled' => 'Zapnuté', + 'Login:' => 'Prihl. meno:', + 'Full Name:' => 'Celé meno:', + 'Email:' => 'Email:', + 'Notifications:' => 'Upozornenia:', + 'Notifications' => 'Upozornenia', + 'Account type:' => 'Typ účtu:', + 'Edit profile' => 'Upraviť profil', + 'Change password' => 'Zmeniť heslo', + 'Password modification' => 'Úprava hesla', + 'External authentications' => 'Externé autentifikácie', + 'Never connected.' => 'Nikdy neprihlásený.', + 'No external authentication enabled.' => 'Žiadne povolené externé autentifikácie.', + 'Password modified successfully.' => 'Heslo úspešne zmenené.', + 'Unable to change the password.' => 'Nemožno zmeniť heslo.', + 'Change category' => 'Zmeniť kategóriu', + '%s updated the task %s' => '%s upravil úlohu %s', + '%s opened the task %s' => '%s otvoril úlohu %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s presunul úlohu %s na pozíciu #%d v stĺpci „%s”', + '%s moved the task %s to the column "%s"' => '%s presunul úlohu %s do stĺpca „%s”', + '%s created the task %s' => '%s vytvoril úlohu %s', + '%s closed the task %s' => '%s ukončil úlohu %s', + '%s created a subtask for the task %s' => '%s vytvoril podúlohu úlohy %s', + '%s updated a subtask for the task %s' => '%s upravil podúlohu úlohy %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Pridelené %s s očakávaním %s/%s h', + 'Not assigned, estimate of %sh' => 'Nepridelené, očakávané %s h', + '%s updated a comment on the task %s' => '%s aktualizoval komentár úlohy %s', + '%s commented the task %s' => '%s komentoval úlohu %s', + '%s\'s activity' => 'Aktivita používateľa %s', + 'RSS feed' => 'Kanál RSS', + '%s updated a comment on the task #%d' => '%s upravil komentár úlohy #%d', + '%s commented on the task #%d' => '%s pridal komentár úlohy #%d', + '%s updated a subtask for the task #%d' => '%s upravil podúlohu úlohy #%d', + '%s created a subtask for the task #%d' => '%s vytvoril podúlohu úlohy #%d', + '%s updated the task #%d' => '%s upravil úlohu #%d', + '%s created the task #%d' => '%s vytvoril úlohu #%d', + '%s closed the task #%d' => '%s ukončil úlohu #%d', + '%s opened the task #%d' => '%s otvoril úlohu #%d', + 'Activity' => 'Aktivita', + 'Default values are "%s"' => 'Predvolené hodnoty sú „%s”', + 'Default columns for new projects (Comma-separated)' => 'Predvolené stĺpce nových projektov (oddelené čiarkami)', + 'Task assignee change' => 'Zmena pridelenia úlohy', + '%s changed the assignee of the task #%d to %s' => '%s zmenil pridelenie úlohy #%d na %s', + '%s changed the assignee of the task %s to %s' => '%s zmenil pridelenie úlohy %s na %s', + 'New password for the user "%s"' => 'Nové heslo používateľa „%s”', + 'Choose an event' => 'Zvoľte udalosť', + 'Create a task from an external provider' => 'Vytvoriť úlohu od externého poskytovateľa', + 'Change the assignee based on an external username' => 'Zmeniť pridelenie na základe externého používateľa', + 'Change the category based on an external label' => 'Zmeniť kategóriu na základe externej menovky', + 'Reference' => 'Odkaz', + 'Label' => 'Menovka', + 'Database' => 'Databázy', + 'About' => 'O aplikácii', + 'Database driver:' => 'Ovládač databázy:', + 'Board settings' => 'Nastavenia nástenky', + 'Webhook settings' => 'Nastavenia webových hákov', + 'Reset token' => 'Obnoviť token', + 'API endpoint:' => 'Prípojný bod API:', + 'Refresh interval for personal board' => 'Interval obnovenia súkromných násteniek', + 'Refresh interval for public board' => 'Interval obnovenia verejných násteniek', + 'Task highlight period' => 'Doba zvýraznenia úlohy', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Doba (v sekundách), počas ktorej považovať úlohu za nedávno upravenú (0 na vypnutie, predvolene 2 dni)', + 'Frequency in second (60 seconds by default)' => 'Frekvencia v sekundách (predvolene 60 sekúnd)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frekvencia v sekundách (predvolene 10 sekúnd)', + 'Application URL' => 'URL aplikácie', + 'Token regenerated.' => 'Token obnovený.', + 'Date format' => 'Formát dátumu', + 'ISO format is always accepted, example: "%s" and "%s"' => 'Formát ISO je vždy prijímaný, napr.: „%s” a %s”', + 'New personal project' => 'Nový súkromný projekt', + 'This project is personal' => 'Tento projekt je súkromný', + 'Add' => 'Pridať', + 'Start date' => 'Dátum začiatku', + 'Time estimated' => 'Očakávaný čas', + 'There is nothing assigned to you.' => 'Nemáte nič pridelené.', + 'My tasks' => 'Moje úlohy', + 'Activity stream' => 'Prehľad aktivity', + 'Dashboard' => 'Nástenka', + 'Confirmation' => 'Potvrdenie', + 'Webhooks' => 'Webové háky', + 'API' => 'API', + 'Create a comment from an external provider' => 'Vytvoriť komentár od externého poskytovateľa', + 'Project management' => 'Správa projektov', + 'Columns' => 'Stĺpce', + 'Task' => 'Úloha', + 'Percentage' => 'Percentá', + 'Number of tasks' => 'Počet úloh', + 'Task distribution' => 'Distribúcia úloh', + 'Analytics' => 'Analytika', + 'Subtask' => 'Podúloha', + 'User repartition' => 'Rozdelenie používateľov', + 'Clone this project' => 'Naklonovať tento projekt', + 'Column removed successfully.' => 'Stĺpec úspešne odstránený.', + 'Not enough data to show the graph.' => 'Nedostatok dát na zobrazenie grafu.', + 'Previous' => 'Predošlé', + 'The id must be an integer' => 'ID musí byť celé číslo', + 'The project id must be an integer' => 'ID projektu musí byť celé číslo', + 'The status must be an integer' => 'Stav musí byť celé číslo', + 'The subtask id is required' => 'ID podúlohy je povinné', + 'The subtask id must be an integer' => 'ID podúlohy musí byť celé číslo', + 'The task id is required' => 'ID úlohy je povinné', + 'The task id must be an integer' => 'ID úlohy musí byť celé číslo', + 'The user id must be an integer' => 'ID používateľa musí byť celé číslo', + 'This value is required' => 'Táto hodnota je povinná', + 'This value must be numeric' => 'Táto hodnota musí byť číselná', + 'Unable to create this task.' => 'Nemožno vytvoriť túto úlohu.', + 'Cumulative flow diagram' => 'Kumulatívny diagram toku', + 'Daily project summary' => 'Senný sumár projektu', + 'Daily project summary export' => 'Export denného sumáru projektu', + 'Exports' => 'Exporty', + 'This export contains the number of tasks per column grouped per day.' => 'Tento export obsahuje počet úloh na stĺpec, zoskupené pod dňoch.', + 'Active swimlanes' => 'Aktívne dráhy', + 'Add a new swimlane' => 'Pridať novú dráhu', + 'Default swimlane' => 'Hlavná dráha', + 'Do you really want to remove this swimlane: "%s"?' => 'Naozaj chcete odstrániť dráhu: „%s”?', + 'Inactive swimlanes' => 'Neaktívne dráhy', + 'Remove a swimlane' => 'Odstrániť dráhu', + 'Swimlane modification for the project "%s"' => 'Úpravy dráhy projektu „%s”', + 'Swimlane removed successfully.' => 'Dráha úspešne odstránená.', + 'Swimlanes' => 'Dráhy', + 'Swimlane updated successfully.' => 'Dráha úspešne upravená.', + 'Unable to remove this swimlane.' => 'Nemožno odstrániť túto dráhu.', + 'Unable to update this swimlane.' => 'Nemožno upraviť túto dráhu.', + 'Your swimlane has been created successfully.' => 'Dráha úspešne vytvorená.', + 'Example: "Bug, Feature Request, Improvement"' => 'Príklad: "Chyba, Nová vlastnosť, Vylepšenie"', + 'Default categories for new projects (Comma-separated)' => 'Predvolené kategórie nových projektov (oddelené čiarkami)', + 'Integrations' => 'Integrácie', + 'Integration with third-party services' => 'Integrácia so službami tretích strán', + 'Subtask Id' => 'ID podúlohy', + 'Subtasks' => 'Podúlohy', + 'Subtasks Export' => 'Export podúloh', + 'Task Title' => 'Názov úlohy', + 'Untitled' => 'Bez názvu', + 'Application default' => 'Predvolené', + 'Language:' => 'Jazyk:', + 'Timezone:' => 'Časové pásmo:', + 'All columns' => 'Všetky stĺpce', + 'Next' => 'Ďalšie', + '#%d' => '# %d', + 'All swimlanes' => 'Všetky dráhy', + 'All colors' => 'Všetky farby', + 'Moved to column %s' => 'Presunuté do stĺpca %s', + 'User dashboard' => 'Nástenka používateľa', + 'Allow only one subtask in progress at the same time for a user' => 'Povoliť len jednu spracovávanú podúlohu v rovnakom čase na používateľa', + 'Edit column "%s"' => 'Upraviť stĺpec „%s”', + 'Select the new status of the subtask: "%s"' => 'Vyberte nový stav podúlohy: „%s”', + 'Subtask timesheet' => 'Výkaz podúloh', + 'There is nothing to show.' => 'Nič na zobrazenie.', + 'Time Tracking' => 'Sledovanie času', + 'You already have one subtask in progress' => 'Už máte jednu podúlohu v riešení', + 'Which parts of the project do you want to duplicate?' => 'Ktoré časti projektu chcete duplikovať?', + 'Disallow login form' => 'Zakázať prihlasovací formulár', + 'Start' => 'Začiatok', + 'End' => 'Koniec', + 'Task age in days' => 'Vek úlohy v dňoch', + 'Days in this column' => 'Dní v tomto stĺpci', + '%dd' => '%d d', + 'Add a new link' => 'Pridať nový odkaz', + 'Do you really want to remove this link: "%s"?' => 'Naozaj chcete odstrániť odkaz: „%s”?', + 'Do you really want to remove this link with task #%d?' => 'Naozaj chcete odstrániť odkaz s úlohou #%d?', + 'Field required' => 'Pole je povinné', + 'Link added successfully.' => 'Odkaz úspešne pridaný.', + 'Link updated successfully.' => 'Odkaz úspešne upravený.', + 'Link removed successfully.' => 'Odkaz úspešne odstránený.', + 'Link labels' => 'Menovky odkazov', + 'Link modification' => 'Úprava odkazu', + 'Opposite label' => 'Opačná menovka', + 'Remove a link' => 'Odstrániť odkaz', + 'The labels must be different' => 'Menovky sa musia líšiť', + 'There is no link.' => 'Žiadny odkaz.', + 'This label must be unique' => 'Menovka musí byť jedinečná', + 'Unable to create your link.' => 'Nemožno vytvoriť odkaz.', + 'Unable to update your link.' => 'Nemožno aktualizovať odkaz.', + 'Unable to remove this link.' => 'Nemožno odstrániť odkaz.', + 'relates to' => 'súvisí s', + 'blocks' => 'blokuje', + 'is blocked by' => 'je blokovaná', + 'duplicates' => 'duplikuje', + 'is duplicated by' => 'je duplikovaná', + 'is a child of' => 'je potomkom', + 'is a parent of' => 'je rodičom', + 'targets milestone' => 'cieľový míľnik', + 'is a milestone of' => 'je míľnikom', + 'fixes' => 'opravuje', + 'is fixed by' => 'je opravená', + 'This task' => 'Táto úloha', + '<1h' => '<1 h', + '%dh' => '%d h', + 'Expand tasks' => 'Rozbaliť úlohy', + 'Collapse tasks' => 'Zbaliť úlohy', + 'Expand/collapse tasks' => 'Rozbaliť/Zbaliť úlohy', + 'Close dialog box' => 'Zatvoriť dialógové okno', + 'Submit a form' => 'Odoslať formulár', + 'Board view' => 'Zobrazenie nástenky', + 'Keyboard shortcuts' => 'Klávesové skratky', + 'Open board switcher' => 'Prepínač otvorených násteniek', + 'Application' => 'Aplikácia', + 'Compact view' => 'Kompaktné zobrazenie', + 'Horizontal scrolling' => 'Vodorovný posun', + 'Compact/wide view' => 'Kompaktné/široké zobrazenie', + 'Currency' => 'Mena', + 'Personal project' => 'Súkromný projekt', + 'AUD - Australian Dollar' => 'AUD – Austrálsky dolár', + 'CAD - Canadian Dollar' => 'CAD – Kanadský dolár', + 'CHF - Swiss Francs' => 'CHF – Švajčiarsky frank', + 'Custom Stylesheet' => 'Vlastné CSS', + 'EUR - Euro' => 'EUR – Euro', + 'GBP - British Pound' => 'GBP – Britská libra', + 'INR - Indian Rupee' => 'INR – Indická rupia', + 'JPY - Japanese Yen' => 'JPY – Japonský jen', + 'NZD - New Zealand Dollar' => 'NZD – Novozélandský dolár', + 'PEN - Peruvian Sol' => 'PEN – peruánsky sol', + 'RSD - Serbian dinar' => 'RSD – Srbský dinár', + 'CNY - Chinese Yuan' => 'CNY – Čínsky juan', + 'USD - US Dollar' => 'USD – Americký dolár', + 'VES - Venezuelan Bolívar' => 'Venezuelský bolívar', + 'Destination column' => 'Cieľový stĺpec', + 'Move the task to another column when assigned to a user' => 'Presunúť úlohu do iného stĺpca, keď je pridelená používateľovi', + 'Move the task to another column when assignee is cleared' => 'Presunúť úlohu do iného stĺpca, keď je pridelenie vymazané', + 'Source column' => 'Zdrojový stĺpec', + 'Transitions' => 'Prechody', + 'Executer' => 'Spúšťač', + 'Time spent in the column' => 'Čas strávený v stĺpci', + 'Task transitions' => 'Prechody úloh', + 'Task transitions export' => 'Export prechodov úloh', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Tento výstup obsahuje presuny stĺpcov všetkých úloh s dátumom, používateľom a časom stráveným v každom prechode.', + 'Currency rates' => 'Kurzy meny', + 'Rate' => 'Kurz', + 'Change reference currency' => 'Zmeniť referenčnú menu', + 'Reference currency' => 'Referenčná mena', + 'The currency rate has been added successfully.' => 'Kurz meny bol úspešne pridaný.', + 'Unable to add this currency rate.' => 'Nemožno pridať tento kurz meny.', + 'Webhook URL' => 'URL webového háku', + '%s removed the assignee of the task %s' => '%s odstránil pridelenie úlohy %s', + 'Information' => 'Informácie', + 'Check two factor authentication code' => 'Overiť dvojfaktorový autentifikačný kód', + 'The two factor authentication code is not valid.' => 'Dvojfaktorový autentifikačný kód nie je platný.', + 'The two factor authentication code is valid.' => 'Dvojfaktorový autentifikačný kód je platný.', + 'Code' => 'Kód', + 'Two factor authentication' => 'Dvojfaktorová autentifikácia', + 'This QR code contains the key URI: ' => 'Tento kód QR obsahuje URI kľúča: ', + 'Check my code' => 'Skontrolovať svoj kód', + 'Secret key: ' => 'Tajný kľúč: ', + 'Test your device' => 'Otestujte svoje zariadenie', + 'Assign a color when the task is moved to a specific column' => 'Nastaviť farbu, keď je úloha presunutá do zadaného stĺpca', + '%s via Kanboard' => '%s cez Kanboard', + 'Burndown chart' => 'Burndown diagram', + 'This chart show the task complexity over the time (Work Remaining).' => 'Tento diagram zobrazuje komplexnosť úloh v čase (Zostávajúca práca).', + 'Screenshot taken %s' => 'Vytvorená snímka obrazovky %s', + 'Add a screenshot' => 'Pridať snímku obrazovky', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Vytvorte snímku obrazovky a stlačte CTRL+V alebo ⌘+V na jej vloženie tu.', + 'Screenshot uploaded successfully.' => 'Snímka úspešne nahraná.', + 'SEK - Swedish Krona' => 'SEK – Švédska koruna', + 'Identifier' => 'Identifikátor', + 'Disable two factor authentication' => 'Vypnúť dvojfaktorovú autentifikáciu', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Naozaj chcete zakázať dvojfaktorovú autentifikáciu používateľa: „%s”?', + 'Edit link' => 'Upraviť odkaz', + 'Start to type task title...' => 'Začnite písať názov úlohy...', + 'A task cannot be linked to itself' => 'Úloha nemôže odkazovať sama na seba', + 'The exact same link already exists' => 'Rovnaký odkaz už existuje', + 'Recurrent task is scheduled to be generated' => 'Opakovaná úloha je naplánovaná na vygenerovanie', + 'Score' => 'Skóre', + 'The identifier must be unique' => 'Identifikátor musí byť jedinečný', + 'This linked task id doesn\'t exists' => 'ID odkazovanej úlohy neexistuje', + 'This value must be alphanumeric' => 'Hodnota musí byť alfanumerická', + 'Edit recurrence' => 'Upraviť opakovanie', + 'Generate recurrent task' => 'Generovať opakovanú úlohu', + 'Trigger to generate recurrent task' => 'Spúšťač generovania opakovanej úlohy', + 'Factor to calculate new due date' => 'Faktor výpočtu nového termínu splnenia', + 'Timeframe to calculate new due date' => 'Časový rozvrh výpočtu nového termínu splnenia', + 'Base date to calculate new due date' => 'Základný dátum výpočtu nového termínu splnenia', + 'Action date' => 'Dátum akcie', + 'Base date to calculate new due date: ' => 'Základný dátum výpočtu nového termínu splnenia: ', + 'This task has created this child task: ' => 'Úloha vytvorila túto dcérsku úlohu: ', + 'Day(s)' => 'Deň(dni)', + 'Existing due date' => 'Existujúci termín splnenia', + 'Factor to calculate new due date: ' => 'Faktor výpočtu nového termínu splnenia: ', + 'Month(s)' => 'Mesiac(e)', + 'This task has been created by: ' => 'Túto úlohu vytvoril: ', + 'Recurrent task has been generated:' => 'Opakovaná úloha bola vygenerovaná:', + 'Timeframe to calculate new due date: ' => 'Časový rozvrh výpočtu nového termínu splnenia: ', + 'Trigger to generate recurrent task: ' => 'Spúšťač generovania opakovanej úlohy: ', + 'When task is closed' => 'Keď je úloha ukončená', + 'When task is moved from first column' => 'Keď je úloha presunutá z prvého stĺpca', + 'When task is moved to last column' => 'Keď je úloha presunutá do posledného stĺpca', + 'Year(s)' => 'Rok(y)', + 'Project settings' => 'Nastavenia projektu', + 'Automatically update the start date' => 'Automaticky aktualizovať dátum začiatku', + 'iCal feed' => 'Kanál iCal', + 'Preferences' => 'Nastavenia', + 'Security' => 'Bezpečnosť', + 'Two factor authentication disabled' => 'Dvojfaktorová autentifikácia zakázaná', + 'Two factor authentication enabled' => 'Dvojfaktorová autentifikácia povolená', + 'Unable to update this user.' => 'Nemožno aktualizovať tohoto používateľa.', + 'There is no user management for personal projects.' => 'Súkromné projekty nemajú správu používateľov.', + 'User that will receive the email' => 'Používateľ, ktorý dostane email', + 'Email subject' => 'Predmet emailu', + 'Date' => 'Dátum', + 'Add a comment log when moving the task between columns' => 'Pridať do záznamu komentár, keď je úloha presunutá medzi stĺpcami', + 'Move the task to another column when the category is changed' => 'Presunúť úlohu do iného stĺpca, keď je zmenená kategória', + 'Send a task by email to someone' => 'Poslať úlohu niekomu emailom', + 'Reopen a task' => 'Znova tvoriť úlohu', + 'Notification' => 'Upozornenie', + '%s moved the task #%d to the first swimlane' => '%s presunul úlohu #%d do prvej dráhy', + 'Swimlane' => 'Dráha', + '%s moved the task %s to the first swimlane' => '%s presunul úlohu %s do prvej dráhy', + '%s moved the task %s to the swimlane "%s"' => '%s presunul úlohu %s do dráhy „%s”', + 'This report contains all subtasks information for the given date range.' => 'Tento výstup obsahuje informácie o všetkých podúlohách v zadanom rozsahu dátumov.', + 'This report contains all tasks information for the given date range.' => 'Tento výstup obsahuje všetky úlohy v zadanom rozsahu dátumov.', + 'Project activities for %s' => 'Aktivity projektu %s', + 'view the board on Kanboard' => 'zobraziť v nástenke Kanboard', + 'The task has been moved to the first swimlane' => 'Úloha bola presunutá do prvej dráhy', + 'The task has been moved to another swimlane:' => 'Úloha bola presunutá do inej dráhy:', + 'New title: %s' => 'Nový názov: %s', + 'The task is not assigned anymore' => 'Úloha už viac nie je pridelená', + 'New assignee: %s' => 'Nové pridelenie: %s', + 'There is no category now' => 'Teraz žiadna kategória', + 'New category: %s' => 'Nová kategória. %s', + 'New color: %s' => 'Nová farba: %s', + 'New complexity: %d' => 'Nová zložitosť: %d', + 'The due date has been removed' => 'Termín splnenia odstránený', + 'There is no description anymore' => 'Už viac nemá popis', + 'Recurrence settings has been modified' => 'Nastavenia opakovania boli zmenené', + 'Time spent changed: %sh' => 'Strávený čas zmenený: %s hod', + 'Time estimated changed: %sh' => 'Predpokladaný čas zmenený: %s hod', + 'The field "%s" has been updated' => 'Pole „%s” bolo aktualizované', + 'The description has been modified:' => 'Popis bol zmenený:', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Naozaj chcete ukončiť úlohu „%s” spolu so všetkými podúlohami?', + 'I want to receive notifications for:' => 'Chcem dostávať upozornenia na:', + 'All tasks' => 'Všetky úlohy', + 'Only for tasks assigned to me' => 'Len úlohy pridelené mne', + 'Only for tasks created by me' => 'Len mnou vytvorené úlohy', + 'Only for tasks created by me and tasks assigned to me' => 'Len mnou vytvorené a mne pridelené úlohy', + '%%Y-%%m-%%d' => '%%d. %%m. %%Y', + 'Total for all columns' => 'Celkom všetky stĺpce', + 'You need at least 2 days of data to show the chart.' => 'Na zobrazenie diagramu sú potrebné dáta aspoň za dva dni.', + '<15m' => '<15 m', + '<30m' => '<30 m', + 'Stop timer' => 'Zastaviť časovač', + 'Start timer' => 'Spustiť časovač', + 'My activity stream' => 'Moja aktivita', + 'Search tasks' => 'Hľadať úlohy', + 'Reset filters' => 'Vymazať filtre', + 'My tasks due tomorrow' => 'Moje zajtrajšie úlohy', + 'Tasks due today' => 'Dnešné úlohy', + 'Tasks due tomorrow' => 'Zajtrajšie úlohy', + 'Tasks due yesterday' => 'Včerajšie úlohy', + 'Closed tasks' => 'Ukončené úlohy', + 'Open tasks' => 'Otvorené úlohy', + 'Not assigned' => 'Nepridelené', + 'View advanced search syntax' => 'Zobraziť syntax pokročilého hľadania', + 'Overview' => 'Prehľad', + 'Board/Calendar/List view' => 'Zobrazenie Nástenky/Kalendára/Zoznamu', + 'Switch to the board view' => 'Prepnúť do zobrazenia nástenky', + 'Switch to the list view' => 'Prepnúť do zobrazenia zoznamu', + 'Go to the search/filter box' => 'Prejsť do poľa hľadania/filtra', + 'There is no activity yet.' => 'Zatiaľ žiadna aktivita.', + 'No tasks found.' => 'Nenájdené žiadne úlohy.', + 'Keyboard shortcut: "%s"' => 'Klávesová skratka: „%s”', + 'List' => 'Zoznam', + 'Filter' => 'Filter', + 'Advanced search' => 'Pokročilé hľadanie', + 'Example of query: ' => 'Príklad dopytu: ', + 'Search by project: ' => 'Hľadať podľa projektu: ', + 'Search by column: ' => 'Hľadať podľa stĺpca: ', + 'Search by assignee: ' => 'Hľadať podľa pridelenia: ', + 'Search by color: ' => 'Hľadať podľa farby: ', + 'Search by category: ' => 'Hľadať podľa kategórie: ', + 'Search by description: ' => 'Hľadať podľa popisu: ', + 'Search by due date: ' => 'Hľadať podľa dátumu splnenia: ', + 'Average time spent in each column' => 'Priemerný čas strávený v každom stĺpci', + 'Average time spent' => 'Priemerne strávený čas', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Tento diagram zobrazuje priemerný čas cyklu a trvania posledných %d úloh.', + 'Average Lead and Cycle time' => 'Priemerný čas trvania a cyklu', + 'Average lead time: ' => 'Priemerný čas trvania: ', + 'Average cycle time: ' => 'Priemerný čas cyklu: ', + 'Cycle Time' => 'Čas cyklu', + 'Lead Time' => 'Čas trvania', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Tento diagram zobrazuje priemerný čas cyklu a trvania posledných %d úloh v čase.', + 'Average time into each column' => 'Priemerný čas v stĺpci', + 'Lead and cycle time' => 'Čas trvania a cyklu', + 'Lead time: ' => 'Čas trvania: ', + 'Cycle time: ' => 'Čas cyklu: ', + 'Time spent in each column' => 'Čas strávený v každom stĺpci', + 'The lead time is the duration between the task creation and the completion.' => 'Čas trvania je doba medzi vytvorením úlohy a jej ukončením.', + 'The cycle time is the duration between the start date and the completion.' => 'Čas cyklu je doba medzi začatím úlohy a jej ukončením.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Ak úloha nie je ukončená, je namiesto času ukončenia použitý aktuálny čas.', + 'Set the start date automatically' => 'Automaticky nastaviť dátum začiatku', + 'Edit Authentication' => 'Upraviť Autentifikáciu', + 'Remote user' => 'Vzdialený používateľ', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Vzdialení používatelia nemajú uložené svoje heslo v databáze Kanboard, príklady: LDAP, účty Google a Github.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Ak označíte zaškrtávacie pole „Zakázať prihlasovací formulár”, prihlasovacie údaje zadané v prihlasovacom formulári budú ignorované.', + 'Default task color' => 'Predvolená farba úlohy', + 'This feature does not work with all browsers.' => 'Táto vlastnosť nefunguje vo všetkých prehliadačoch.', + 'There is no destination project available.' => 'Cieľový projekt nie je dostupný.', + 'Trigger automatically subtask time tracking' => 'Automaticky spustiť sledovanie času podúlohy', + 'Include closed tasks in the cumulative flow diagram' => 'V kumulatívnom diagrame toku zahrnúť aj ukončené úlohy', + 'Current swimlane: %s' => 'Aktuálna dráha: %s', + 'Current column: %s' => 'Aktuálny stĺpec: %s', + 'Current category: %s' => 'Aktuálna kategória: %s', + 'no category' => 'žiadna kategória', + 'Current assignee: %s' => 'Aktuálne pridelené: %s', + 'not assigned' => 'nepridelené', + 'Author:' => 'Autor:', + 'contributors' => 'prispievatelia', + 'License:' => 'Licencia:', + 'License' => 'Licencia', + 'Enter the text below' => 'Zadajte nasledujúci text', + 'Start date:' => 'Dátum začiatku:', + 'Due date:' => 'Termín ukončenia:', + 'People who are project managers' => 'Ľudia, ktorí sú správcovia projektu', + 'People who are project members' => 'Ľudia, ktorí sú členovia projektu', + 'NOK - Norwegian Krone' => 'NOK – Nórska koruna', + 'Show this column' => 'Zobraziť tento stĺpec', + 'Hide this column' => 'Skryť tento stĺpec', + 'End date' => 'Dátum ukončenia', + 'Users overview' => 'Prehľad používateľov', + 'Members' => 'Členovia', + 'Shared project' => 'Zdieľané projekty', + 'Project managers' => 'Správcovia projektu', + 'Projects list' => 'Zoznam projektov', + 'End date:' => 'Dátum ukončenia:', + 'Change task color when using a specific task link' => 'Zmeniť farbu úlohy, pri použití daného odkazu na úlohu', + 'Task link creation or modification' => 'Vytvorenie alebo úprava odkazu na úlohu', + 'Milestone' => 'Míľnik', + 'Reset the search/filter box' => 'Vymazať pole hľadania/filtra', + 'Documentation' => 'Dokumentácia', + 'Author' => 'Autor', + 'Version' => 'Verzia', + 'Plugins' => 'Zásuvné moduly', + 'There is no plugin loaded.' => 'Žiadne načítané zásuvné moduly.', + 'My notifications' => 'Moje upozornenia', + 'Custom filters' => 'Vlastné filtre', + 'Your custom filter has been created successfully.' => 'Váš vlastný filter bol úspešne vytvorený.', + 'Unable to create your custom filter.' => 'Nemožno vytvoriť Váš vlastný filter.', + 'Custom filter removed successfully.' => 'Vlastný filter úspešne odstránený.', + 'Unable to remove this custom filter.' => 'Nemožno odstrániť vlastný filter.', + 'Edit custom filter' => 'Upraviť vlastný filter', + 'Your custom filter has been updated successfully.' => 'Vlastný filter bol úspešne aktualizovaný.', + 'Unable to update custom filter.' => 'Nemožno aktualizovať vlastný filter.', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Nová príloha úlohy #%d: %s', + 'New comment on task #%d' => 'Nový komentár úlohy #%d', + 'Comment updated on task #%d' => 'Aktualizovaný komentár úlohy #%d', + 'New subtask on task #%d' => 'Nová podúloha úlohy #%d', + 'Subtask updated on task #%d' => 'Aktualizovaná podúloha úlohy #%d', + 'New task #%d: %s' => 'Nová úloha #%d: %s', + 'Task updated #%d' => 'Aktualizovaná úloha #%d', + 'Task #%d closed' => 'Úloha #%d ukončená', + 'Task #%d opened' => 'Úloha #%d otvorená', + 'Column changed for task #%d' => 'Zmenený stĺpec úlohy #%d', + 'New position for task #%d' => 'Nová pozícia úlohy #%d', + 'Swimlane changed for task #%d' => 'Zmenená dráha úlohy #%d', + 'Assignee changed on task #%d' => 'Zmenené pridelenie úlohy #%d', + '%d overdue tasks' => '%d úloh po termíne', + 'No notification.' => 'Žiadne upozornenia.', + 'Mark all as read' => 'Označiť všetky ako prečítané', + 'Mark as read' => 'Označiť ako prečítané', + 'Total number of tasks in this column across all swimlanes' => 'Celkový počet úloh tohoto stĺpca vo všetkých dráhach', + 'Collapse swimlane' => 'Zbaliť cestu', + 'Expand swimlane' => 'Rozbaliť cestu', + 'Add a new filter' => 'Pridať nový filter', + 'Share with all project members' => 'Zdieľať so všetkými členmi projektu', + 'Shared' => 'Zdieľané', + 'Owner' => 'Vlastník', + 'Unread notifications' => 'Neprečítané upozornenia', + 'Notification methods:' => 'Metódy upozornení:', + 'Unable to read your file' => 'Nemožno čítať zadaný súbor', + '%d task(s) have been imported successfully.' => '%d úloha(y) úspešne importovaná/é.', + 'Nothing has been imported!' => 'Nebolo importované nič', + 'Import users from CSV file' => 'Importovať používateľov zo súboru CSV', + '%d user(s) have been imported successfully.' => '%d požívateľ(ia) úspešne importovaní.', + 'Comma' => 'čiarka', + 'Semi-colon' => 'bodkočiarka', + 'Tab' => 'tabulátor', + 'Vertical bar' => 'zvislá čiara', + 'Double Quote' => 'úvodzovky', + 'Single Quote' => 'apostrofy', + '%s attached a file to the task #%d' => '%s pripojil súbor k úlohe #%d', + 'There is no column or swimlane activated in your project!' => 'V tomto projekte nie sú aktivované stĺpce alebo dráhy', + 'Append filter (instead of replacement)' => 'Pridať filter (namiesto nahradenia)', + 'Append/Replace' => 'Pridať/Nahradiť', + 'Append' => 'Pridať', + 'Replace' => 'Nahradiť', + 'Import' => 'Importovať', + 'Change sorting' => 'Zmeniť radenie', + 'Tasks Importation' => 'Import úloh', + 'Delimiter' => 'Oddeľovač', + 'Enclosure' => 'Uzavretie', + 'CSV File' => 'Súbor CSV', + 'Instructions' => 'Pokyny', + 'Your file must use the predefined CSV format' => 'Súbor musí používať prednastavený formát CSV', + 'Your file must be encoded in UTF-8' => 'Súbor musí byť kódovaný v UTF-8', + 'The first row must be the header' => 'Prvý riadok musí byť hlavička', + 'Duplicates are not verified for you' => 'Duplikáty nie sú kontrolované', + 'The due date must use the ISO format: YYYY-MM-DD' => 'Dátum ukončenia musí byť vo formáte ISO: RRRR-MM-DD', + 'Download CSV template' => 'Stiahnuť šablónu CSV', + 'No external integration registered.' => 'Nie je zaregistrovaná externá integrácia.', + 'Duplicates are not imported' => 'Duplikáta nie sú importované', + 'Usernames must be lowercase and unique' => 'Mená používateľov musia byť malými písmenami a jedinečné', + 'Passwords will be encrypted if present' => 'Heslá, ak existujú, budú šifrované', + '%s attached a new file to the task %s' => '%s pripojil nový súbor k úlohe %s', + 'Link type' => 'Typ odkazu', + 'Assign automatically a category based on a link' => 'Automaticky nastaviť kategóriu na základe odkazu', + 'BAM - Konvertible Mark' => 'BAM – Konvertibilná marka', + 'Assignee Username' => 'Prihl. meno pridelenia', + 'Assignee Name' => 'Meno pridelenia', + 'Groups' => 'Skupiny', + 'Members of %s' => 'Členovia %s', + 'New group' => 'Nová skupina', + 'Group created successfully.' => 'Skupina úspešne vytvorená.', + 'Unable to create your group.' => 'Nemožno vytvoriť skupinu.', + 'Edit group' => 'Upraviť skupinu', + 'Group updated successfully.' => 'Skupina úspešne aktualizovaná.', + 'Unable to update your group.' => 'Nemožno aktualizovať skupinu.', + 'Add group member to "%s"' => 'Pridať člena skupiny do „%s”', + 'Group member added successfully.' => 'Člen skupiny úspešne pridaný.', + 'Unable to add group member.' => 'Nemožno pridať člena skupiny.', + 'Remove user from group "%s"' => 'Odstrániť používateľa zo skupiny „%s”', + 'User removed successfully from this group.' => 'Používateľ úspešne odstránený zo skupiny.', + 'Unable to remove this user from the group.' => 'Nemožno odstrániť používateľa zo skupiny.', + 'Remove group' => 'Odstrániť skupinu', + 'Group removed successfully.' => 'Skupina úspešne odstránená.', + 'Unable to remove this group.' => 'Nemožno odstrániť skupinu.', + 'Project Permissions' => 'Povolenia projektu', + 'Manager' => 'Správca', + 'Project Manager' => 'Správca projektu', + 'Project Member' => 'Člen projektu', + 'Project Viewer' => 'Čitateľ projektu', + 'Your account is locked for %d minutes' => 'Váš účet je zamknutý na %d min', + 'Invalid captcha' => 'Neplatná CAPTCHA', + 'The name must be unique' => 'Meno musí byť jedinečné', + 'View all groups' => 'Zobraziť všetky skupiny', + 'There is no user available.' => 'Nie je dostupný žiadny používateľ.', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Naozaj chcete odstrániť používateľa „%s” zo skupiny „%s”?', + 'There is no group.' => 'Nie je tu žiadna skupina.', + 'Add group member' => 'Pridať člena skupiny', + 'Do you really want to remove this group: "%s"?' => 'Naozaj chcete odstrániť skupinu: „%s”?', + 'There is no user in this group.' => 'Žiadny používatelia tejto skupiny.', + 'Permissions' => 'Povolenia', + 'Allowed Users' => 'Povolení používatelia', + 'No specific user has been allowed.' => 'Žiadni výslovne povolení používatelia.', + 'Role' => 'Rola', + 'Enter user name...' => 'Zadajte prihlasovacie meno...', + 'Allowed Groups' => 'Povolené skupiny', + 'No group has been allowed.' => 'Žiadne výslovne povolené skupiny.', + 'Group' => 'Skupiny', + 'Group Name' => 'Meno skupiny', + 'Enter group name...' => 'Zadajte meno skupiny...', + 'Role:' => 'Rola:', + 'Project members' => 'Členovia projektu', + '%s mentioned you in the task #%d' => '%s Vás spomenul v úlohe #%d', + '%s mentioned you in a comment on the task #%d' => '%s Vás spomenul v komentári úlohy #%d', + 'You were mentioned in the task #%d' => 'Boli ste spomenutý v úlohe #%d', + 'You were mentioned in a comment on the task #%d' => 'Boli ste spomenutý v komentári úlohy #%d', + 'Estimated hours: ' => 'Očakávané hodiny: ', + 'Actual hours: ' => 'Aktuálne hodiny: ', + 'Hours Spent' => 'Strávených hodín', + 'Hours Estimated' => 'Plánovaných hodín', + 'Estimated Time' => 'Plánovaný čas', + 'Actual Time' => 'Aktuálny čas', + 'Estimated vs actual time' => 'Očakávaný a aktuálny čas', + 'RUB - Russian Ruble' => 'RUB – Ruský rubeľ', + 'Assign the task to the person who does the action when the column is changed' => 'Prideliť úlohu osobe, ktorá vykonala akciu so zmenou stĺpca', + 'Close a task in a specific column' => 'Ukončiť úlohu v zadanom stĺpci', + 'Time-based One-time Password Algorithm' => 'Algoritmus časovo obmedzeného jednoúčelového hesla', + 'Two-Factor Provider: ' => 'Poskytovateľ dvojfaktoru: ', + 'Disable two-factor authentication' => 'Vypnúť dvojfaktorovú autentifikáciu', + 'Enable two-factor authentication' => 'Zapnúť dvojfaktorovú autentifikáciu', + 'There is no integration registered at the moment.' => 'Momentálne žiadna registrovaná integrácia.', + 'Password Reset for Kanboard' => 'Obnova hesla pre Kanboard', + 'Forgot password?' => 'Zabudli ste heslo?', + 'Enable "Forget Password"' => 'Zapnúť „Zabudnuté heslo”', + 'Password Reset' => 'Obnovenie hesla', + 'New password' => 'Nové heslo', + 'Change Password' => 'Zmeniť heslo', + 'To reset your password click on this link:' => 'Na obnovu hesla kliknite na nasledujúci odkaz:', + 'Last Password Reset' => 'Posledná obnova hesla', + 'The password has never been reinitialized.' => 'Heslo nebolo nikdy obnovované.', + 'Creation' => 'Vytvorenie', + 'Expiration' => 'Vypršanie', + 'Password reset history' => 'História obnovenia hesla', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Všetky úlohy stĺpca „%s” a dráhy „%s” boli úspešne ukončené.', + 'Do you really want to close all tasks of this column?' => 'Naozaj chcete ukončiť všetky úlohy tohoto stĺpca?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d úloha(y) v stĺpci „%s” a dráhy „%s” budú ukončené.', + 'Close all tasks in this column and this swimlane' => 'Ukončiť všetky úlohy tohoto stĺpca', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Žiadny zásuvný modul nemá zaregistrovanú metódu upozornení projektu. I tak môžete nastaviť jednotlivé upozornenia vo svojom profile.', + 'My dashboard' => 'Moja nástenka', + 'My profile' => 'Môj profil', + 'Project owner: ' => 'Vlastník projektu: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'Identifikátor projektu je voliteľný a musí byť alfanumerický, napr. MOJPORJEKT.', + 'Project owner' => 'Vlastník projektu', + 'Personal projects do not have users and groups management.' => 'Súkromné projekty nemajú správu používateľov a skupín.', + 'There is no project member.' => 'Žiadny člen projektu.', + 'Priority' => 'Priorita', + 'Task priority' => 'Priorita úlohy', + 'General' => 'Všeobecné', + 'Dates' => 'Dátumy', + 'Default priority' => 'Predvolená priorita', + 'Lowest priority' => 'Najnižšia priorita', + 'Highest priority' => 'Najvyššia priorita', + 'Close a task when there is no activity' => 'Ukončiť úlohu, ak je bez aktivity', + 'Duration in days' => 'Trvanie v dňoch', + 'Send email when there is no activity on a task' => 'Poslať email, keď nenastala aktivita s úlohou', + 'Unable to fetch link information.' => 'Nemožno získať informácie odkazu.', + 'Daily background job for tasks' => 'Denná úloha na pozadí pre úlohy', + 'Auto' => 'Auto', + 'Related' => 'Súvisí', + 'Attachment' => 'Prílohy', + 'Web Link' => 'Webový odkaz', + 'External links' => 'Externé odkazy', + 'Add external link' => 'Pridať externý odkaz', + 'Type' => 'Typ', + 'Dependency' => 'Závislosť', + 'Add internal link' => 'Pridať interný odkaz', + 'Add a new external link' => 'Pridať nový externý odkaz', + 'Edit external link' => 'Upraviť externý odkaz', + 'External link' => 'Externý odkaz', + 'Copy and paste your link here...' => 'Prekopírujte a vložte svoj odkaz tu...', + 'URL' => 'URL', + 'Internal links' => 'Interné odkazy', + 'Assign to me' => 'Prideliť mne', + 'Me' => 'mne', + 'Do not duplicate anything' => 'Nič neduplikovať', + 'Projects management' => 'Správa projektov', + 'Users management' => 'Správa používateľov', + 'Groups management' => 'Správa skupín', + 'Create from another project' => 'Vytvoriť z iného projektu', + 'open' => 'otvorený', + 'closed' => 'ukončený', + 'Priority:' => 'Priorita:', + 'Reference:' => 'Odkaz:', + 'Complexity:' => 'Zložitosť:', + 'Swimlane:' => 'Dráha:', + 'Column:' => 'Stĺpec:', + 'Position:' => 'Pozícia:', + 'Creator:' => 'Autor:', + 'Time estimated:' => 'Predpokladaný čas:', + '%s hours' => '%s hod', + 'Time spent:' => 'Strávený čas:', + 'Created:' => 'Vytvorené:', + 'Modified:' => 'Zmenené:', + 'Completed:' => 'Dokončené:', + 'Started:' => 'Začaté:', + 'Moved:' => 'Presunuté:', + 'Task #%d' => 'Úloha #%d', + 'Time format' => 'Formát času', + 'Start date: ' => 'Dátum začiatku: ', + 'End date: ' => 'Dátum ukončenia: ', + 'New due date: ' => 'Nový termín splnenia: ', + 'Start date changed: ' => 'Dátum začiatku zmenený: ', + 'Disable personal projects' => 'Vypnúť súkromné projekty', + 'Do you really want to remove this custom filter: "%s"?' => 'Naozaj chcete odstrániť vlastný filter: „%s”?', + 'Remove a custom filter' => 'Odstrániť vlastný filter', + 'User activated successfully.' => 'Používateľ úspešne aktivovaný.', + 'Unable to enable this user.' => 'Nemožno povoliť tohoto používateľa.', + 'User disabled successfully.' => 'Používateľ úspešne zakázaný.', + 'Unable to disable this user.' => 'Nemožno zakázať tohoto používateľa.', + 'All files have been uploaded successfully.' => 'Všetky súbory úspešne nahrané.', + 'The maximum allowed file size is %sB.' => 'Maximálna povolená veľkosť súboru je %sB.', + 'Drag and drop your files here' => 'Pretiahnite svoje súbory tu', + 'choose files' => 'zvoľte súbory', + 'View profile' => 'Zobraziť profil', + 'Two Factor' => 'Dvojfaktor', + 'Disable user' => 'Zakázať používateľa', + 'Do you really want to disable this user: "%s"?' => 'Naozaj chcete zakázať používateľa: „%s”?', + 'Enable user' => 'Povoliť používateľa', + 'Do you really want to enable this user: "%s"?' => 'Naozaj chcete povoliť používateľa: „%s”?', + 'Download' => 'Stiahnuť', + 'Uploaded: %s' => 'Nahrané: %s', + 'Size: %s' => 'Veľkosť: %s', + 'Uploaded by %s' => 'Nahral %s', + 'Filename' => 'Meno súboru', + 'Size' => 'Veľkosť', + 'Column created successfully.' => 'Stĺpec úspešne vytvorený.', + 'Another column with the same name exists in the project' => 'V projekte už existuje iný stĺpec s rovnakým menom', + 'Default filters' => 'Predvolené filtre', + 'Your board doesn\'t have any columns!' => 'Vaša nástenka nemá žiadne stĺpce!', + 'Change column position' => 'Zmeniť pozíciu stĺpca', + 'Switch to the project overview' => 'Prepnúť na prehľad projektu', + 'User filters' => 'Filtre používateľa', + 'Category filters' => 'Filtre kategórie', + 'Upload a file' => 'Nahrať súbor', + 'View file' => 'Zobraziť súbor', + 'Last activity' => 'Nedávna aktivita', + 'Change subtask position' => 'Zmeniť pozíciu podúlohy', + 'This value must be greater than %d' => 'Táto hodnota musí byť väčšia ako %d', + 'Another swimlane with the same name exists in the project' => 'V projekte už existuje iná dráha s rovnakým menom', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Príklad: https://kanboard.example.org/ (slúži na generovanie absolútnych URL)', + 'Actions duplicated successfully.' => 'Akcie úspešne duplikované.', + 'Unable to duplicate actions.' => 'Nemožno duplikovať akciu.', + 'Add a new action' => 'Pridať novú akciu', + 'Import from another project' => 'Importovať z iného projektu', + 'There is no action at the moment.' => 'Momentálne žiadne akcie.', + 'Import actions from another project' => 'Importovať akcie z iného projektu', + 'There is no available project.' => 'Žiadny dostupný projekt.', + 'Local File' => 'Lokálny súbor', + 'Configuration' => 'Nastavenia', + 'PHP version:' => 'Verzia PHP:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'Verzia OS:', + 'Database version:' => 'Verzia databázy:', + 'Browser:' => 'Prehliadač:', + 'Task view' => 'Zobrazenie úlohy', + 'Edit task' => 'Upraviť úlohu', + 'Edit description' => 'Upraviť popis', + 'New internal link' => 'Nový interný odkaz', + 'Display list of keyboard shortcuts' => 'Zobraziť zoznam klávesových skratiek', + 'Avatar' => 'Avatar', + 'Upload my avatar image' => 'Nahrať obrázok svojho avatara', + 'Remove my image' => 'Odstrániť svoj obrázok', + 'The OAuth2 state parameter is invalid' => 'Parameter stavu OAuth2 je neplatný', + 'User not found.' => 'Používateľ nenájdený.', + 'Search in activity stream' => 'Hľadať v prehľade aktivity', + 'My activities' => 'Moje aktivity', + 'Activity until yesterday' => 'Aktivita do včera', + 'Activity until today' => 'Aktivita do dnes', + 'Search by creator: ' => 'Hľadať podľa autora: ', + 'Search by creation date: ' => 'Hľadať podľa dátumu vytvorenia: ', + 'Search by task status: ' => 'Hľadať podľa stavu úlohy: ', + 'Search by task title: ' => 'Hľadať podľa názvu úlohy: ', + 'Activity stream search' => 'Hľadanie v prehľade aktivity', + 'Projects where "%s" is manager' => 'Projekty, kde je správcom „%s”', + 'Projects where "%s" is member' => 'Projekty, kde je členom „%s”', + 'Open tasks assigned to "%s"' => 'Otvorené úlohy pridelené „%s”', + 'Closed tasks assigned to "%s"' => 'Ukončené úlohy pridelené „%s”', + 'Assign automatically a color based on a priority' => 'Automaticky nastaviť farbu na základe priority', + 'Overdue tasks for the project(s) "%s"' => 'Úlohy po termíne projektu(ov) „%s”', + 'Upload files' => 'Nahrať súbory', + 'Installed Plugins' => 'Nainštalované zásuvné moduly', + 'Plugin Directory' => 'Zložka zásuvných modulov', + 'Plugin installed successfully.' => 'Zásuvný modul úspešne nainštalovaný.', + 'Plugin updated successfully.' => 'Zásuvný modul úspešne aktualizovaný.', + 'Plugin removed successfully.' => 'Zásuvný modul úspešne odstránený.', + 'Subtask converted to task successfully.' => 'Podúloha úspešne konvertovaná na úlohu.', + 'Unable to convert the subtask.' => 'Nemožno konvertovať podúlohu.', + 'Unable to extract plugin archive.' => 'Nemožno rozbaliť archív zásuvného modulu.', + 'Plugin not found.' => 'Zásuvný modul nenájdený.', + 'You don\'t have the permission to remove this plugin.' => 'Nemáte práva na odstránenie tohoto zásuvného modulu.', + 'Unable to download plugin archive.' => 'Nemožno stiahnuť archív zásuvného modulu.', + 'Unable to write temporary file for plugin.' => 'Nemožno zapísať dočasný súbor zásuvného modulu.', + 'Unable to open plugin archive.' => 'Nemožno otvoriť archív zásuvného modulu.', + 'There is no file in the plugin archive.' => 'V archíve zásuvného modulu nie je súbor.', + 'Create tasks in bulk' => 'Hromadné vytvorenie úloh', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Táto inštalácia Kanboard nie je nastavená na inštaláciu zásuvných modulov z používateľského rozhrania.', + 'There is no plugin available.' => 'Žiadne dostupné zásuvné moduly.', + 'Install' => 'Inštalovať', + 'Update' => 'Aktualizovať', + 'Up to date' => 'Aktuálny', + 'Not available' => 'Nedostupný', + 'Remove plugin' => 'Odstrániť zásuvný modul', + 'Do you really want to remove this plugin: "%s"?' => 'Naozaj chcete odstrániť zásuvný modul: „%s”?', + 'Uninstall' => 'Odinštalovať', + 'Listing' => 'Zoznam', + 'Metadata' => 'Metadáta', + 'Manage projects' => 'Správa projektov', + 'Convert to task' => 'Konvertovať na úlohu', + 'Convert sub-task to task' => 'Konvertovať podúlohu na úlohu', + 'Do you really want to convert this sub-task to a task?' => 'Naozaj chcete konvertovať túto podúlohu na úlohu?', + 'My task title' => 'Názov mojej úlohy', + 'Enter one task by line.' => 'Zadajte jednu úlohu na riadok.', + 'Number of failed login:' => 'Počet zlyhaných prihlásení:', + 'Account locked until:' => 'Účet uzamknutý do:', + 'Email settings' => 'Nastavenia emailu', + 'Email sender address' => 'Adresa odosielateľa', + 'Email transport' => 'Emailový transport', + 'Webhook token' => 'Token webového háku', + 'Project tags management' => 'Správa značiek projektu', + 'Tag created successfully.' => 'Značka úspešne vytvorená.', + 'Unable to create this tag.' => 'Nemožno vytvoriť túto značku.', + 'Tag updated successfully.' => 'Značka úspešne zmenená.', + 'Unable to update this tag.' => 'Nemožno upraviť túto značku.', + 'Tag removed successfully.' => 'Značka úspešne odstránená.', + 'Unable to remove this tag.' => 'Nemožno odstrániť túto značku.', + 'Global tags management' => 'Správa globálnych značiek', + 'Tags' => 'Značky', + 'Tags management' => 'Správa značiek', + 'Add new tag' => 'Pridať novú značku', + 'Edit a tag' => 'Upraviť značku', + 'Project tags' => 'Značky projektu', + 'There is no specific tag for this project at the moment.' => 'Momentálne tento projekt nemá žiadnu konkrétnu značku.', + 'Tag' => 'Značka', + 'Remove a tag' => 'Odstrániť značku', + 'Do you really want to remove this tag: "%s"?' => 'Naozaj chcete odstrániť značku: „%s”?', + 'Global tags' => 'Globálne značky', + 'There is no global tag at the moment.' => 'Momentálne žiadne globálne značky.', + 'This field cannot be empty' => 'Toto pole nemôže byť prázdne', + 'Close a task when there is no activity in a specific column' => 'Ukončiť úlohu, ak nebola aktivita v zadanom stĺpci', + '%s removed a subtask for the task #%d' => '%s odstránil podúlohu úlohy #%d', + '%s removed a comment on the task #%d' => '%s odstránil komentár úlohy #%d', + 'Comment removed on task #%d' => 'Odstránený komentár úlohy #%d', + 'Subtask removed on task #%d' => 'Odstránená podúloha úlohy #%d', + 'Hide tasks in this column in the dashboard' => 'Skryť úlohy tohoto stĺpca v nástenke', + '%s removed a comment on the task %s' => '%s odstránil komentár úlohy %s', + '%s removed a subtask for the task %s' => '%s odstránil podúlohu úlohy %s', + 'Comment removed' => 'Komentár odstránený', + 'Subtask removed' => 'Podúloha odstránená', + '%s set a new internal link for the task #%d' => '%s nastavil nový interný odkaz úlohy #%d', + '%s removed an internal link for the task #%d' => '%s odstránil interný odkaz úlohy #%d', + 'A new internal link for the task #%d has been defined' => 'Bol definovaný nový interný odkaz úlohy #%d', + 'Internal link removed for the task #%d' => 'Odstránený interný odkaz úlohy #%d', + '%s set a new internal link for the task %s' => '%s vytvoril nový interný odkaz úlohy %s', + '%s removed an internal link for the task %s' => '%s odstránil interný odkaz úlohy %s', + 'Automatically set the due date on task creation' => 'Automaticky nastaviť termín splnenia pri vytvorení úlohy', + 'Move the task to another column when closed' => 'Presunúť úlohu do iného stĺpca pri ukončení', + 'Move the task to another column when not moved during a given period' => 'Presunúť úlohu do iného stĺpca, ak nebola presunutá po zadanú dobu', + 'Dashboard for %s' => 'Nástenka %s', + 'Tasks overview for %s' => 'Prehľad úloh %s', + 'Subtasks overview for %s' => 'Prehľad podúloh %s', + 'Projects overview for %s' => 'Prehľad projektov %s', + 'Activity stream for %s' => 'Prúd aktivity %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Nastaviť farbu, keď je úloha presunutá do zadanej dráhy', + 'Assign a priority when the task is moved to a specific swimlane' => 'Nastaviť prioritu, keď je úloha presunutá do zadanej dráhy', + 'User unlocked successfully.' => 'Používateľ úspešne odomknutý.', + 'Unable to unlock the user.' => 'Nemožno odomknúť používateľa.', + 'Move a task to another swimlane' => 'Presunúť úlohu do inej dráhy', + 'Creator Name' => 'Meno autora', + 'Time spent and estimated' => 'Strávený a očakávaný čas', + 'Move position' => 'Presunúť pozíciu', + 'Move task to another position on the board' => 'Presunúť úlohu na inú pozíciu v nástenke', + 'Insert before this task' => 'Vložiť pred túto úlohu', + 'Insert after this task' => 'Vložiť za túto úlohu', + 'Unlock this user' => 'Odomknúť tohoto používateľa', + 'Custom Project Roles' => 'Vlastné role projektu', + 'Add a new custom role' => 'Pridať novú vlastnú rolu', + 'Restrictions for the role "%s"' => 'Obmedzenia roly „%s”', + 'Add a new project restriction' => 'Pridať nové obmedzenie projektu', + 'Add a new drag and drop restriction' => 'Pridať obmedzenie Ťahaj a Pusť', + 'Add a new column restriction' => 'Pridať obmedzenie nového stĺpca', + 'Edit this role' => 'Upraviť túto rolu', + 'Remove this role' => 'Odstrániť túto rolu', + 'There is no restriction for this role.' => 'Táto rola nemá žiadne obmedzenia.', + 'Only moving task between those columns is permitted' => 'Povolený je len presun úloh medzi týmito stĺpcami', + 'Close a task in a specific column when not moved during a given period' => 'Ukončiť úlohu v zadanom stĺpci, ak nebola presunutá počas zadanej doby', + 'Edit columns' => 'Upraviť stĺpce', + 'The column restriction has been created successfully.' => 'Obmedzenie stĺpca úspešne vytvorené.', + 'Unable to create this column restriction.' => 'Nemožno vytvoriť obmedzenie stĺpca.', + 'Column restriction removed successfully.' => 'Obmedzenie stĺpca úspešne vytvorené.', + 'Unable to remove this restriction.' => 'Nemožno odstrániť obmedzenie.', + 'Your custom project role has been created successfully.' => 'Vlastná rola projektu úspešne vytvorená.', + 'Unable to create custom project role.' => 'Nemožno vytvoriť vlastnú rolu projektu.', + 'Your custom project role has been updated successfully.' => 'Vlastná rola projektu úspešne aktualizovaná.', + 'Unable to update custom project role.' => 'Nemožno aktualizovať vlastnú rolu projektu.', + 'Custom project role removed successfully.' => 'Vlastná rola projektu úspešne vytvorená.', + 'Unable to remove this project role.' => 'Nemožno odstrániť rolu projektu.', + 'The project restriction has been created successfully.' => 'Obmedzenie projektu úspešne vytvorené.', + 'Unable to create this project restriction.' => 'Nemožno vytvoriť obmedzenie projektu.', + 'Project restriction removed successfully.' => 'Obmedzenie projektu úspešne odstránené.', + 'You cannot create tasks in this column.' => 'Nemôžete vytvoriť úlohu v tomto stĺpci.', + 'Task creation is permitted for this column' => 'Vytvorenie úloh povolené pre tento stĺpec', + 'Closing or opening a task is permitted for this column' => 'Otvorenie a ukončenie úloh je povolené pre tento stĺpec', + 'Task creation is blocked for this column' => 'Vytvorenie úloh je zakázané pre tento stĺpec', + 'Closing or opening a task is blocked for this column' => 'Otvorenie a ukončenie úloh je zakázané pre tento stĺpec', + 'Task creation is not permitted' => 'Vytvorenie úlohy nie je dovolené', + 'Closing or opening a task is not permitted' => 'Otvorenie a ukončenie úlohy nie je dovolené', + 'New drag and drop restriction for the role "%s"' => 'Nové obmedzenie Ťahaj a Pusť pre rolu „%s”', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Ľudia, ktorí patria do tejto roly, budú môcť presúvať úlohy len medzi zdrojovým a cieľovým stĺpcom.', + 'Remove a column restriction' => 'Odstrániť obmedzenie stĺpca', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Naozaj chcete odstrániť obmedzenie stĺpca: „%s” do „%s”?', + 'New column restriction for the role "%s"' => 'Nové obmedzenie stĺpca roly „%s”', + 'Rule' => 'Pravidlo', + 'Do you really want to remove this column restriction?' => 'Naozaj chcete odstrániť obmedzenie stĺpca?', + 'Custom roles' => 'Vlastné roly', + 'New custom project role' => 'Nová vlastná rola projektu', + 'Edit custom project role' => 'Upraviť vlastnú rolu projektu', + 'Remove a custom role' => 'Odstrániť vlastnú rolu projektu', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Naozaj chcete odstrániť vlastnú rolu: „%s”? Všetci priradení k tejto role sa stanú členmi projektu.', + 'There is no custom role for this project.' => 'Tento projekt nemá vlastné roly.', + 'New project restriction for the role "%s"' => 'Nové obmedzenie roly projektu „%s”', + 'Restriction' => 'Obmedzenia', + 'Remove a project restriction' => 'Odstrániť obmedzenie projektu', + 'Do you really want to remove this project restriction: "%s"?' => 'naozaj chcete odstrániť obmedzenie projektu: „%s”?', + 'Duplicate to multiple projects' => 'Duplikovať do viacerých', + 'This field is required' => 'Toto pole je povinné', + 'Moving a task is not permitted' => 'Presun úlohy nie je povolený', + 'This value must be in the range %d to %d' => 'Táto hodnota musí byť v rozsahu %d – %d', + 'You are not allowed to move this task.' => 'Nemáte dovolené presunúť túto úlohu.', + 'API User Access' => 'Prístup API používateľa', + 'Preview' => 'Ukážka', + 'Write' => 'Písať', + 'Write your text in Markdown' => 'Napíšte svoj text v Markdown', + 'No personal API access token registered.' => 'Nie sú zaregistrované žiadne prístupové tokeny API.', + 'Your personal API access token is "%s"' => 'Váš osobný prístupový token API je „%s”', + 'Remove your token' => 'Odstrániť svoj token', + 'Generate a new token' => 'Generovať nový token', + 'Showing %d-%d of %d' => 'Zobrazené %d – %d z %d', + 'Outgoing Emails' => 'Odchádzajúce emaily', + 'Add or change currency rate' => 'Pridať alebo zmeniť konverzný kurz', + 'Reference currency: %s' => 'Referenčná mena: %s', + 'Add custom filters' => 'Pridať vlastné filtre', + 'Export' => 'Exportovať', + 'Add link label' => 'Pridať menovku odkazu', + 'Incompatible Plugins' => 'Nekompatibilné zásuvné moduly', + 'Compatibility' => 'Kompatibilita', + 'Permissions and ownership' => 'Povolenia a vlastníctvo', + 'Priorities' => 'Priority', + 'Close this window' => 'Zatvoriť okno', + 'Unable to upload this file.' => 'Nemožno nahrať tento súbor.', + 'Import tasks' => 'Importovať úlohy', + 'Choose a project' => 'Zvoľte projekt', + 'Profile' => 'Profile', + 'Application role' => 'Rola aplikácie', + '%d invitations were sent.' => '%d pozvaní odoslaných.', + '%d invitation was sent.' => '%d pozvanie odoslané.', + 'Unable to create this user.' => 'Nemožno vytvoriť používateľa.', + 'Kanboard Invitation' => 'Pozvánka Kanboard', + 'Visible on dashboard' => 'Viditeľné v nástenke', + 'Created at:' => 'Vytvorené:', + 'Updated at:' => 'Upravené:', + 'There is no custom filter.' => 'Žiadny vlastný filter.', + 'New User' => 'Nový používateľ', + 'Authentication' => 'Autentifikácia', + 'If checked, this user will use a third-party system for authentication.' => 'Ak je zvolené, tento používateľ bude používať na autentifikáciu systém tretej strany.', + 'The password is necessary only for local users.' => 'Heslo je potrebné len pre lokálnych používateľov.', + 'You have been invited to register on Kanboard.' => 'Boli ste pozvaný na registráciu v Kanboard.', + 'Click here to join your team' => 'Kliknite tu na vstup do tímu', + 'Invite people' => 'Pozvať', + 'Emails' => 'Emaily', + 'Enter one email address by line.' => 'Zadajte jednu emailovú adresu na riadok.', + 'Add these people to this project' => 'Pridať týchto ľudí do projektu', + 'Add this person to this project' => 'Pridať tohoto človeka do projektu', + 'Sign-up' => 'Registrovať', + 'Credentials' => 'Prihl. údaje', + 'New user' => 'Nový používateľ', + 'This username is already taken' => 'Prihlasovacie meno už je použité', + 'Your profile must have a valid email address.' => 'Váš profil musí mať platnú emailovú adresu.', + 'TRL - Turkish Lira' => 'TRL – Turecká líra', + 'The project email is optional and could be used by several plugins.' => 'Email projektu je voliteľný a môže byť použitý viacerými zásuvnými modulmi.', + 'The project email must be unique across all projects' => 'Email projektu musí byť jedinečný vo všetkých projektoch', + 'The email configuration has been disabled by the administrator.' => 'Nastavenie emailu bolo zakázané administrátorom.', + 'Close this project' => 'Ukončiť tento projekt', + 'Open this project' => 'Otvoriť tento projekt', + 'Close a project' => 'Ukončiť projekt', + 'Do you really want to close this project: "%s"?' => 'Naozaj chcete ukončiť projekt „%s”?', + 'Reopen a project' => 'Znova otvoriť projekt', + 'Do you really want to reopen this project: "%s"?' => 'Naozaj chcete znova otvoriť projekt: „%s”?', + 'This project is open' => 'Tento projekt je otvorený', + 'This project is closed' => 'Tento projekt je ukončený', + 'Unable to upload files, check the permissions of your data folder.' => 'Nemožno nahrať súbory, skontrolujte prístupové práva zložky „data”.', + 'Another category with the same name exists in this project' => 'Iná kategória s rovnakým menom existuje v toto projekte', + 'Comment sent by email successfully.' => 'Komentár úspešne odoslaný emailom.', + 'Sent by email to "%s" (%s)' => 'Poslané emailom „%s” (%s)', + 'Unable to read uploaded file.' => 'Nemožno čítať nahratý súbor.', + 'Database uploaded successfully.' => 'Databáza úspešne nahratá.', + 'Task sent by email successfully.' => 'Úloha úspešne odoslaná emailom.', + 'There is no category in this project.' => 'Tento projekt nemá kategórie.', + 'Send by email' => 'Odoslať emailom', + 'Create and send a comment by email' => 'Vytvoriť a poslať komentár emailom', + 'Subject' => 'Predmet', + 'Upload the database' => 'Nahrať databázu', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Môžete nahrať predtým stiahnutú databázu SQLite (formát Gzip).', + 'Database file' => 'Súbor databázy', + 'Upload' => 'Nahrať', + 'Your project must have at least one active swimlane.' => 'Projekt musí mať aspoň jednu aktívnu cestu.', + 'Project: %s' => 'Projekt: %s', + 'Automatic action not found: "%s"' => 'Automatická akcia nenájdená: „%s”', + '%d projects' => '%d projekty(ov)', + '%d project' => '%d projekt', + 'There is no project.' => 'Nie je tu žiadny projekt.', + 'Sort' => 'Radiť', + 'Project ID' => 'ID projektu', + 'Project name' => 'Názov projektu', + 'Public' => 'Verejný', + 'Personal' => 'Súkromný', + '%d tasks' => '%d úloh(y)', + '%d task' => '%d úloha', + 'Task ID' => 'ID úlohy', + 'Assign automatically a color when due date is expired' => 'Automaticky nastaviť farbu, po vypršaní termínu splnenia', + 'Total score in this column across all swimlanes' => 'Celkové skóre tohoto stĺpca vo všetkých dráhach', + 'HRK - Kuna' => 'HRK – Chorvátska kuna', + 'ARS - Argentine Peso' => 'ARS – Argentínske peso', + 'COP - Colombian Peso' => 'COP – Kolumbijské peso', + '%d groups' => '%d skupiny(ín)', + '%d group' => '%d skupina', + 'Group ID' => 'ID skupiny', + 'External ID' => 'Externé ID', + '%d users' => '%d používatelia(ľov)', + '%d user' => '%d používateľ', + 'Hide subtasks' => 'Skryť podúlohy', + 'Show subtasks' => 'Zobraziť podúlohy', + 'Authentication Parameters' => 'Autentifikačné parametre', + 'API Access' => 'Prístup API', + 'No users found.' => 'Nenájdení žiadny používatelia.', + 'User ID' => 'ID používateľa', + 'Notifications are activated' => 'Upozornenia sú aktivovaná', + 'Notifications are disabled' => 'Upozornenia sú zakázané', + 'User disabled' => 'Používateľ zakázaný', + '%d notifications' => '%d upozornení', + '%d notification' => '%d upozornenie', + 'There is no external integration installed.' => 'Nie je nainštalovaná žiadna externá integrácia.', + 'You are not allowed to update tasks assigned to someone else.' => 'Nemáte povolené upraviť úlohu pridelenú niekomu inému.', + 'You are not allowed to change the assignee.' => 'Nemáte dovolené zmeniť pridelenie.', + 'Task suppression is not permitted' => 'Potlačenie úlohy nie je povolené', + 'Changing assignee is not permitted' => 'Zmena pridelenia nie je dovolená', + 'Update only assigned tasks is permitted' => 'Povolená je len aktualizácia pridelených úloh', + 'Only for tasks assigned to the current user' => 'Len úlohy pridelené aktuálnemu používateľovi', + 'My projects' => 'Moje projekty', + 'You are not a member of any project.' => 'Nie ste členom žiadneho projektu.', + 'My subtasks' => 'Moje podúlohy', + '%d subtasks' => '%d podúloh(y)', + '%d subtask' => '%d podúloha', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Presun úlohy medzi týmito stĺpcami je povolený len pre úlohy pridelené aktuálnemu používateľovi', + '[DUPLICATE]' => '[DUPLIKÁT]', + 'DKK - Danish Krona' => 'DKK – Dánska koruna', + 'Remove user from group' => 'Odstrániť používateľa zo skupiny', + 'Assign the task to its creator' => 'Prideliť úlohu jej autorovi', + 'This task was sent by email to "%s" with subject "%s".' => 'Táto úloha bude odoslaná emailom „%s” s predmetom „%s”.', + 'Predefined Email Subjects' => 'Preddefinované predmety emailu', + 'Write one subject by line.' => 'Napíšte jeden predmet na riadok.', + 'Create another link' => 'Vytvoriť ďalší odkaz', + 'BRL - Brazilian Real' => 'BRL – Brazílsky real', + 'Add a new Kanboard task' => 'Pridať novú úlohu Kanboard', + 'Subtask not started' => 'Podúloha nezačatá', + 'Subtask currently in progress' => 'Podúloha v riešení', + 'Subtask completed' => 'Podúloha dokončená', + 'Subtask added successfully.' => 'Podúloha úspešne pridaná.', + '%d subtasks added successfully.' => '%d podúloh úspešne pridaných.', + 'Enter one subtask by line.' => 'Zadajte jednu podúlohu na riadok.', + 'Predefined Contents' => 'Preddefinovaný obsah', + 'Predefined contents' => 'Preddefinovaný obsah', + 'Predefined Task Description' => 'Preddefinované popisy úloh', + 'Do you really want to remove this template? "%s"' => 'Naozaj chcete odstrániť šablónu: „%s”?', + 'Add predefined task description' => 'Pridaný preddefinovaný popis úlohy', + 'Predefined Task Descriptions' => 'Preddefinované popisy úloh', + 'Template created successfully.' => 'Šablóna úspešne pridaná.', + 'Unable to create this template.' => 'Nemožno vytvoriť šablónu.', + 'Template updated successfully.' => 'Šablóna úspešne aktualizovaná.', + 'Unable to update this template.' => 'Nemožno aktualizovať šablónu.', + 'Template removed successfully.' => 'Šablóna úspešne odstránená.', + 'Unable to remove this template.' => 'Nemožno odstrániť šablónu.', + 'Template for the task description' => 'Šablóna popisu úlohy', + 'The start date is greater than the end date' => 'Dátum začiatku je až po dátume ukončenia', + 'Tags must be separated by a comma' => 'Značky musia byť oddelené čiarkou', + 'Only the task title is required' => 'Len názov úlohy je povinný', + 'Creator Username' => 'Prihl. meno autora', + 'Color Name' => 'Meno farby', + 'Column Name' => 'Meno stĺpca', + 'Swimlane Name' => 'Meno dráhy', + 'Time Estimated' => 'Očakávaný čas', + 'Time Spent' => 'Strávený čas', + 'External Link' => 'Externý odkaz', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Táto vlastnosť zapína kanály iCal, kanály RSS a verejné zobrazenie nástenky.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Zastaviť časovač všetkých podúloh, keď je úloha presunutá do iného stĺpca', + 'Subtask Title' => 'Názov podúlohy', + 'Add a subtask and activate the timer when moving a task to another column' => 'Pridať podúlohu a spustiť časovač, keď je úloha presunutá do iného stĺpca', + 'days' => 'dni', + 'minutes' => 'minúty', + 'seconds' => 'sekundy', + 'Assign automatically a color when preset start date is reached' => 'Automaticky nastaviť farbu, keď nastane prednastavený dátum začiatku', + 'Move the task to another column once a predefined start date is reached' => 'Presunúť úlohu do iného stĺpca, keď nastane prednastavený dátum začiatku', + 'This task is now linked to the task %s with the relation "%s"' => 'Táto úloha je teraz prepojená s úlohou %s so vzťahom „%s”', + 'The link with the relation "%s" to the task %s has been removed' => 'Odkaz so vzťahom „%s” na úlohu %s bol odstránený', + 'Custom Filter:' => 'Vlastný filter:', + 'Unable to find this group.' => 'Nemožno nájsť túto skupinu.', + '%s moved the task #%d to the column "%s"' => '%s presunul úlohu #%d do stĺpca „%s”', + '%s moved the task #%d to the position %d in the column "%s"' => '%s presunul úlohu #%d na pozíciu %d v stĺpci „%s”', + '%s moved the task #%d to the swimlane "%s"' => '%s presunul úlohu #%d do dráhy „%s”', + '%sh spent' => '%s h strávené', + '%sh estimated' => '%s h očakávané', + 'Select All' => 'Vybrať všetky', + 'Unselect All' => 'Zrušiť výber', + 'Apply action' => 'Použiť akciu', + 'Move selected tasks to another column or swimlane' => 'Presunúť zvolené do iného stĺpca', + 'Edit tasks in bulk' => 'Hromadná úprava úloh', + 'Choose the properties that you would like to change for the selected tasks.' => 'Zvoľte vlastnosti, ktoré chcete zmeniť vo všetkých zvolených úlohách.', + 'Configure this project' => 'Nastaviť projekt', + 'Start now' => 'Začať teraz', + '%s removed a file from the task #%d' => '%s odstránil súbor z úlohy #%d', + 'Attachment removed from task #%d: %s' => 'Príloha odstránená z úlohy #%d: %s', + 'No color' => 'Bez farby', + 'Attachment removed "%s"' => 'Príloha odstránená „%s”', + '%s removed a file from the task %s' => '%s odstránil súbor z úlohy %s', + 'Move the task to another swimlane when assigned to a user' => 'Presunúť úlohu do inej dráhy, keď je pridelená používateľovi', + 'Destination swimlane' => 'Cieľová dráha', + 'Assign a category when the task is moved to a specific swimlane' => 'Nastaviť kategóriu, keď je úloha presunutá do zadanej dráhy', + 'Move the task to another swimlane when the category is changed' => 'Presunúť úlohu do inej dráhy, keď je zmenená kategória', + 'Reorder this column by priority (ASC)' => 'Zoradiť stĺpec podľa priority (VZOST)', + 'Reorder this column by priority (DESC)' => 'Zoradiť stĺpec podľa priority (ZOST)', + 'Reorder this column by assignee and priority (ASC)' => 'Zoradiť stĺpec podľa pridelenia a priority (VZOST)', + 'Reorder this column by assignee and priority (DESC)' => 'Zoradiť stĺpec podľa pridelenia a priority (ZOST)', + 'Reorder this column by assignee (A-Z)' => 'Zoradiť stĺpec podľa pridelenia (A-Z)', + 'Reorder this column by assignee (Z-A)' => 'Zoradiť stĺpec podľa pridelenia (Z-A)', + 'Reorder this column by due date (ASC)' => 'Zoradiť stĺpec podľa termínu ukončenia (VZOST)', + 'Reorder this column by due date (DESC)' => 'Zoradiť stĺpec podľa termínu ukončenia (ZOST)', + 'Reorder this column by id (ASC)' => 'Zoradiť tento stĺpec podľa ID (vzostupne)', + 'Reorder this column by id (DESC)' => 'Zoradiť tento stĺpec podľa ID (zostupne)', + '%s moved the task #%d "%s" to the project "%s"' => '%s presunul/a úlohu č. %d „%s” do projektu „%s”', + 'Task #%d "%s" has been moved to the project "%s"' => 'Úloha č. %d „%s” bola presunutá do projektu „%s”', + 'Move the task to another column when the due date is less than a certain number of days' => 'Presunúť úlohu do iného stĺpca, keď je do termínu ukončenia menej ako zadaný počet dní', + 'Automatically update the start date when the task is moved away from a specific column' => 'Automaticky aktualizovať dátum začiatku, keď je úloha presunutá zo zadaného stĺpca', + 'HTTP Client:' => 'Klient HTTP', + 'Assigned' => 'Priadené', + 'Task limits apply to each swimlane individually' => 'Limit úloh použiť na každú dráhu samostatne', + 'Column task limits apply to each swimlane individually' => 'Limit úloh stĺpca použiť na každú dráhu samostatne', + 'Column task limits are applied to each swimlane individually' => 'Limit úloh stĺpca je použitý na každú dráhu samostatne', + 'Column task limits are applied across swimlanes' => 'Limit úloh stĺpca je použitý na všetky dráhy spolu', + 'Task limit: ' => 'Limit úloh', + 'Change to global tag' => 'Zmeniť globálnu značku', + 'Do you really want to make the tag "%s" global?' => 'Naozaj má byť značka „%s” globálna?', + 'Enable global tags for this project' => 'Zapnúť globálne značky v tomto projekte', + 'Group membership(s):' => 'Členstvo v skupine(ách)', + '%s is a member of the following group(s): %s' => '%s je členom tejto skupiny(n): %s', + '%d/%d group(s) shown' => 'zobrazených %d/%d skupíny(ín)', + 'Subtask creation or modification' => 'Vytvorenie alebo úprava podúlohy', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Prideliť úlohu zadanému používateľovi, keď je úloha presunutá do zadanej dráhy', + 'Comment' => 'Komentár', + 'Collapse vertically' => 'Zbaliť vertikálne', + 'Expand vertically' => 'Rozbaliť vertikálne', + 'MXN - Mexican Peso' => 'Mexické peso', + 'Estimated vs actual time per column' => 'Očakávaný a aktuálny čas na stĺpec', + 'HUF - Hungarian Forint' => 'HUF – Maďarský forint', + 'XBT - Bitcoin' => 'XBT – Bitcoin', + 'You must select a file to upload as your avatar!' => 'Musíte zvoliť súbor na nahratie ako svoj avatar', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'nahraný súbor nie je paltný obrázok! (Povolené sú len *.gif, *.jpg, *.jpeg a *.png!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Automaticky nastaviť termín úlohy po presunutí z určitého stĺpca', + 'No other projects found.' => 'Nenašli sa žiadne ďalšie projekty.', + 'Tasks copied successfully.' => 'Úlohy boli úspešne skopírované.', + 'Unable to copy tasks.' => 'Úlohy sa nepodarilo skopírovať.', + 'Theme' => 'Téma', + 'Theme:' => 'Téma:', + 'Light theme' => 'Svetlá téma', + 'Dark theme' => 'Tmavá téma', + 'Automatic theme - Sync with system' => 'Automatická téma – synchronizácia so systémom', + 'Application managers or more' => 'Správcovia aplikácie alebo vyššie', + 'Administrators' => 'Administrátori', + 'Visibility:' => 'Viditeľnosť:', + 'Standard users' => 'Štandardní používatelia', + 'Visibility is required' => 'Viditeľnosť je povinná', + 'The visibility should be an app role' => 'Viditeľnosť musí byť rolou v aplikácii', + 'Reply' => 'Odpovedať', + '%s wrote: ' => '%s napísal(a): ', + 'Number of visible tasks in this column and swimlane' => 'Počet viditeľných úloh v tomto stĺpci a plaveckom pruhu', + 'Number of tasks in this swimlane' => 'Počet úloh v tomto plaveckom pruhu', + 'Unable to find another subtask in progress, you can close this window.' => 'Nepodarilo sa nájsť ďalšiu podúlohu v priebehu, môžete zavrieť toto okno.', + 'This theme is invalid' => 'Táto téma je neplatná', + 'This role is invalid' => 'Táto rola je neplatná', + 'This timezone is invalid' => 'Toto časové pásmo je neplatné', + 'This language is invalid' => 'Tento jazyk je neplatný', + 'This URL is invalid' => 'Tento URL je neplatný', + 'Date format invalid' => 'Neplatný formát dátumu', + 'Time format invalid' => 'Neplatný formát času', + 'Invalid Mail transport' => 'Neplatný spôsob odosielania e-mailov', + 'Color invalid' => 'Neplatná farba', + 'This value must be greater or equal to %d' => 'Táto hodnota musí byť väčšia alebo rovná %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Pridať BOM na začiatok súboru (vyžaduje Microsoft Excel)', + 'Just add these tag(s)' => 'Len pridajte tieto značky', + 'Remove internal link(s)' => 'Odstrániť interné odkazy', + 'Import tasks from another project' => 'Importovať úlohy z iného projektu', + 'Select the project to copy tasks from' => 'Vyberte projekt, z ktorého chcete kopírovať úlohy', + 'The total maximum allowed attachments size is %sB.' => 'Celková maximálna povolená veľkosť príloh je %sB.', + 'Add attachments' => 'Pridať prílohy', + 'Task #%d "%s" is overdue' => 'Úloha #%d „%s” je po termíne', + 'Enable notifications by default for all new users' => 'Predvolene zapnúť upozornenia pre všetkých nových používateľov', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Priradiť úlohu jej tvorcovi pre vybrané stĺpce, ak nie je ručne nastavený riešiteľ', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Priradiť úlohu prihlásenému používateľovi pri presune do zadaného stĺpca, ak nie je priradený žiadny používateľ', +]; diff --git a/app/Locale/sr_Latn_RS/translations.php b/app/Locale/sr_Latn_RS/translations.php new file mode 100644 index 0000000..4832759 --- /dev/null +++ b/app/Locale/sr_Latn_RS/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => '.', + 'None' => 'Nijedan', + 'Edit' => 'Izmeni', + 'Remove' => 'Ukloni', + 'Yes' => 'Da', + 'No' => 'Ne', + 'cancel' => 'odustani', + 'or' => 'ili', + 'Yellow' => 'Žuta', + 'Blue' => 'Plava', + 'Green' => 'Zelena', + 'Purple' => 'Ljubičasta', + 'Red' => 'Crvena', + 'Orange' => 'Narandžasta', + 'Grey' => 'Siva', + 'Brown' => 'Braon', + 'Deep Orange' => 'Tamno naradžasta', + 'Dark Grey' => 'Tamno siva', + 'Pink' => 'Roze', + 'Teal' => 'Tirkizna', + 'Cyan' => 'Cijan', + 'Lime' => 'Limeta', + 'Light Green' => 'Svetlo zelena', + 'Amber' => 'Ćilibar', + '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 želite da uklonite korisnika: "%s"?', + 'All users' => 'Svi korisnici', + 'Username' => 'Korisnik', + 'Password' => 'Lozinka', + 'Administrator' => 'Administrator', + 'Sign in' => 'Prijava', + 'Users' => 'Korisnici', + 'Forbidden' => 'Zabranjeno', + 'Access Forbidden' => 'Zabranjen pristup', + 'Edit user' => 'Izmeni korisnika', + 'Logout' => 'Odjava', + 'Bad username or password' => 'Loše korisničko ime ili lozinka', + 'Edit project' => 'Izmeni projekat', + 'Name' => 'Naziv', + 'Projects' => 'Projekti', + 'No project' => 'Bez projekta', + 'Project' => 'Projekat', + 'Status' => 'Status', + 'Tasks' => 'Zadatak', + 'Board' => 'Tabla', + 'Actions' => 'Akcije', + 'Inactive' => 'Neaktivno', + 'Active' => 'Aktivno', + 'Unable to update this board.' => 'Ažuriranje ove table nije uspelo', + 'Disable' => 'Onemogući', + 'Enable' => 'Omogući', + 'New project' => 'Novi projekat', + 'Do you really want to remove this project: "%s"?' => 'Da li zaista želite da uklonite projekat: "%s"?', + 'Remove project' => 'Ukloni projekat', + 'Edit the board for "%s"' => 'Izmeni tablu za "%s"', + 'Add a new column' => 'Dodaj novu kolonu', + 'Title' => 'Naslov', + 'Assigned to %s' => 'Dodeljen korisniku %s', + 'Remove a column' => 'Ukloni kolonu', + 'Unable to remove this column.' => 'Nije moguće ukloniti ovu kolonu.', + 'Do you really want to remove this column: "%s"?' => 'Da li zaista želite da uklonite ovu kolonu: "%s"?', + 'Settings' => 'Podešavanja', + 'Application settings' => 'Podešavanja aplikacije', + 'Language' => 'Jezik', + 'Webhook token:' => 'Webhook token :', + 'API token:' => 'API token', + 'Database size:' => 'Veličina baze :', + 'Download the database' => 'Preuzmi bazu', + 'Optimize the database' => 'Optimizuj bazu', + '(VACUUM command)' => '(VACUUM komanda)', + '(Gzip compressed Sqlite file)' => '(Sqlite baza spakovana Gzip-om)', + 'Close a task' => 'Zatvori zadatak', + 'Column' => 'Kolona', + 'Color' => 'Boja', + 'Assignee' => 'Izvršilac', + '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 želite da otvorite ovaj zadatak: "%s"?', + 'Back to the board' => 'Nazad na tablu', + 'There is nobody assigned' => 'Nikome nije dodeljeno', + 'Column on the board:' => 'Kolona na tabli:', + '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 celobrojna vrednost', + 'The username must be unique' => 'Korisničko ime mora biti jedinstveno', + '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 project is required' => 'Projekat je obavezan', + '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', + 'The title is required' => 'Naslov je obavezan', + 'Settings saved successfully.' => 'Podešavanja uspešno snimljena.', + 'Unable to save your settings.' => 'Nije mogućue sačuvati podešavanja.', + 'Database optimization done.' => 'Optimizacija baze je završena.', + 'Your project has been created successfully.' => 'Projekat je uspešno napravljen.', + 'Unable to create your project.' => 'Nije mogućue kreirati projekat.', + 'Project updated successfully.' => 'Projekat je uspešno ažuriran.', + 'Unable to update this project.' => 'Nije mogućue ažurirati ovaj projekat.', + 'Unable to remove this project.' => 'Nije mogućue ukloniti ovaj projekat.', + 'Project removed successfully.' => 'Projekat uspešno uklonjen.', + 'Project activated successfully.' => 'Projekat uspešno aktiviran.', + 'Unable to activate this project.' => 'Nije mogućue aktivirti ovaj projekat.', + 'Project disabled successfully.' => 'Projekat uspešno deaktiviran.', + 'Unable to disable this project.' => 'Nije moguće onemogućiti ovaj projekat.', + 'Unable to open this task.' => 'Nije moguće otvoriti ovaj zadatak', + 'Task opened successfully.' => 'Zadatak uspešno otvoren.', + 'Unable to close this task.' => 'Nije moguće zatvoriti ovaj zadatak.', + '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.', + '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:', + 'Id' => 'Id', + 'Public link' => 'Javni link', + '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', + 'Task limit' => 'Ograničenje zadatka', + 'Task count' => 'Broj zadataka', + 'User' => 'Korisnik', + 'Comments' => 'Komentari', + 'Comment is required' => 'Komentar je obavezan', + 'Comment added successfully.' => 'Komentar uspešno ostavljen', + 'Unable to create your comment.' => 'Nemoguće kreiranje komentara', + 'Due Date' => 'Rok završetka', + 'Invalid date' => 'Loš datum', + 'Automatic actions' => 'Automatske akcije', + 'Your automatic action has 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"' => 'Akcije za automatizaciju projekta "%s"', + 'Add an action' => 'dodaj akciju', + 'Event name' => 'Naziv događaja', + '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', + 'Do you really want to remove this action: "%s"?' => 'Da li da obrišem akciju "%s"?', + 'Remove an automatic action' => 'Obriši automatsku akciju', + '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', + 'Task modification' => 'Izmena zadatka', + 'Task creation' => 'Kreiranje zadatka', + 'Closing a task' => 'Zatvaranja zadatka', + 'Assign a color to a specific user' => 'Dodeli boju korisniku', + 'Position' => 'Pozicija', + 'Duplicate to project' => 'Kopiraj u drugi projekat', + 'Duplicate' => 'Napravi kopiju', + 'Link' => 'Link', + '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?', + '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', + 'Unknown' => 'Nepoznat', + 'Last logins' => 'Poslednja prijava', + 'Login date' => 'Datum prijave', + 'Authentication method' => 'Metod autentifikacije', + 'IP address' => 'IP adresa', + 'User agent' => 'Pregledač', + 'Persistent connections' => 'Stalna konekcija', + 'No session.' => 'Bez sesije', + 'Expiration date' => 'Ističe', + 'Remember Me' => 'Zapamti me', + 'Creation date' => 'Datum kreiranja', + 'Everybody' => 'Svi', + 'Open' => 'Otvoreni', + 'Closed' => 'Zatvoreni', + 'Search' => 'Traži', + 'Nothing found.' => 'Ništa nije pronađeno', + 'Due date' => 'Rok završetka', + 'Description' => 'Opis', + '%d comments' => '%d komentara', + '%d comment' => '%d komentar', + 'Email address invalid' => 'Email adresa nije validna', + 'Your external account is not linked anymore to your profile.' => 'Vaš spoljni nalog više nije povezan sa vašim profilom.', + 'Unable to unlink your external account.' => 'Nije moguće prekinuti vezu sa spoljnim nalogom.', + 'External authentication failed' => 'Spoljna autentifikacija nije uspela', + 'Your external account is linked to your profile successfully.' => 'Vaš spoljni nalog je uspešno povezan sa vašim profilom.', + 'Email' => 'E-mail', + 'Task removed successfully.' => 'Zadatak uspešno uklonjen.', + 'Unable to remove this task.' => 'Nije moguće ukoliniti zadatak.', + 'Remove a task' => 'Ukloni zadatak', + 'Do you really want to remove this task: "%s"?' => 'Da li zaista želite da uklonite ovaj 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', + 'Your category has been created successfully.' => 'Uspešno kreirana kategorija.', + 'This category has been updated successfully.' => 'Kategorija je uspešno izmenjena', + 'Unable to update this category.' => 'Nije moguć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', + 'Add a new category' => 'Dodaj novu kategoriju', + 'Do you really want to remove this category: "%s"?' => 'Da li zaista želiš da ukloniš kategoriju: "%s"?', + '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' => 'Priloži dokument', + 'Do you really want to remove this file: "%s"?' => 'Da li zaista želite da ukolnite fajl: "%s"?', + 'Attachments' => 'Prilozi', + 'Edit the task' => 'Izmena zadatka', + '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 zaista želite da uklonite podzadatak?', + 'Remaining:' => 'Preostalo:', + 'hours' => 'sati', + 'estimated' => 'procenjeno', + 'Sub-Tasks' => 'Podzadaci', + 'Add a sub-task' => 'Dodaj podzadatak', + 'Original estimate' => 'Originalna procena', + 'Create another sub-task' => 'Dodaj novi podzadatak', + 'Time spent' => 'Utrošeno vreme', + 'Edit a sub-task' => 'Izmeni podzadatak', + 'Remove a sub-task' => 'Ukloni podzadatak', + 'The time must be a numeric value' => 'Vreme mora biti numerička vrednost', + 'Todo' => 'Uraditi', + 'In progress' => 'U radu', + 'Sub-task removed successfully.' => 'Podzadatak uspešno uklonjen.', + 'Unable to remove this sub-task.' => 'Nie moguće ukloniti ovaj podzadatak', + 'Sub-task updated successfully.' => 'Podzadatak uspešno ažuriran', + 'Unable to update your sub-task.' => 'Ažuriranje podzadatka nije uspelo.', + 'Unable to create your sub-task.' => 'Nije moguće kreirati podzadatak.', + 'Maximum size: ' => 'Maksimalna veličina: ', + 'Display another project' => 'Prikaži drugi projekat', + 'Created by %s' => 'Napravio/la %s', + 'Tasks Export' => 'Izvoz zadataka', + 'Start Date' => 'Početni datum', + 'Execute' => 'Izvrši', + 'Task Id' => 'Identifikator zadatka', + 'Creator' => 'Autor', + 'Modification date' => 'Datum izmene', + 'Completion date' => 'Datum kompletiranja', + 'Clone' => 'Kloniraj', + 'Project cloned successfully.' => 'Projekat uspešno kloniran.', + 'Unable to clone this project.' => 'Nije moguće klonirati projekat.', + 'Enable email notifications' => 'Omogući obaveštenja email-om', + 'Task position:' => 'Pozicija zadatka:', + 'The task #%d has been opened.' => 'Zadatak #%d je otvoren.', + 'The task #%d has been closed.' => 'Zadatak #%d je zatvoren.', + 'Sub-task updated' => 'Podzadatak izmenjen', + 'Title:' => 'Naslov:', + 'Status:' => 'Status', + 'Assignee:' => 'Izvršilac:', + 'Time tracking:' => 'Praćenje vremena: ', + 'New sub-task' => 'Novi podzadatak', + 'New attachment added "%s"' => 'Novi prilog ubačen "%s"', + 'New comment posted by %s' => 'Novi komentar ostavio %s', + 'New comment' => 'Novi komentar', + 'Comment updated' => 'Komentar ažuriran', + 'New subtask' => 'Novi podzadatak', + 'I only want to receive notifications for these projects:' => 'Želim obaveštenja samo za projekate:', + 'view the task on Kanboard' => 'pregled zadataka u Kanboard-u', + 'Public access' => 'Javni pristup', + 'Disable public access' => 'Zabrani javni pristup', + 'Enable public access' => 'Dozvoli javni pristup', + 'Public access disabled' => 'Javni pristup onemogućen!', + 'Move the task to another project' => 'Premesti zadatak u drugi projekat', + 'Move to project' => 'Premesti u drugi projekat', + 'Do you really want to duplicate this task?' => 'Da li zaista želite da kopirate ovaj zadatak?', + 'Duplicate a task' => 'Kopiraj zadatak', + 'External accounts' => 'Spoljni nalozi', + 'Account type' => 'Tip naloga', + 'Local' => 'Lokalno', + 'Remote' => 'Udaljno', + 'Enabled' => 'Omogućeno', + 'Disabled' => 'Onemogućeno', + 'Login:' => 'Prijava:', + 'Full Name:' => 'Ime i prezime', + 'Email:' => 'Email: ', + 'Notifications:' => 'Obaveštenja: ', + 'Notifications' => 'Obaveštenja', + 'Account type:' => 'Vrsta naloga:', + 'Edit profile' => 'Izmeni profil', + 'Change password' => 'Izmeni lozinku', + 'Password modification' => 'Izmena lozinke', + 'External authentications' => 'Spoljne akcije', + 'Never connected.' => 'Nikad povezan.', + 'No external authentication enabled.' => 'Nema omogućenih spoljnih autentifikacija.', + 'Password modified successfully.' => 'Uspešna izmena lozinke.', + 'Unable to change the password.' => 'Nije moguće izmeniti lozinku.', + 'Change category' => 'Izmeni kategoriju', + '%s updated the task %s' => '%s je ažurirao/la zadatak %s', + '%s opened the task %s' => '%s je otvorio/la zadatak %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s je premestio/la zadatak %s na poziciju #%d u koloni "%s"', + '%s moved the task %s to the column "%s"' => '%s je premestio/la zadatak %s u kolonu "%s"', + '%s created the task %s' => '%s je kreirao/la zadatak %s', + '%s closed the task %s' => '%s je zatvorio/la zadatak %s', + '%s created a subtask for the task %s' => '%s je kreirao/la podzadatak za zadatka %s', + '%s updated a subtask for the task %s' => '%s je izmenio/la podzadatak za zadatka %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Dodeljen korisniku %s uz procenu vremena %s/%sh', + 'Not assigned, estimate of %sh' => 'Nedodeljen, procenjeno vreme %sh', + '%s updated a comment on the task %s' => '%s je ažurirao/la komentar za zadatka %s', + '%s commented the task %s' => '%s je komentarisao/la zadatak %s', + '%s\'s activity' => '%s - aktivnosti', + 'RSS feed' => 'RSS kanal', + '%s updated a comment on the task #%d' => '%s ažurirao/la komentar za zadatka #%d', + '%s commented on the task #%d' => '%s komentarisao/la zadatak #%d', + '%s updated a subtask for the task #%d' => '%s ažurirao/la podzadatak za zadatka #%d', + '%s created a subtask for the task #%d' => '%s kreirao/la podzadatak za zadatka #%d', + '%s updated the task #%d' => '%s ažurirao/la zadatak #%d', + '%s created the task #%d' => '%s kreirao/la zadatak #%d', + '%s closed the task #%d' => '%s zatvorio/la zadatak #%d', + '%s opened the task #%d' => '%s otvorio/la zadatak #%d', + 'Activity' => 'Aktivnosti', + 'Default values are "%s"' => 'Podrazumevane vrednosti su: "%s"', + 'Default columns for new projects (Comma-separated)' => 'Podrazumevane kolone za novi projekat (odvojeno zarezom)', + 'Task assignee change' => 'Promena izvršioca zadatka', + '%s changed the assignee of the task #%d to %s' => '%s je promenio/la izvršioca zadataka #%d na %s', + '%s changed the assignee of the task %s to %s' => '%s je promenio/la izvršioca zadatak %s na %s', + 'New password for the user "%s"' => 'Nova lozinka za korisnika "%s"', + 'Choose an event' => 'Izaberi događaj', + 'Create a task from an external provider' => 'Napravi zadatak preko posrednika', + 'Change the assignee based on an external username' => 'Promeni izvršioca bazirano na spoljnom korisničkom imenu', + 'Change the category based on an external label' => 'Promeni kategoriju bazirano na spoljnoj etiketi', + 'Reference' => 'Referenca', + 'Label' => 'Etikieta', + 'Database' => 'Baza', + 'About' => 'Informacje', + 'Database driver:' => 'Drajver baze podataka:', + 'Board settings' => 'Podešavanje table', + 'Webhook settings' => 'Webhook podešavanja', + 'Reset token' => 'Resetuj token', + 'API endpoint:' => 'Krajnja tačka API-a:', + 'Refresh interval for personal board' => 'Interval osvežavanja ličnih tabli', + 'Refresh interval for public board' => 'Interval osvežavanja javnih tabli', + 'Task highlight period' => 'Period naznačavanja zadatka', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Period (u sekundama) za koji se smatra da je bilo izmena na zadatku (0 je onemogućeno, 2 dana je podrazumevano)', + 'Frequency in second (60 seconds by default)' => 'Učestalost u sekundama (60 je podrazumevano)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Učestalost u sekundama (0 gasi ovu funkecionalnost, 10 je podrazumevano)', + 'Application URL' => 'URL adresa aplikacije', + 'Token regenerated.' => 'Token regenerisan', + 'Date format' => 'Format datuma', + 'ISO format is always accepted, example: "%s" and "%s"' => 'ISO format je uvek prihvatljiv, primer: "%s", "%s"', + 'New personal project' => 'Novi lični projekat', + 'This project is personal' => 'Ovaj projekat je ličan', + 'Add' => 'Dodaj', + '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' => 'Komandna tabla', + 'Confirmation' => 'Potvrda', + 'Webhooks' => 'Webhook-ovi', + 'API' => 'API', + 'Create a comment from an external provider' => 'Napravi komentar putem spoljnog posrednika', + 'Project management' => 'Uređivanje projekata', + 'Columns' => 'Kolone', + 'Task' => 'Zadatak', + 'Percentage' => 'Procenat', + 'Number of tasks' => 'Broj zadataka', + 'Task distribution' => 'Podela zadataka', + 'Analytics' => 'Analiza', + 'Subtask' => 'Podzadatak', + 'User repartition' => 'Zaduženja korisnika', + 'Clone this project' => 'Kloniraj projekat', + 'Column removed successfully.' => 'Kolona usepšno uklonjena.', + 'Not enough data to show the graph.' => 'Nedovoljno podataka za grafikon.', + 'Previous' => 'Prethodni', + 'The id must be an integer' => 'ID mora biti celobrojna vrednost', + 'The project id must be an integer' => 'ID projekta mora biti celobrojna vrednost', + 'The status must be an integer' => 'Status mora biti celobrojna vrednost', + 'The subtask id is required' => 'ID podzadataka je obavezan', + 'The subtask id must be an integer' => 'ID podzadatka mora biti celobrojna vrednost', + 'The task id is required' => 'ID zadatka je obavezan', + 'The task id must be an integer' => 'ID zadatka mora biti celobrojna vrednost', + 'The user id must be an integer' => 'ID korisnika mora biti celobrojna vrednost', + 'This value is required' => 'Ova vrednost je obavezna', + 'This value must be numeric' => 'Ova vrednost mora biti broj', + 'Unable to create this task.' => 'Nije moguće kreirati ovaj zadatak.', + 'Cumulative flow diagram' => 'Kumulativni dijagram toka', + 'Daily project summary' => 'Zbirni pregled po danima', + 'Daily project summary export' => 'Izvoz zbirnog pregleda po danima', + 'Exports' => 'Izvozi', + 'This export contains the number of tasks per column grouped per day.' => 'Ovaj izvoz sadrži broj zadataka po koloni grupisano po danima.', + 'Active swimlanes' => 'Aktivna staza', + 'Add a new swimlane' => 'Dodaj novu stazu', + 'Default swimlane' => 'Podrazumevana staza', + 'Do you really want to remove this swimlane: "%s"?' => 'Da li zasita želite da da uklonite stazu: "%s"?', + 'Inactive swimlanes' => 'Neaktivne staze', + 'Remove a swimlane' => 'Ukloni stazu', + 'Swimlane modification for the project "%s"' => 'Izmena staze za projekat "%s"', + 'Swimlane removed successfully.' => 'Staza uspešno uklonjen.', + 'Swimlanes' => 'Staze', + 'Swimlane updated successfully.' => 'Staza uspešno ažuriran.', + 'Unable to remove this swimlane.' => 'Nije moguće ukloniti ovu stazu', + 'Unable to update this swimlane.' => 'Nije moguće ažuriranje ove staze', + 'Your swimlane has been created successfully.' => 'Vaša staza je uspešno napravljena.', + 'Example: "Bug, Feature Request, Improvement"' => 'Na primer: "Greška, Zahtev za funkcionalnost, Poboljšanje"', + 'Default categories for new projects (Comma-separated)' => 'Podrazumevane kategorije za nove projekate', + 'Integrations' => 'Integracje', + 'Integration with third-party services' => 'Integracja sa uslugama spoljnih servisa', + 'Subtask Id' => 'ID podzadatka', + 'Subtasks' => 'Podzadaci', + 'Subtasks Export' => 'Izvoz podzdataka', + 'Task Title' => 'Naslov zadatka', + 'Untitled' => 'Bez naslova', + 'Application default' => 'Aplikacijski podrazumevano', + 'Language:' => 'Jezik:', + 'Timezone:' => 'Vremenska zona:', + 'All columns' => 'Sve kolone', + 'Next' => 'Sledeće', + '#%d' => '#%d', + 'All swimlanes' => 'Sve staze', + 'All colors' => 'Sve boje', + 'Moved to column %s' => 'Premešten u kolonu %s', + 'User dashboard' => 'Korisnička komandna tabla', + 'Allow only one subtask in progress at the same time for a user' => 'Dozvoli samo jedan podzadatak "u radu" po korisniku', + 'Edit column "%s"' => 'Uredi kolonu "%s"', + 'Select the new status of the subtask: "%s"' => 'Izaberi novi status za podzadatak: "%s"', + 'Subtask timesheet' => 'Tabela vremena za podzadatke', + 'There is nothing to show.' => 'Nema podataka', + 'Time Tracking' => 'Praćenje vremena', + 'You already have one subtask in progress' => 'Već imate jedan podzadatak "u radu"', + 'Which parts of the project do you want to duplicate?' => 'Koje delove projekta želite da duplirate', + 'Disallow login form' => 'Zabrani prijavnu formu', + 'Start' => 'Početak', + 'End' => 'Kraj', + 'Task age in days' => 'Starost zadatka u danima', + 'Days in this column' => 'Dana u ovoj koloni', + '%dd' => '%dd', + 'Add a new link' => 'Dodaj novu vezu', + 'Do you really want to remove this link: "%s"?' => 'Da li zaista želite da uklonite ovu vezu: "%s"?', + 'Do you really want to remove this link with task #%d?' => 'Da li zaista želite da uklonite ovu vezu sa zadatkom #%d?', + 'Field required' => 'Polje je obavezno', + 'Link added successfully.' => 'Veza je uspešno dodata.', + 'Link updated successfully.' => 'Veza je uspešno ažurirana.', + 'Link removed successfully.' => 'Veza je uspešno uklonjena.', + 'Link labels' => 'Veza etiketa', + 'Link modification' => 'Veza modifikacija', + 'Opposite label' => 'Suprotna etiketa', + 'Remove a link' => 'Ukloni vezu', + 'The labels must be different' => 'Etikete moraju biti različite', + 'There is no link.' => 'Ne postoji veza', + 'This label must be unique' => 'Ova etiketa mora biti jedinstvena', + 'Unable to create your link.' => 'Nije moguće napraviti vezu.', + 'Unable to update your link.' => 'Nije moguće ažurirati vezu.', + 'Unable to remove this link.' => 'Nije moguće ukloniti vezu.', + 'relates to' => 'relacija sa', + 'blocks' => 'blokira', + 'is blocked by' => 'je blokiran od', + 'duplicates' => 'duplira', + 'is duplicated by' => 'je dupliran od', + 'is a child of' => 'je dete od', + 'is a parent of' => 'je roditelj od', + 'targets milestone' => 'cilj prekretnice', + 'is a milestone of' => 'je od prekretnice', + 'fixes' => 'popravlja', + 'is fixed by' => 'je popravljen od', + 'This task' => 'Ovaj zadatak', + '<1h' => '<1h', + '%dh' => '%dh', + 'Expand tasks' => 'Proširi zadatke', + 'Collapse tasks' => 'Skupi zadatke', + 'Expand/collapse tasks' => 'Proširi/skupi zadatke', + 'Close dialog box' => 'Skupi dijalog', + 'Submit a form' => 'Pošalji obrazac', + 'Board view' => 'Pregled table', + 'Keyboard shortcuts' => 'Prečice tastature', + 'Open board switcher' => 'Otvori prekidače table', + 'Application' => 'Aplikacija', + 'Compact view' => 'Kompaktan pregled', + 'Horizontal scrolling' => 'Horizontalno listanje', + 'Compact/wide view' => 'Skupi/raširi pregled', + 'Currency' => 'Valuta', + 'Personal project' => 'Lični projekat', + 'AUD - Australian Dollar' => 'AUD - Australijski dolar', + 'CAD - Canadian Dollar' => 'CAD - Kanadski dolar', + 'CHF - Swiss Francs' => 'CHF - Švajcarski franak', + 'Custom Stylesheet' => 'Prilagođeni stil', + 'EUR - Euro' => 'EUR - Euro', + 'GBP - British Pound' => 'GBP - Britanska funta', + 'INR - Indian Rupee' => 'INR - Indijski rupi', + 'JPY - Japanese Yen' => 'JPY - Japanski jen', + 'NZD - New Zealand Dollar' => 'NZD - Novozelandski dolar', + 'PEN - Peruvian Sol' => 'PEN - peruanski sol', + 'RSD - Serbian dinar' => 'RSD - Srpski dinar', + 'CNY - Chinese Yuan' => 'CNY - Kineski jen', + 'USD - US Dollar' => 'USD - Američki dolar', + 'VES - Venezuelan Bolívar' => 'VES - Venecuelanski bolivar', + 'Destination column' => 'Odredišna kolona', + 'Move the task to another column when assigned to a user' => 'Premesti zadatak u neku drugu kolonu kada se dodeli izvršiocu', + 'Move the task to another column when assignee is cleared' => 'Premesti zadatak u neku drugu kolonu kada se ukloni izvršiocu', + 'Source column' => 'Izvorna kolona', + 'Transitions' => 'Prelaz', + 'Executer' => 'Izvršilac', + 'Time spent in the column' => 'Vreme provedeno u koloni', + 'Task transitions' => 'Prelazi zadatka', + 'Task transitions export' => 'Izvezi prelaze zadatka', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Ovaj izveštaj sadrži sva premeštanja unutar kolona za svaki zadatak sa datumom, korisnika i utrošeno vreme za svaki premeštaj.', + 'Currency rates' => 'Stopa valute', + 'Rate' => 'Stopa', + 'Change reference currency' => 'Promeni referentnu valutu', + 'Reference currency' => 'Referentna valuta', + 'The currency rate has been added successfully.' => 'Stopa valute je uspešno dodata.', + 'Unable to add this currency rate.' => 'Nije moguće dodati ovu stopu valute.', + 'Webhook URL' => 'Webhook URL', + '%s removed the assignee of the task %s' => '%s je uklonio/la izvršioca zadatka %s', + 'Information' => 'Informacije', + 'Check two factor authentication code' => 'Proverite kod za dvofaktorsku autentifikaciju', + 'The two factor authentication code is not valid.' => 'Kod dvofaktorsku autentifikaciju nije važeći.', + 'The two factor authentication code is valid.' => 'Kod za dvofaktorsku autentifikaciju je važeći', + 'Code' => 'Kod', + 'Two factor authentication' => 'Dvofaktorska autentifikacija', + 'This QR code contains the key URI: ' => 'Ovaj QR kod sadrži ključni URL:', + 'Check my code' => 'Proveri moj kod', + 'Secret key: ' => 'Tajni ključ: ', + 'Test your device' => 'Testiraj svoj uređaj', + 'Assign a color when the task is moved to a specific column' => 'Dodeli boju kada je zadatak pomeren u odabranu kolonu', + '%s via Kanboard' => '%s putem Kanboard-a', + 'Burndown chart' => 'Preostalo posla / vreme', + 'This chart show the task complexity over the time (Work Remaining).' => 'Ovaj grafikon pokazuje kompleksnost zadatka tokom vremena (Preostalo posla)', + 'Screenshot taken %s' => 'Slika ekrana uzeta %s', + 'Add a screenshot' => 'Dodaj sliku ekrana', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Uzmi sliku ekrana i pritisni CTRL+V ili ⌘+V da zalepiš ovde.', + 'Screenshot uploaded successfully.' => 'Slika ekrana uspešno dodata.', + 'SEK - Swedish Krona' => 'SEK - Švedska kruna', + 'Identifier' => 'Identifikator', + 'Disable two factor authentication' => 'Onemogućite dvofaktorsku autnetifikaciju', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Da li zaista želite da onemogućite dvofaktorsku autentifikaciju za ovog korisnika: "%s"?', + 'Edit link' => 'Uredi vezu', + 'Start to type task title...' => 'Počni pisanje naslova zadatka...', + 'A task cannot be linked to itself' => 'Zadatak ne može biti povezan sa samim sobom', + 'The exact same link already exists' => 'Identična veza već postoji', + 'Recurrent task is scheduled to be generated' => 'Ponavljajući zadatak je pripremljen da bude kreiran', + 'Score' => 'Ocena', + 'The identifier must be unique' => 'Identifikator mora biti jedinstven', + 'This linked task id doesn\'t exists' => 'Povezani ID zadatka ne postoji', + 'This value must be alphanumeric' => 'Ova vrednost mora biti alfanumerička', + 'Edit recurrence' => 'Izmena ponavljanja', + 'Generate recurrent task' => 'Napravi ponavljajući zadatak', + 'Trigger to generate recurrent task' => 'Okidač koji pravi ponavljajući zadatak', + 'Factor to calculate new due date' => 'Faktor za računanje novog roka završetka', + 'Timeframe to calculate new due date' => 'Vremenski okvir za računanje novog roka završetka', + 'Base date to calculate new due date' => 'Početni datum za računanje novog roka završetka', + 'Action date' => 'Datum akcije', + 'Base date to calculate new due date: ' => 'Početni datum za računanje novog roka završetka: ', + 'This task has created this child task: ' => 'Ovaj zadatak je napravio dete zadatka: ', + 'Day(s)' => 'Dan/i', + 'Existing due date' => 'Postojeći rok završetka', + 'Factor to calculate new due date: ' => 'Faktor za računanje novog roka završetka: ', + 'Month(s)' => 'Mesec(i)', + 'This task has been created by: ' => 'Ovaj zadatak je napravio: ', + 'Recurrent task has been generated:' => 'Ponavljajući zadatak je napravio:', + 'Timeframe to calculate new due date: ' => 'Vremenski okvir za računanje novog roka završetka:', + 'Trigger to generate recurrent task: ' => 'Okidač za pravljenje ponavljajućeg zadatka', + 'When task is closed' => 'Kada je zadatak zatvoren', + 'When task is moved from first column' => 'Kada je zadatak premešten iz prve kolone', + 'When task is moved to last column' => 'Kada je zadatak premešten u poslednju kolonu', + 'Year(s)' => 'Godina/e', + 'Project settings' => 'Postavke projekta', + 'Automatically update the start date' => 'Automatski ažuriraj početni datum', + 'iCal feed' => 'iCal kanal', + 'Preferences' => 'Postavke', + 'Security' => 'Sigurnost', + 'Two factor authentication disabled' => 'Dvofaktorska autentifikacija je onemogućena', + 'Two factor authentication enabled' => 'Dvofaktorska autentifikacija je omogućena', + 'Unable to update this user.' => 'Nije moguće ažurirati ovog korisnika', + 'There is no user management for personal projects.' => 'Nema mehanizma za upravljanje korisnicima kod ličnih projekata.', + 'User that will receive the email' => 'Korisnik koji će primiti email', + 'Email subject' => 'Predmet email-a', + 'Date' => 'Datum', + 'Add a comment log when moving the task between columns' => 'Dodaj komentar u dnevnik kada se zadatak premesti iz jedne kolonu u drugu', + 'Move the task to another column when the category is changed' => 'Pomeri zadatak u drugu kolonu kada je kategorija promenjena', + 'Send a task by email to someone' => 'Pošalji zadatak nekome putem email-a', + 'Reopen a task' => 'Ponovo otvori zadatak', + 'Notification' => 'Obaveštenja', + '%s moved the task #%d to the first swimlane' => '%s je premestio/la zadatak #%d u prvu stazu', + 'Swimlane' => 'Staza', + '%s moved the task %s to the first swimlane' => '%s je premestio/la zadatak %s u prvu stazu', + '%s moved the task %s to the swimlane "%s"' => '%s je premestio/la zadatak %s u stazu "%s"', + 'This report contains all subtasks information for the given date range.' => 'Ovaj izveštaj sadrži sve informacije o podzadacima u datom periodu', + 'This report contains all tasks information for the given date range.' => 'Ovaj izveštaj sadrži sve informacije o zadacima u datom periodu', + 'Project activities for %s' => 'Aktivnosti projekta za %s', + 'view the board on Kanboard' => 'pregled table na Kanboard-u', + 'The task has been moved to the first swimlane' => 'Zadatak je premešten u prvu stazu', + 'The task has been moved to another swimlane:' => 'Zadatak je premešten u drugu stazu:', + 'New title: %s' => 'Novi naslov: %s', + 'The task is not assigned anymore' => 'Ovaj zadatak više nema izvršioca', + 'New assignee: %s' => 'Novi izvršilac: %s', + 'There is no category now' => 'Sada nema kategorije', + 'New category: %s' => 'Nova kategorija: %s', + 'New color: %s' => 'Nova boja: %s', + 'New complexity: %d' => 'Nova složenost: %d', + 'The due date has been removed' => 'Rok završetka je ukloljen', + 'There is no description anymore' => 'Nema više opisa', + 'Recurrence settings has been modified' => 'Promenjene postavke za ponavljajuće zadatke', + 'Time spent changed: %sh' => 'Utrošeno vreme je promenjeno: %sh', + 'Time estimated changed: %sh' => 'Očekivano vreme je promenjeno: %sh', + 'The field "%s" has been updated' => 'Polje "%s" je ažurirano', + 'The description has been modified:' => 'Promenjen opis:', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Da li zaista želite da zatvorite zadatak "%s" kao i sve podzadatke?', + 'I want to receive notifications for:' => 'Želim da dobijam obaveštenja za:', + 'All tasks' => 'Sve zadatke', + 'Only for tasks assigned to me' => 'Samo za zadatke na kojima sam ja izvršilac', + 'Only for tasks created by me' => 'Samo za zadatke koje sam ja napravio/la', + 'Only for tasks created by me and tasks assigned to me' => 'Samo za zadatke koje sam ja napravio/la ili sam izvršilac zadatka', + '%%Y-%%m-%%d' => '%%Y-%%m-%%d', + 'Total for all columns' => 'Ukupno za sve kolone', + 'You need at least 2 days of data to show the chart.' => 'Da bi se prikazao ovaj grafik neophodno je da postoje podaci barem dva dana unazad.', + '<15m' => '<15m', + '<30m' => '<30m', + 'Stop timer' => 'Zaustavi tajmer', + 'Start timer' => 'Pokreni tajmer', + 'My activity stream' => 'Tok mojih aktivnosti', + 'Search tasks' => 'Pretraga zadataka', + 'Reset filters' => 'Resetuj filtere', + 'My tasks due tomorrow' => 'Moji zadaci koje treba završiti sutra', + 'Tasks due today' => 'Zadaci koje treba završiti danas', + 'Tasks due tomorrow' => 'Zadaci koje treba završiti sutra', + 'Tasks due yesterday' => 'Zadaci koje je trebalo završiti juče', + 'Closed tasks' => 'Zatvoreni zadaci', + 'Open tasks' => 'Otvoreni zadaci', + 'Not assigned' => 'Bez izvršioca', + 'View advanced search syntax' => 'Vidi naprednu sintaksu pretrage', + 'Overview' => 'Pregled', + 'Board/Calendar/List view' => 'Pregled Table/Kalendara/Liste', + 'Switch to the board view' => 'Prebacivanje na prikaz table', + 'Switch to the list view' => 'Prebacivanje na prikaz liste', + 'Go to the search/filter box' => 'Idite na polje za pretragu / filter', + 'There is no activity yet.' => 'Nema aktivnosti još uvek.', + 'No tasks found.' => 'Zadaci nisu pronađeni.', + 'Keyboard shortcut: "%s"' => 'Prečica tastature: "%s"', + 'List' => 'Lista', + 'Filter' => 'Filter', + 'Advanced search' => 'Napredna pretraga', + 'Example of query: ' => 'Primer za upit: ', + 'Search by project: ' => 'Pretraga po projektu: ', + 'Search by column: ' => 'Pretraga po koloni: ', + 'Search by assignee: ' => 'Pretraga po izvršiocu: ', + 'Search by color: ' => 'Pretraga po boji: ', + 'Search by category: ' => 'Pretraga po kategoriji: ', + 'Search by description: ' => 'Pretraga po opisu: ', + 'Search by due date: ' => 'Pretraga po roku završetka: ', + 'Average time spent in each column' => 'Prosek utrošenog vrmena u svakoj koloni', + 'Average time spent' => 'Prosek utrošenog vremena', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Ovaj grafikon pokazuje prosek utrošenog vremena u svakoj koloni za posljednjih %d zadataka.', + 'Average Lead and Cycle time' => 'Prosečno vreme realizacije(lead) i izvođenja(cycle)', + 'Average lead time: ' => 'Prosečno vreme realizacije: ', + 'Average cycle time: ' => 'Prosečno vreme ciklusa: ', + 'Cycle Time' => 'Vreme ciklusa', + 'Lead Time' => 'Vreme realizacije', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Ovaj grafikon prikazuje prosečno vreme realizacije i ciklusa za posljednjih %d zadataka tokom vremena.', + 'Average time into each column' => 'Prosečno vreme u svakoj koloni', + 'Lead and cycle time' => 'Vreme realizacije i ciklusa', + 'Lead time: ' => 'Vreme realizacije: ', + 'Cycle time: ' => 'Vreme ciklusa: ', + 'Time spent in each column' => 'Utrošeno vreme u svakoj koloni', + 'The lead time is the duration between the task creation and the completion.' => 'Vreme realizacije je vreme koje je proteklo između otvaranja i zatvaranja zadatka.', + 'The cycle time is the duration between the start date and the completion.' => 'Vreme ciklusa je vreme koje je proteklo između početka i završetka rada na zadatku.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Ako zadatak nije zatvoren trenutno vreme se koristi umesto datuma završetka.', + 'Set the start date automatically' => 'Automatski postavi početno vreme', + 'Edit Authentication' => 'Uredi autentifikaciju', + 'Remote user' => 'Udaljeni korisnik', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Udaljeni korisnik ne čuva lozinku u Kanboard bazi, npr: LDAP, Google i Github korisnički nalozi.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Ako ste štiklirali polje "Zabrani prijavnu formu" unos pristupnih podataka u prijavnoj formi će biti ignorisan.', + 'Default task color' => 'Podrazumijevana boja zadatka', + 'This feature does not work with all browsers.' => 'Ova funckionalnost ne radi na svim pregledačima.', + 'There is no destination project available.' => 'Nije dostupan nijedan odredišni projekat.', + 'Trigger automatically subtask time tracking' => 'Okidač za automatsko vremensko praćenje za podzadatke', + 'Include closed tasks in the cumulative flow diagram' => 'Obuhvati zatvorene zadatke u kumulativnom dijagramu toka', + 'Current swimlane: %s' => 'Trenutna staza: %s', + 'Current column: %s' => 'Trenutna kolona: %s', + 'Current category: %s' => 'Trenutna kategorija: %s', + 'no category' => 'bez kategorije', + 'Current assignee: %s' => 'Trenutni izvršilac: %s', + 'not assigned' => 'nije dodeljeno', + 'Author:' => 'Autor:', + 'contributors' => 'saradnici', + 'License:' => 'Licenca:', + 'License' => 'Licenca', + 'Enter the text below' => 'Unesite tekst ispod', + 'Start date:' => 'Početno vreme:', + 'Due date:' => 'Rok završetka:', + 'People who are project managers' => 'Osobe koji su menadžeri projekta', + 'People who are project members' => 'Osobe koje su članovi projekta', + 'NOK - Norwegian Krone' => 'NOK - Norveška kruna', + 'Show this column' => 'Prikaži ovu kolonu', + 'Hide this column' => 'Sakrij ovu kolonu', + 'End date' => 'Datum završetka', + 'Users overview' => 'Pregled korisnika', + 'Members' => 'Članovi', + 'Shared project' => 'Deljeni projekat', + 'Project managers' => 'Menadžeri projekta', + 'Projects list' => 'Lista projekata', + 'End date:' => 'Datum završetka:', + 'Change task color when using a specific task link' => 'Promeni boju zadatka kada se koristi određena veza na zadatku', + 'Task link creation or modification' => 'Veza na zadatku je napravljena ili izmenjena', + 'Milestone' => 'Prekretnica', + 'Reset the search/filter box' => 'Resetuj plje za pretragu/filtere', + 'Documentation' => 'Dokumentacija', + 'Author' => 'Autor', + 'Version' => 'Verzija', + 'Plugins' => 'Dodaci', + 'There is no plugin loaded.' => 'Nema učitanih dodataka.', + 'My notifications' => 'Moja obaveštenja', + 'Custom filters' => 'Prilagođeni filteri', + 'Your custom filter has been created successfully.' => 'Tvoj prilagođeni filter je uspešno napravljen.', + 'Unable to create your custom filter.' => 'Nije moguće napraviti prilagođeni filter.', + 'Custom filter removed successfully.' => 'Prilagođeni filter uspešno uklonjen.', + 'Unable to remove this custom filter.' => 'Nije moguće ukloniti prilagođeni filter.', + 'Edit custom filter' => 'Uredi prilagođeni filter', + 'Your custom filter has been updated successfully.' => 'Prilagođeni filter uspešno ažuriran.', + 'Unable to update custom filter.' => 'Nije moguće ažurirati prilagođeni filter', + 'Web' => 'Veb', + 'New attachment on task #%d: %s' => 'Novi priložak na zadatku #%d: %s', + 'New comment on task #%d' => 'Novi komentar na zadatku #%d', + 'Comment updated on task #%d' => 'Ažuriran komentar na zadatku #%d', + 'New subtask on task #%d' => 'Novi podzadatak na zadatku #%d', + 'Subtask updated on task #%d' => 'Podzadatak ažuriran na zadatku #%d', + 'New task #%d: %s' => 'Novi zadatak #%d: %s', + 'Task updated #%d' => 'Zadatak ažuriran #%d', + 'Task #%d closed' => 'Zadatak #%d zatvoren', + 'Task #%d opened' => 'Zadatak #%d otvoren', + 'Column changed for task #%d' => 'Promenjena kolona za zadatak #%d', + 'New position for task #%d' => 'Nova pozicija za zadatak #%d', + 'Swimlane changed for task #%d' => 'Staza je promenjena za zadatak #%d', + 'Assignee changed on task #%d' => 'Izvršilac je promenjen na zadatku #%d', + '%d overdue tasks' => '%d zadataka kasni', + 'No notification.' => 'Nema novih obaveštenja.', + 'Mark all as read' => 'Označi sve kao pročitano', + 'Mark as read' => 'Označi kao pročitano', + 'Total number of tasks in this column across all swimlanes' => 'Ukupan broj zadataka u ovoj koloni u svim stazama', + 'Collapse swimlane' => 'Skupi stazu', + 'Expand swimlane' => 'Proširi stazu', + 'Add a new filter' => 'Dodaj novi filter', + 'Share with all project members' => 'Podeli sa svim članovima projekta', + 'Shared' => 'Podeljeno', + 'Owner' => 'Vlasnik', + 'Unread notifications' => 'Nepročitana obaveštenja', + 'Notification methods:' => 'Metode obaveštenja:', + 'Unable to read your file' => 'Nije moguće pročitati datoteku', + '%d task(s) have been imported successfully.' => '%d zadataka uspešno uvezeno.', + 'Nothing has been imported!' => 'Ništa nije uvezeno!', + 'Import users from CSV file' => 'Uvezi korisnike putem CSV datoteke', + '%d user(s) have been imported successfully.' => '%d korisnika uspešno uvezeno.', + 'Comma' => 'Zarez', + 'Semi-colon' => 'Tačka i zarez', + 'Tab' => 'Tab', + 'Vertical bar' => 'Vertikalna traka', + 'Double Quote' => 'Dvostruki navodnici', + 'Single Quote' => 'Jednostruki navodnici', + '%s attached a file to the task #%d' => '%s je dodao/la novu datoteku u zadatak %d', + 'There is no column or swimlane activated in your project!' => 'Nema kolone ili aktivne staze u Vašem projektu!', + 'Append filter (instead of replacement)' => 'Dodaj filter (umesto zamene postojećeg)', + 'Append/Replace' => 'Dodaj/Zameni', + 'Append' => 'Dodaj', + 'Replace' => 'Zameni', + 'Import' => 'Uvoz', + 'Change sorting' => 'Promeni sortiranje', + 'Tasks Importation' => 'Uvoz zadataka', + 'Delimiter' => 'Delimter', + 'Enclosure' => 'Prilog', + 'CSV File' => 'CSV datoteka', + 'Instructions' => 'Uputstva', + 'Your file must use the predefined CSV format' => 'Vaša datoteka mora koristiti predefinisani CSV format', + 'Your file must be encoded in UTF-8' => 'Vaša datoteka mora koristiti UTF-8 enkodovana', + 'The first row must be the header' => 'Prvi red mora biti zaglavlje', + 'Duplicates are not verified for you' => 'Dupliranja neće biti provervana (morate sami uraditi)', + 'The due date must use the ISO format: YYYY-MM-DD' => 'Rok završetka mora biti u ISO formatu: YYYY-MM-DD', + 'Download CSV template' => 'Preuzmi CSV šablon', + 'No external integration registered.' => 'Nema registrovanih spoljnih integracija.', + 'Duplicates are not imported' => 'Duplikati nisu uveženi', + 'Usernames must be lowercase and unique' => 'Korisnička imena moraju biti napisana malim slovima i jedinstvena', + 'Passwords will be encrypted if present' => 'Lozinke (ako postoje) će biti šiforvane', + '%s attached a new file to the task %s' => '%s je dodao/la novu datoteku u zadatak %s', + 'Link type' => 'Tip veze', + 'Assign automatically a category based on a link' => 'Automatsko dodeljivanje kategorije bazirano na vezi', + 'BAM - Konvertible Mark' => 'BAM - Konvertibilna marka', + 'Assignee Username' => 'Korisničko ime izvršioca', + 'Assignee Name' => 'Ime izvršioca', + 'Groups' => 'Grupe', + 'Members of %s' => 'Članovi %s', + 'New group' => 'Nova grupa', + 'Group created successfully.' => 'Grupa uspešno kreirana.', + 'Unable to create your group.' => 'Nije moguće kreirati grupu.', + 'Edit group' => 'Uredi grupu', + 'Group updated successfully.' => 'Grupa uspešno ažurirana.', + 'Unable to update your group.' => 'Nije moguće ažurirati grupu', + 'Add group member to "%s"' => 'Dodaj člana u grupu "%s"', + 'Group member added successfully.' => 'Uspješno dodat član grupe.', + 'Unable to add group member.' => 'Nije moguće dodati člana grupi.', + 'Remove user from group "%s"' => 'Ukloni korisnika iz grupe "%s"', + 'User removed successfully from this group.' => 'Korisnik uspešno uklonjen iz grupe.', + 'Unable to remove this user from the group.' => 'Nije moguće ukloniti korisnika iz grupe.', + 'Remove group' => 'Ukloni grupu', + 'Group removed successfully.' => 'Grupa uspešno uklonjena.', + 'Unable to remove this group.' => 'Nije moguće ukloniti grupu.', + 'Project Permissions' => 'Dozvole na projektu', + 'Manager' => 'Menadžer', + 'Project Manager' => 'Menadžer projekta', + 'Project Member' => 'Član projekta', + 'Project Viewer' => 'Posmatrač projekta', + 'Your account is locked for %d minutes' => 'Tvoj korisnički nalog je zaključan narednih %d minuta', + 'Invalid captcha' => 'Pogrešan captcha', + 'The name must be unique' => 'Ime mora biti jedinstveno', + 'View all groups' => 'Pregledaj sve grupe', + 'There is no user available.' => 'Nema dostupnih korisnika.', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Da li zaista želite da uklonite korisnika "%s" iz grupe "%s"?', + 'There is no group.' => 'Nema grupe', + 'Add group member' => 'Dodaj člana grupe', + 'Do you really want to remove this group: "%s"?' => 'Da li zaista želite da ukloniti ovu grupu: "%s"?', + 'There is no user in this group.' => 'Trenutno nema korisnika u grupi.', + 'Permissions' => 'Dozvole', + 'Allowed Users' => 'Dozvoljeni korisnici', + 'No specific user has been allowed.' => 'Nijednom specifiičnom korisniku nije dozvoljeno.', + 'Role' => 'Uloge', + 'Enter user name...' => 'Unesi korisničko ime...', + 'Allowed Groups' => 'Dozvoljene grupe', + 'No group has been allowed.' => 'Nijednoj grupi nije dozvoljeno.', + 'Group' => 'Grupa', + 'Group Name' => 'Ime grupe', + 'Enter group name...' => 'Unesi ime grupe...', + 'Role:' => 'Uloga:', + 'Project members' => 'Članovi projekta', + '%s mentioned you in the task #%d' => '%s Vas je spomenuo/la u zadatku #%d', + '%s mentioned you in a comment on the task #%d' => '%s Vas je spomenuo/la u komentaru zadatka #%d', + 'You were mentioned in the task #%d' => 'Spomenuti ste u zadatku #%d', + 'You were mentioned in a comment on the task #%d' => 'Spomenuti ste u komentaru zadatka #%d', + 'Estimated hours: ' => 'Procenjeno sati:', + 'Actual hours: ' => 'Stvarno sati:', + 'Hours Spent' => 'Utrošeno sati:', + 'Hours Estimated' => 'Sati procenjeno', + 'Estimated Time' => 'Procenjeno vreme', + 'Actual Time' => 'Stvarno vreme', + 'Estimated vs actual time' => 'Procenjeno / stvarno vreme', + 'RUB - Russian Ruble' => 'RUB - Ruski rubij', + 'Assign the task to the person who does the action when the column is changed' => 'Dodeli zadatak osobi koja izvrši akciju promene kolone', + 'Close a task in a specific column' => 'Zatvori zadatak u određenoj koloni', + 'Time-based One-time Password Algorithm' => 'Algoritam jednokratne lozinke zasnovan na vremenu', + 'Two-Factor Provider: ' => 'Dvofaktorski dobavljač:', + 'Disable two-factor authentication' => 'Onemogući dvofaktorsku autentifikaciju', + 'Enable two-factor authentication' => 'Omogućite dvofaktorsku autentifikaciju', + 'There is no integration registered at the moment.' => 'Trenutno nije registrovana nijedna integracija.', + 'Password Reset for Kanboard' => 'Promena lozinke za Kanboard', + 'Forgot password?' => 'Zaboravljena lozinka?', + 'Enable "Forget Password"' => 'Omogući "Zaboravljena lozinka"', + 'Password Reset' => 'Promena lozinke', + 'New password' => 'Nova lozinka', + 'Change Password' => 'Promeni lozinku', + 'To reset your password click on this link:' => 'Da biste promenili lozinku kliknite na ovaj link:', + 'Last Password Reset' => 'Posljednja promena lozinke', + 'The password has never been reinitialized.' => 'Lozinka nije nikada menjana.', + 'Creation' => 'Napravljena', + 'Expiration' => 'Ističe', + 'Password reset history' => 'Istorija promena lozinki', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Svi zadaci kolone "%s" i staze "%s" uspešno su zatvoreni.', + 'Do you really want to close all tasks of this column?' => 'Da li zaista želite da zatvorite sve zadatke ove kolone?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d zadatak/zadaci u koloni "%s" i stazi "%s" će biti zatvoreni.', + 'Close all tasks in this column and this swimlane' => 'Zatvori sve zadatke u ovoj koloni i ovoj stazi', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Nijedan dodatak nije registrovao metodu obaveštavanja o projektu. U korisničkom profilu i dalje možete da konfigurišete pojedinačna obaveštenja.', + 'My dashboard' => 'Moja komandna tabla', + 'My profile' => 'Moj profil', + 'Project owner: ' => 'Vlasnik projekta: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'Identifikator projekta je opcion, mora biti alfanumerički, na primer: MOJPROJEKAT.', + 'Project owner' => 'Vlasnik projekta', + 'Personal projects do not have users and groups management.' => 'Lični projekti nemaju upravljanje korisnicima i grupama.', + 'There is no project member.' => 'Nema članova projekta.', + 'Priority' => 'Prioritet', + 'Task priority' => 'Prioritet zadatka', + 'General' => 'Opšte', + 'Dates' => 'Datumi', + 'Default priority' => 'Podrazumijevani prioritet', + 'Lowest priority' => 'Najmanji prioritet', + 'Highest priority' => 'Najveći prioritet', + 'Close a task when there is no activity' => 'Zatvori zadatak kada nema aktivnosti', + 'Duration in days' => 'Dužina trajanja u danima', + 'Send email when there is no activity on a task' => 'Pošalji email kada nema aktivnosti na zadatku', + 'Unable to fetch link information.' => 'Ne mogu da pribavim informacije o vezi.', + 'Daily background job for tasks' => 'Dnevni pozadinski poslovi za zadatke', + 'Auto' => 'Automatski', + 'Related' => 'Povezan', + 'Attachment' => 'Priložak', + 'Web Link' => 'Veb link', + 'External links' => 'Spoljne veze', + 'Add external link' => 'Dodaj spoljnu vezu', + 'Type' => 'Vrsta', + 'Dependency' => 'Zavisnost', + 'Add internal link' => 'Dodaj unutrašnju vezu', + 'Add a new external link' => 'Dodaj novu spoljnu vezu', + 'Edit external link' => 'Uredi spoljnu vezu', + 'External link' => 'Spoljna veza', + 'Copy and paste your link here...' => 'Kopirajte i zalepite Vašu vezu ovde...', + 'URL' => 'URL', + 'Internal links' => 'Unutrašnje veze', + 'Assign to me' => 'Dodeli meni', + 'Me' => 'Ja', + 'Do not duplicate anything' => 'Ništa ne dupliraj', + 'Projects management' => 'Upravljanje projektima', + 'Users management' => 'Upravljanje korisnicima', + 'Groups management' => 'Upravljanje grupama', + 'Create from another project' => 'Napravi iz drugog projekta', + 'open' => 'otvoreno', + 'closed' => 'zatvoreno', + 'Priority:' => 'Prioritet:', + 'Reference:' => 'Referenca:', + 'Complexity:' => 'Složenost:', + 'Swimlane:' => 'Staza:', + 'Column:' => 'Kolona:', + 'Position:' => 'Pozicija:', + 'Creator:' => 'Kreator:', + 'Time estimated:' => 'Očekivano vreme:', + '%s hours' => '%s sati', + 'Time spent:' => 'Utrošeno vreme:', + 'Created:' => 'Kreirano:', + 'Modified:' => 'Uređeno:', + 'Completed:' => 'Završeno:', + 'Started:' => 'Započeto:', + 'Moved:' => 'Pomereno:', + 'Task #%d' => 'Zadatak #%d', + 'Time format' => 'Format vremena', + 'Start date: ' => 'Početni datum:', + 'End date: ' => 'Krajnji datum:', + 'New due date: ' => 'Novi rok završetka:', + 'Start date changed: ' => 'Početni datum promenjen: ', + 'Disable personal projects' => 'Onemogući lične projekte', + 'Do you really want to remove this custom filter: "%s"?' => 'Da li zaista želite da uklonite ovaj prilagođeni filter "%s"?', + 'Remove a custom filter' => 'Ukloni prilagođeni filter', + 'User activated successfully.' => 'Korisnik uspešno aktiviran.', + 'Unable to enable this user.' => 'Nije moguće omogućiti ovog korisnika.', + 'User disabled successfully.' => 'Korisnik uspešno onemogućen.', + 'Unable to disable this user.' => 'Nije moguće onemogućiti ovog korisnika.', + 'All files have been uploaded successfully.' => 'Sve datoteke su uspešno otpremljene.', + 'The maximum allowed file size is %sB.' => 'Maksimalna dozvoljena veličina datoteke je %sB.', + 'Drag and drop your files here' => 'Prevucite i spustite Vaše datoteke ovde', + 'choose files' => 'izaberite datoteke', + 'View profile' => 'Pregledaj profil', + 'Two Factor' => 'Dva faktora', + 'Disable user' => 'Onemogući korisnika', + 'Do you really want to disable this user: "%s"?' => 'Da li zaista želite da onemogućite ovog korisnika: "%s"?', + 'Enable user' => 'Omogući korisnika', + 'Do you really want to enable this user: "%s"?' => 'Da li zaista želite omogućite ovog korisnika: "%s"?', + 'Download' => 'Preuzeto', + 'Uploaded: %s' => 'Otpremljeno: %s', + 'Size: %s' => 'Veličina: %s', + 'Uploaded by %s' => 'Otpremio %s', + 'Filename' => 'Ime datoteke', + 'Size' => 'Veličina', + 'Column created successfully.' => 'Kolona uspešno napravljena.', + 'Another column with the same name exists in the project' => 'Već postoji kolona sa istim imenom u ovom projektu.', + 'Default filters' => 'Podrazumevani filteri', + 'Your board doesn\'t have any columns!' => 'Tvoja tabla nema ni jednu kolonu!', + 'Change column position' => 'Promeni poziciju kolone', + 'Switch to the project overview' => 'Prebacivanje na pregled projekta', + 'User filters' => 'Fliteri korisnika', + 'Category filters' => 'Fliteri kategorija', + 'Upload a file' => 'Otpremi datoteku', + 'View file' => 'Pogledaj datoteku', + 'Last activity' => 'Posljednja aktivnost', + 'Change subtask position' => 'Promijeni poziciju podzadatka', + 'This value must be greater than %d' => 'Ova vrednost mora biti veća od %d', + 'Another swimlane with the same name exists in the project' => 'Već postoji staza sa istim imenom u ovom projektu', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Na primer: https://example.kanboard.org/ (koristi se za generisanje apsolutnog URL-a)', + 'Actions duplicated successfully.' => 'Akcije uspješno duplirane.', + 'Unable to duplicate actions.' => 'Nije moguće duplirati akcije.', + 'Add a new action' => 'Dodaj novu akciju', + 'Import from another project' => 'Uvezi iz drugog projekta', + 'There is no action at the moment.' => 'Trenutno nema akcija.', + 'Import actions from another project' => 'Uvezi akcije iz drugog projekta', + 'There is no available project.' => 'Trenutno nema dostupnih projekata.', + 'Local File' => 'Lokalna datoteka', + 'Configuration' => 'Konfiguracija', + 'PHP version:' => 'Verzija PHP-a:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'Verzija OS-a:', + 'Database version:' => 'Verzija baze podataka:', + 'Browser:' => 'Pregledač:', + 'Task view' => 'Pregled zadatka', + 'Edit task' => 'Uredi zadatak', + 'Edit description' => 'Uredi opis', + 'New internal link' => 'Nova unutrašnja veza', + 'Display list of keyboard shortcuts' => 'Prikaži listu prečica na tastaturi', + 'Avatar' => 'Avatar', + 'Upload my avatar image' => 'Otpremi sliku za avatar', + 'Remove my image' => 'Ukloni moju sliku', + 'The OAuth2 state parameter is invalid' => 'Parametar stanja OAuth2 je nevažeći', + 'User not found.' => 'Korisnik nije pronađen.', + 'Search in activity stream' => 'Pretraži aktivnosti', + 'My activities' => 'Moje aktivnosti', + 'Activity until yesterday' => 'Aktivnosti do juče', + 'Activity until today' => 'Aktivnosti do danas', + 'Search by creator: ' => 'Pretraga po kreatoru: ', + 'Search by creation date: ' => 'Pretraga po datumu kreiranja: ', + 'Search by task status: ' => 'Pretraga po statusu zadatka: ', + 'Search by task title: ' => 'Pretraga po naslovu zadatka: ', + 'Activity stream search' => 'Pretraga aktivnosti', + 'Projects where "%s" is manager' => 'Projekti gde je "%s" menadžer', + 'Projects where "%s" is member' => 'Projekti gde je "%s" član', + 'Open tasks assigned to "%s"' => 'Otvoreni zadaci dodeljeni "%s"', + 'Closed tasks assigned to "%s"' => 'Zatvoreni zadaci dodijeljeni "%s"', + 'Assign automatically a color based on a priority' => 'Dodeli boju automatski bazirano na prioritetu', + 'Overdue tasks for the project(s) "%s"' => 'Zadaci koji kasne za projekat/projekte "%s"', + 'Upload files' => 'Otpremi datoteke', + 'Installed Plugins' => 'Instalirani dodaci', + 'Plugin Directory' => 'Direktorijum dodataka', + 'Plugin installed successfully.' => 'Dodatak je uspešno instaliran.', + 'Plugin updated successfully.' => 'Dodatak je uspešno ažuriran.', + 'Plugin removed successfully.' => 'Dodatak uspešno uklonjen.', + 'Subtask converted to task successfully.' => 'Podzadatak uspešno pretvoren u zadatak.', + 'Unable to convert the subtask.' => 'Nije moguće pretvoriti podzadatak.', + 'Unable to extract plugin archive.' => 'Nije moguće otpakovati arhivu dodatka.', + 'Plugin not found.' => 'Dodatak nije pronađen.', + 'You don\'t have the permission to remove this plugin.' => 'Nemate dozvolu za uklanjanje ovog dodatka.', + 'Unable to download plugin archive.' => 'Nije moguće preuzeti arhivu dodatka.', + 'Unable to write temporary file for plugin.' => 'Nije moguće zapisati privremenu datoteku za dodatak.', + 'Unable to open plugin archive.' => 'Nije moguće otvoriti arhivu dodatka.', + 'There is no file in the plugin archive.' => 'Nema datoteke u arhivi dodatka.', + 'Create tasks in bulk' => 'Masovno kreiranja zadataka', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Ova Kanboard nije konfigurisan tako da podržava instalaciju dodataka kroz korisničko okruženje.', + 'There is no plugin available.' => 'Nema dostupnih dodataka.', + 'Install' => 'Instaliraj', + 'Update' => 'Ažuriraj', + 'Up to date' => 'Ažurno', + 'Not available' => 'Nije dostupno', + 'Remove plugin' => 'Ukloni dodatak', + 'Do you really want to remove this plugin: "%s"?' => 'Da li zaista želite da uklonite ovaj dodatak: "%s"?', + 'Uninstall' => 'Deinstaliraj', + 'Listing' => 'Spisak', + 'Metadata' => 'Metadata', + 'Manage projects' => 'Upravljanje projektima', + 'Convert to task' => 'Pretvori u zadatak', + 'Convert sub-task to task' => 'Pretvori podzadatak u zadatak', + 'Do you really want to convert this sub-task to a task?' => 'Da li zaista želite da pretvorite podzadatak u zadatak?', + 'My task title' => 'Naslov zadatka', + 'Enter one task by line.' => 'Unesi jedan zadatak po liniji.', + 'Number of failed login:' => 'Broj neuspešnih prijava:', + 'Account locked until:' => 'Korisnički nalog zaključan do:', + 'Email settings' => 'Postavke email-a', + 'Email sender address' => 'Email adresa pošiljaoca', + 'Email transport' => 'Transport email-a', + 'Webhook token' => 'Webhook token', + 'Project tags management' => 'Upravljanje oznakama projekta', + 'Tag created successfully.' => 'Oznaka uspešno kreirana.', + 'Unable to create this tag.' => 'Nije moguće kreirati oznaku.', + 'Tag updated successfully.' => 'Oznaka uspešno ažurirana.', + 'Unable to update this tag.' => 'Nije moguće ažurirati ovu oznaku.', + 'Tag removed successfully.' => 'Oznaka uspešno uklonjena.', + 'Unable to remove this tag.' => 'Nije moguće ukloniti ovu oznaku.', + 'Global tags management' => 'Globalno upravljanje oznakama.', + 'Tags' => 'Oznake', + 'Tags management' => 'Upravljanje oznakama', + 'Add new tag' => 'Dodaj novu oznaku', + 'Edit a tag' => 'Uredi oznaku', + 'Project tags' => 'Oznake projekta', + 'There is no specific tag for this project at the moment.' => 'Trenutno nema oznaka za ovaj projekat.', + 'Tag' => 'Oznaka', + 'Remove a tag' => 'Ukloni oznaku', + 'Do you really want to remove this tag: "%s"?' => 'Da li zaista želite da uklonite ovu oznaku: "%s"?', + 'Global tags' => 'Globalne oznake', + 'There is no global tag at the moment.' => 'Trenutno nema globalnih oznaka.', + 'This field cannot be empty' => 'Ovo polje ne može biti prazno', + 'Close a task when there is no activity in a specific column' => 'Zatvori zadatak kada nema aktivnosti u odabranoj koloni', + '%s removed a subtask for the task #%d' => '%s je uklonio/la podzadatak za zadatak #%d', + '%s removed a comment on the task #%d' => '%s je uklonio/la komentar na zadatku #%d', + 'Comment removed on task #%d' => 'Komentar ukloljen na zadatku #%d', + 'Subtask removed on task #%d' => 'Podzadatak ukloljen na zadatku #%d', + 'Hide tasks in this column in the dashboard' => 'Sakrijte zadatke u ovoj koloni na kontrolnoj tabli', + '%s removed a comment on the task %s' => '%s je uklonio/la komentar za zadatak %s', + '%s removed a subtask for the task %s' => '%s je uklonio/la podzadatak za zadatak %s', + 'Comment removed' => 'Komentar uklonjen', + 'Subtask removed' => 'Podzadatak uklonjen', + '%s set a new internal link for the task #%d' => '%s je postavio/la novu unutrašnju vezu za zadatak #%d', + '%s removed an internal link for the task #%d' => '%s je uklonio/la unutrašnju vezu za zadatak #%d', + 'A new internal link for the task #%d has been defined' => 'Nova unutrašnja veza za zadatak #%d je definisana', + 'Internal link removed for the task #%d' => 'Unutrašnja veza je uklonjena za zadatak #%d', + '%s set a new internal link for the task %s' => '%s je dodao/la novu unutrašnju vezu za zadatak %s', + '%s removed an internal link for the task %s' => '%s je uklonio/la unutrašnju vezu za zadatak %s', + 'Automatically set the due date on task creation' => 'Automatski dodaj rok završetka na kreiranom zadatku', + 'Move the task to another column when closed' => 'Pomeri zadatak u drugu kolonu kada je zatvoren', + 'Move the task to another column when not moved during a given period' => 'Pomeri zadatak u drugu kolonu kada nema pomeranja zadatka za zadati period', + 'Dashboard for %s' => 'Komandna tabla - %s', + 'Tasks overview for %s' => 'Pregled zadataka za %s', + 'Subtasks overview for %s' => 'Pregled podzadataka za %s', + 'Projects overview for %s' => 'Pregled projekata za %s', + 'Activity stream for %s' => 'Tok aktivnosti za %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Dodeli boju kada je zadatak pomeren u određenu stazu', + 'Assign a priority when the task is moved to a specific swimlane' => 'Dodeli prioritet kada je zadatak pomeren u određenu stazu', + 'User unlocked successfully.' => 'Korisnik je uspešno otključan.', + 'Unable to unlock the user.' => 'Nije moguće otključati korisnika.', + 'Move a task to another swimlane' => 'Pomeri zadatak u drugu stazu', + 'Creator Name' => 'Ime kreatora', + 'Time spent and estimated' => 'Utrošeno i procenjeno vrieme', + 'Move position' => 'Mesto pomeranja', + 'Move task to another position on the board' => 'Pomeri zadataka na drugo mesto na tabli', + 'Insert before this task' => 'Umetni pre ovog zadatka', + 'Insert after this task' => 'Umetni posle ovog zadatka', + 'Unlock this user' => 'Otključaj ovog korisnika', + 'Custom Project Roles' => 'Prilagođne uloge projekta', + 'Add a new custom role' => 'Dodaj novu prilagođenu ulogu', + 'Restrictions for the role "%s"' => 'Ograničenja za ulogu "%s"', + 'Add a new project restriction' => 'Dodaj nova ograničenja na projektu', + 'Add a new drag and drop restriction' => 'Dodaj nova "prevuci i spusti" ograničenja', + 'Add a new column restriction' => 'Dodaj nova ograničenja za kolonu', + 'Edit this role' => 'Uredi ovu ulogu', + 'Remove this role' => 'Ukloni ovu ulogu', + 'There is no restriction for this role.' => 'Nema ograničenja za ovu ulogu.', + 'Only moving task between those columns is permitted' => 'Samo između ovih kolona je dozvoljeno pomeranje', + 'Close a task in a specific column when not moved during a given period' => 'Zatvori zadatak u odabranoj koloni kada nema pomeranja za zadati period', + 'Edit columns' => 'Uredi kolone', + 'The column restriction has been created successfully.' => 'Ograničenje za kolonu su uspešno kreiranao.', + 'Unable to create this column restriction.' => 'Nemoguće kreirati ograničenja za kolonu.', + 'Column restriction removed successfully.' => 'Ograničenje za kolonu su uspešno uklonjeno.', + 'Unable to remove this restriction.' => 'Nije moguće ukloniti ovo ograničenje.', + 'Your custom project role has been created successfully.' => 'Tvoja prilagođena uloga na projekatu je uspešno kreirana.', + 'Unable to create custom project role.' => 'Nije moguće kreirati prilagođenu ulogu na projektu.', + 'Your custom project role has been updated successfully.' => 'Tvoja prilagođena uloga na projekatu je uspešno ažurirana.', + 'Unable to update custom project role.' => 'Nije moguće ažurirati prilagođenu ulogu na projektu.', + 'Custom project role removed successfully.' => 'Prilagođena uloga na projektu uspešno uklonjena.', + 'Unable to remove this project role.' => 'Nije moguće ukloniti ovu ulogu na projektu.', + 'The project restriction has been created successfully.' => 'Ograničenje na projektu uspešno kreirano.', + 'Unable to create this project restriction.' => 'Nije moguće kreirati ovo ograničenje na projektu.', + 'Project restriction removed successfully.' => 'Ograničenje na projektu uspešno uklonjeno.', + 'You cannot create tasks in this column.' => 'Ne možete kreirati zadatak u ovoj koloni.', + 'Task creation is permitted for this column' => 'Kreiranje zadatka u ovoj koloni je zabranjeno', + 'Closing or opening a task is permitted for this column' => 'Zatvaranje ili otvaranje zadatka u ovoj koloni je zabranjeno', + 'Task creation is blocked for this column' => 'Kreiranje zadatka je onemogućeno za ovu kolonu', + 'Closing or opening a task is blocked for this column' => 'Zatvaranje ili otvaranje zadatka u ovoj koloni je onemogućeno', + 'Task creation is not permitted' => 'Kreiranje zadatka nije dozvoljeno', + 'Closing or opening a task is not permitted' => 'Zatvarnaje ili otvaranje zadatka nije dozvoljeno', + 'New drag and drop restriction for the role "%s"' => 'Novo "prevuci i spusti" ograničenje za ulogu "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Osobe kojima se dodeli ova uloga će biti u mogućnosti da pomeraju zadatke samo imeđu izvorne i odredišne kolone.', + 'Remove a column restriction' => 'Ukloni ograničenje za kolonu', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Da li zaista želite da uklonite ovo ograničenje za kolonu: "%s" u "%s"?', + 'New column restriction for the role "%s"' => 'Novo ograničenje za kolonu za ulogu "%s"', + 'Rule' => 'Pravilo', + 'Do you really want to remove this column restriction?' => 'Da li zaista želite daukloniti ovo ograničenje za kolonu?', + 'Custom roles' => 'Prilagođene uloge', + 'New custom project role' => 'Nova prilagođena uloga za projekat', + 'Edit custom project role' => 'Uredi prilagođenu ulogu za projekat', + 'Remove a custom role' => 'Ukloni prilagođenu ulogu', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Da li zaista želite da uklonite ovu prilagođenu ulogu: "%s"? Sve osobe kojima je dodeljena ova uloga će postati članovi projekta.', + 'There is no custom role for this project.' => 'Nema prilagođenih uloga za ovaj projekat.', + 'New project restriction for the role "%s"' => 'Novo ograničenje na projektu za ulogu "%s"', + 'Restriction' => 'Ograničenje', + 'Remove a project restriction' => 'Ukloni ograničenje na projektu', + 'Do you really want to remove this project restriction: "%s"?' => 'Da li zaista želite da ukloniti ovo ograničenje na projektu: "%s"?', + 'Duplicate to multiple projects' => 'Dupliraj u više projekata', + 'This field is required' => 'Ovo polje je obavezno', + 'Moving a task is not permitted' => 'Pomeranje zadatka nije dozvoljeno', + 'This value must be in the range %d to %d' => 'Ova vrednost mora biti u rasponu od %d do %d', + 'You are not allowed to move this task.' => 'Nemaš dozvolu za pomeranje ovog zadatka.', + 'API User Access' => 'API Pristup korisnika', + 'Preview' => 'Pregled', + 'Write' => 'Piši', + 'Write your text in Markdown' => 'Upiši tekst u Markdown-u', + 'No personal API access token registered.' => 'Nije registrovan lični API pristupni token', + 'Your personal API access token is "%s"' => 'Vaš lični API token za pristup je: "%s"', + 'Remove your token' => 'Uklonite Vaš token', + 'Generate a new token' => 'Generiši novi token', + 'Showing %d-%d of %d' => 'Prikaži %d-%d od %d', + 'Outgoing Emails' => 'Odlazni email-ovi', + 'Add or change currency rate' => 'Dodajte ili promenite kurs valute', + 'Reference currency: %s' => 'Referentna valuta: %s', + 'Add custom filters' => 'Dodaj prilagođene filtere', + 'Export' => 'Izvezi', + 'Add link label' => 'Dodaj etiketu na vezu', + 'Incompatible Plugins' => 'Nekompatibilni dodaci', + 'Compatibility' => 'Kompatibilnost', + 'Permissions and ownership' => 'Dozvole i vlasništvo', + 'Priorities' => 'Prioriteti', + 'Close this window' => 'Zatvori ovaj prozor', + 'Unable to upload this file.' => 'Nije moguće optremiti ovu datoteku.', + 'Import tasks' => 'Uvezi zadatke', + 'Choose a project' => 'Izaberi projekat', + 'Profile' => 'Profil', + 'Application role' => 'Uloga u aplikaciji', + '%d invitations were sent.' => '%d poslato pozivnica.', + '%d invitation was sent.' => '%d pozivnica poslata.', + 'Unable to create this user.' => 'Nije moguće kreirati ovog korisnika.', + 'Kanboard Invitation' => 'Kanboard pozivnica', + 'Visible on dashboard' => 'Vidljivo na komandnoj tabli', + 'Created at:' => 'Kreirano:', + 'Updated at:' => 'Ažurirano:', + 'There is no custom filter.' => 'Nema prilagođenih filtera.', + 'New User' => 'Novi korisnik', + 'Authentication' => 'Autentifikacija', + 'If checked, this user will use a third-party system for authentication.' => 'Ako je označeno, ovaj korisnik će koristiti nezavisni sistem za autentifikaciju.', + 'The password is necessary only for local users.' => 'Lozinka je neophodna samo za lokalne korisnike.', + 'You have been invited to register on Kanboard.' => 'Pozvani ste da se registrujete na Kanboard.', + 'Click here to join your team' => 'Kliknite ovde da se prodružite Vašem timu', + 'Invite people' => 'Pozovi ljude', + 'Emails' => 'email-ovi', + 'Enter one email address by line.' => 'Unesi jednu email adresu po redu.', + 'Add these people to this project' => 'Dodaj ove ljude u ovaj projekat', + 'Add this person to this project' => 'Dodaj ovu osobu u ovaj projekat', + 'Sign-up' => 'Registracija', + 'Credentials' => 'Pristupni podaci', + 'New user' => 'Novi korisnik', + 'This username is already taken' => 'Ovo korisničko ime je zauzeto', + 'Your profile must have a valid email address.' => 'Profil mora imati validnu email adresu.', + 'TRL - Turkish Lira' => 'TRL - Turska Lira', + 'The project email is optional and could be used by several plugins.' => 'email projekta je opcion i može je koristiti nekoliko dodataka.', + 'The project email must be unique across all projects' => 'email projekta mora biti jedinstven za svaki projekat', + 'The email configuration has been disabled by the administrator.' => 'Uređivanje email-a je onemogućeno od strane administratora.', + 'Close this project' => 'Zatvori ovaj projekat', + 'Open this project' => 'Otvori ovaj projekat', + 'Close a project' => 'Zatvori projekat', + 'Do you really want to close this project: "%s"?' => 'Da li zaista želite da zatvorite projekat: "%s"?', + 'Reopen a project' => 'Ponovo otvori projekat', + 'Do you really want to reopen this project: "%s"?' => 'Da li zaista želite da ponovo otvoriti projekat: "%s"?', + 'This project is open' => 'Ovaj projekat je otvoren', + 'This project is closed' => 'Ovaj projekat je zatvoren', + 'Unable to upload files, check the permissions of your data folder.' => 'Nije moguće otpremiti datoteke, proverite prava na direktorijum servera.', + 'Another category with the same name exists in this project' => 'Kategorija sa ovim imenom već postoji na ovom projektu', + 'Comment sent by email successfully.' => 'Komentar je uspešno poslat email-om.', + 'Sent by email to "%s" (%s)' => 'Poslato email-om na "%s" (%s)', + 'Unable to read uploaded file.' => 'Nije moguće pročitati otpremljenu datoteku.', + 'Database uploaded successfully.' => 'Baza podataka je uspešno otpremljena.', + 'Task sent by email successfully.' => 'Zadatak je uspešno poslat email-om.', + 'There is no category in this project.' => 'Nema kategorija u ovom projektu.', + 'Send by email' => 'Pošalji email-om', + 'Create and send a comment by email' => 'Napravi i pošalji komentar email-om', + 'Subject' => 'Predmet', + 'Upload the database' => 'Otpremi bazu podataka', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Možete otpremiti prethodno preuzetu Sqlite bazu podataka (Gzip format).', + 'Database file' => 'Datoteka baze podataka', + 'Upload' => 'Otpremi', + 'Your project must have at least one active swimlane.' => 'Projekat mora imati barem jednu aktivnu stazu.', + 'Project: %s' => 'Projekat: %s', + 'Automatic action not found: "%s"' => 'Automatska akcija nije pronađena: "%s"', + '%d projects' => '%d projekata', + '%d project' => '%d projekat', + 'There is no project.' => 'Nema projekta.', + 'Sort' => 'Sortiraj', + 'Project ID' => 'ID projekta', + 'Project name' => 'Naziv projekta', + 'Public' => 'Javno', + 'Personal' => 'Lično', + '%d tasks' => '%d zadataka', + '%d task' => '%d zadatak', + 'Task ID' => 'ID zadatka', + 'Assign automatically a color when due date is expired' => 'Automatski postavi boju kada je rok završetka istekao', + 'Total score in this column across all swimlanes' => 'Ukupni rezultat u ovoj koloni za sve staze', + 'HRK - Kuna' => 'HRK - Hrvatska Kuna', + 'ARS - Argentine Peso' => 'ARS - Argentiski pezos', + 'COP - Colombian Peso' => 'COP - Kolumbijski pezos', + '%d groups' => '%d grupe', + '%d group' => '%d grupa', + 'Group ID' => 'ID grupe', + 'External ID' => 'Spoljni ID', + '%d users' => '%d korisnika', + '%d user' => '%d korisnik', + 'Hide subtasks' => 'Sakrij podzadatke', + 'Show subtasks' => 'Prikaži podzadatke', + 'Authentication Parameters' => 'Parametri za atentifikaciju', + 'API Access' => 'API pristup', + 'No users found.' => 'Nema ponađenih korisnika.', + 'User ID' => 'ID korisnika', + 'Notifications are activated' => 'Notifikacije su omogućene', + 'Notifications are disabled' => 'Notifikacije su onemogućene', + 'User disabled' => 'Korisnik je onemogućen', + '%d notifications' => '%d notifkacija/e', + '%d notification' => '%d notifikacija', + 'There is no external integration installed.' => 'Nije instalirana spoljna integracija.', + 'You are not allowed to update tasks assigned to someone else.' => 'Nije vam dozvoljeno da ažurirate zadatke dodeljene nekom drugom.', + 'You are not allowed to change the assignee.' => 'Nije vam dozvoljeno da promenite izvršioca.', + 'Task suppression is not permitted' => 'Suzbijanje zadataka nije dozvoljeno', + 'Changing assignee is not permitted' => 'Menjanje izvršioca nije dozvoljeno', + 'Update only assigned tasks is permitted' => 'Dozvoljeno je ažuriranje samo dodeljenih zadataka', + 'Only for tasks assigned to the current user' => 'Samo za zadatke dodeljene trenutnom korisniku', + 'My projects' => 'Moji projekti', + 'You are not a member of any project.' => 'Niste član nijednog projekta.', + 'My subtasks' => 'Moji podzadaci.', + '%d subtasks' => '%d podzadataka', + '%d subtask' => '%d podzadatak', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Samo premeštanja zadatka između tih kolona je dozvoljeno za zadatke dodeljene trenutnom korisniku', + '[DUPLICATE]' => '[DUPLIKAT]', + 'DKK - Danish Krona' => 'DKK - Danska kruna', + 'Remove user from group' => 'Ukloni korisnika iz grupe', + 'Assign the task to its creator' => 'Dodeli zadatak njegovom kreatoru', + 'This task was sent by email to "%s" with subject "%s".' => 'Ovaj zadatak je poslat email-om na "%s" sa predmetom "%s".', + 'Predefined Email Subjects' => 'Unapred definisani predmeti e-pošte', + 'Write one subject by line.' => 'Unesite jedan predmet po liniji', + 'Create another link' => 'Napravi još jednu vezu', + 'BRL - Brazilian Real' => 'BRL - Brazilski real', + 'Add a new Kanboard task' => 'Dodajte novi Kanboard zadatak', + 'Subtask not started' => 'Podzadatak nije započet', + 'Subtask currently in progress' => 'Podzadatak je trenutno u toku', + 'Subtask completed' => 'Podzadatak završen', + 'Subtask added successfully.' => 'Podzadatak uspešno dodat.', + '%d subtasks added successfully.' => '%d podzadataka uspešno dodato.', + 'Enter one subtask by line.' => 'Unesite jedan podzadatak po liniji.', + 'Predefined Contents' => 'Predefnisani sadržaj', + 'Predefined contents' => 'Predefnisani sadržaj', + 'Predefined Task Description' => 'Predefinisan opis zadatka', + 'Do you really want to remove this template? "%s"' => 'Da li zaista želite da uklonite ovaj šablon? "%s"', + 'Add predefined task description' => 'Dodat predefinsani opis zadatka', + 'Predefined Task Descriptions' => 'Predefinisani opis zadatka', + 'Template created successfully.' => 'Šablon uspešno kreiran', + 'Unable to create this template.' => 'Nije moguće kreirati ovaj šablon', + 'Template updated successfully.' => 'Šablon uspešno ažuriran', + 'Unable to update this template.' => 'Nije moguće ažurirati ovaj šablon', + 'Template removed successfully.' => 'Šablon uspešno uklonjen', + 'Unable to remove this template.' => 'Nije moguće ukloniti ovaj šablon', + 'Template for the task description' => 'Šablon za opis zadatka', + 'The start date is greater than the end date' => 'Početni datum je veći od kranjeg datuma', + 'Tags must be separated by a comma' => 'Oznake moraju biti razdvojene zarezom', + 'Only the task title is required' => 'Samo naslov zadatka je obavezan', + 'Creator Username' => 'Korisničko ime kreatora', + 'Color Name' => 'Boja kolone', + 'Column Name' => 'Naziv kolone', + 'Swimlane Name' => 'Naziv staze', + 'Time Estimated' => 'Procenjeno vreme', + 'Time Spent' => 'Utrošeno vreme', + 'External Link' => 'Spoljna veza', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Ova funckionalnost omogućava iCal kanal, RSS kanal i prikaz javne table.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Zaustavi tajmer na svim podzadacima prilikom premeštanja zadatka u drugu kolonu', + 'Subtask Title' => 'Naslov podzadatka', + 'Add a subtask and activate the timer when moving a task to another column' => 'Dodaj podzadatak i aktiviraj tajmer prilikom premeštanja zadatka u drugu kolonu', + 'days' => 'dana', + 'minutes' => 'minuta', + 'seconds' => 'sekundi', + 'Assign automatically a color when preset start date is reached' => 'Automatski postavi boju kada se dostigne unapred zadati datum početka', + 'Move the task to another column once a predefined start date is reached' => 'Premestite zadatak u drugu kolonu kada se dostigne unapred definisani datum početka', + 'This task is now linked to the task %s with the relation "%s"' => 'Zadatak je sada povezan sa zadatkom %s sa relacijom "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'Veza sa relacijom "%s" sa zadatkom %s je uklonjena', + 'Custom Filter:' => 'Prilagođeni filter', + 'Unable to find this group.' => 'Nije moguće pronaći ovu grupu.', + '%s moved the task #%d to the column "%s"' => '%s je pomerio/la zadatak #%d u kolonu "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => '%s je pomerio/la zadatakk #%d na poziciju %d u koloni "%s"', + '%s moved the task #%d to the swimlane "%s"' => '%s je pomerio/la zadatak #%d u stazu "%s"', + '%sh spent' => '%sč potrošeno', + '%sh estimated' => '%sč procenjeno', + 'Select All' => 'Izaberi sve', + 'Unselect All' => 'Poništi sve', + 'Apply action' => 'Primeni akciju', + 'Move selected tasks to another column or swimlane' => 'Pomerei odabrani zadatak u neku drugu kolonu ili stazu', + 'Edit tasks in bulk' => 'Masovno uređivanje zadataka', + 'Choose the properties that you would like to change for the selected tasks.' => 'Izaberite svojstva koja biste želeli da promenite za izabrane zadatke.', + 'Configure this project' => 'Konfigurišite ovaj projekat', + 'Start now' => 'Započni sada', + '%s removed a file from the task #%d' => '%s je uklonio/la dataoteku iz zadatka #%d', + 'Attachment removed from task #%d: %s' => 'Dodatak je uklonjen iz zadatka #%d: %s', + 'No color' => 'Nema boju', + 'Attachment removed "%s"' => 'Priložak uklonjen "%s"', + '%s removed a file from the task %s' => '%s je uklonio/la datoteku iz zadatka %s', + 'Move the task to another swimlane when assigned to a user' => 'Premesti zadatak u drugu stazu kada je zadatak dodeljen korisniku', + 'Destination swimlane' => 'Odredišna staza', + 'Assign a category when the task is moved to a specific swimlane' => 'Postavi kategoriju kada je zadatak premešten u određenu stazu', + 'Move the task to another swimlane when the category is changed' => 'Premesti zadatak u drugu stazu kada se promeni kategorija', + 'Reorder this column by priority (ASC)' => 'Promenite redosled ove kolone prema prioritetu (UZLAZNO)', + 'Reorder this column by priority (DESC)' => 'Promenite redosled ove kolone prema prioritetu (SILAZNO)', + 'Reorder this column by assignee and priority (ASC)' => 'Promenite redosled ove kolone prema izvršiocu (UZLAZNO)', + 'Reorder this column by assignee and priority (DESC)' => 'Promenite redosled ove kolone prema izvršiocu (SILAZNO)', + 'Reorder this column by assignee (A-Z)' => 'Promenite redosled ove kolone prema izvršiocu (A-Ž)', + 'Reorder this column by assignee (Z-A)' => 'Promenite redosled ove kolone prema izvršiocu (Ž-A)', + 'Reorder this column by due date (ASC)' => 'Promenite redosled ove kolone prema roku završetka (UZLAZNO)', + 'Reorder this column by due date (DESC)' => 'Promenite redosled ove kolone prema roku završetka (SILAZNO)', + 'Reorder this column by id (ASC)' => 'Promenite redosled ove kolone prema ID-u (UZLAZNO)', + 'Reorder this column by id (DESC)' => 'Promenite redosled ove kolone prema ID-u (SILAZNO)', + '%s moved the task #%d "%s" to the project "%s"' => '%s je pomerio/la zadatak #%d "%s" u projekat "%s"', + 'Task #%d "%s" has been moved to the project "%s"' => 'Zadatak #%d "%s" je premešten u projekat "%s"', + 'Move the task to another column when the due date is less than a certain number of days' => 'Pomeri zadatak u neku drugu kolonu kada je datum roka završetka kraći od određenog broja dana', + 'Automatically update the start date when the task is moved away from a specific column' => 'Automatski ažuriraj datum početka kada se zadatak ukloni iz određene kolone', + 'HTTP Client:' => 'HTTP klijent', + 'Assigned' => 'Dodeljeno', + 'Task limits apply to each swimlane individually' => 'Ograničenje broja zadataka važi za svaku stazu zasebno', + 'Column task limits apply to each swimlane individually' => 'Ograničenje broja zadataka u koloni važi za svaku stazu zasebno', + 'Column task limits are applied to each swimlane individually' => 'Ograničenje broja zadataka u koloni se primenjuje na svaku stazu zasebno', + 'Column task limits are applied across swimlanes' => 'Ograničenje broja zadataka u koloni se primenjuje na više staza', + 'Task limit: ' => 'Ograničenje broja zadataka: ', + 'Change to global tag' => 'Promeni u globalnu oznaku', + 'Do you really want to make the tag "%s" global?' => 'Da li zaista želite da oznaku "%s" učinite globalnom?', + 'Enable global tags for this project' => 'Omogući globalne oznake za ovaj projekat', + 'Group membership(s):' => 'Članstvo u grupi/grupama:', + '%s is a member of the following group(s): %s' => '%s je član grupa(e): %s', + '%d/%d group(s) shown' => '%d/%d prikazane grupa(e)', + 'Subtask creation or modification' => 'Kreiranje ili modifikacija podzadatka', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Dodeli zadatak određenom korisnika kada je zadatak prebačen u određenu stazu', + 'Comment' => 'Komentar', + 'Collapse vertically' => 'Skupi vertikalno', + 'Expand vertically' => 'Raširi vertikalno', + 'MXN - Mexican Peso' => 'MXN - Meksički pezos', + 'Estimated vs actual time per column' => 'Procenjeno vreme naspram stvarnog vremena po koloni', + 'HUF - Hungarian Forint' => 'HUF - Mađarska forinta', + 'XBT - Bitcoin' => 'XBT - Bitkoin', + 'You must select a file to upload as your avatar!' => 'Morate izabrati datoteku koju ćete postaviti kao avatar!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'Datoteka koju ste otpremili nije validna slika! (Dozvoljeni su samo *.gif, *.jpg, *.jpeg i *.png formati!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Automatski postavite rok završetka kada se zadatak premesti iz određene kolone', + 'No other projects found.' => 'Nisu pronađeni drugi projekti.', + 'Tasks copied successfully.' => 'Zadaci su uspešno iskopirani.', + 'Unable to copy tasks.' => 'Nije moguće kopirati zadatke.', + 'Theme' => 'Tema', + 'Theme:' => 'Tema:', + 'Light theme' => 'Svetla tema', + 'Dark theme' => 'Tamna tema', + 'Automatic theme - Sync with system' => 'Automatska tema - sinhronizacija sa sistemom', + 'Application managers or more' => 'Menadžeri aplikacije ili više', + 'Administrators' => 'Administratori', + 'Visibility:' => 'Vidljivost:', + 'Standard users' => 'Standardni korisnici', + 'Visibility is required' => 'Vidljivost je obavezna', + 'The visibility should be an app role' => 'Vidljivost treba da bude uloga aplikacije', + 'Reply' => 'Odgovori', + '%s wrote: ' => '%s je napisao/la: ', + 'Number of visible tasks in this column and swimlane' => 'Broj vidljivih zadataka u ovoj koloni i plivačkoj stazi', + 'Number of tasks in this swimlane' => 'Broj zadataka u ovoj plivačkoj stazi', + 'Unable to find another subtask in progress, you can close this window.' => 'Nije moguće pronaći drugi podzadatak u toku, možete zatvoriti ovaj prozor.', + 'This theme is invalid' => 'Ova tema nije validna', + 'This role is invalid' => 'Ova uloga nije validna', + 'This timezone is invalid' => 'Ova vremenska zona nije validna', + 'This language is invalid' => 'Ovaj jezik nije validan', + 'This URL is invalid' => 'Ovaj URL nije validan', + 'Date format invalid' => 'Format datuma nije validan', + 'Time format invalid' => 'Format vremena nije validan', + 'Invalid Mail transport' => 'Neispravan način slanja pošte', + 'Color invalid' => 'Neispravna boja', + 'This value must be greater or equal to %d' => 'Ova vrednost mora biti veća ili jednaka %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Dodaj BOM na početak fajla (potrebno za Microsoft Excel)', + 'Just add these tag(s)' => 'Samo dodaj ove oznake', + 'Remove internal link(s)' => 'Ukloni interne linkove', + 'Import tasks from another project' => 'Uvezi zadatke iz drugog projekta', + 'Select the project to copy tasks from' => 'Izaberi projekat iz kog želiš da kopiraš zadatke', + 'The total maximum allowed attachments size is %sB.' => 'Ukupna dozvoljena veličina priloga je %sB.', + 'Add attachments' => 'Dodaj priloge', + 'Task #%d "%s" is overdue' => 'Zadatak #%d "%s" je s iztečen rok', + 'Enable notifications by default for all new users' => 'Omogući notifikacije po difoltu za sve nove korisnike', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Dodeli zadatak njegovom kreatoru za određene kolone ako izvršilac nije ručno postavljen', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Dodeli zadatak prijavljenom korisniku pri promeni kolone u zadatu kolonu ako niko nije dodeljen', +]; diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php new file mode 100644 index 0000000..68d3131 --- /dev/null +++ b/app/Locale/sv_SE/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => ' ', + 'None' => 'Ingen', + 'Edit' => 'Redigera', + 'Remove' => 'Ta bort', + 'Yes' => 'Ja', + 'No' => 'Nej', + 'cancel' => 'avbryt', + 'or' => 'eller', + 'Yellow' => 'Gul', + 'Blue' => 'Blå', + 'Green' => 'Grön', + 'Purple' => 'Lila', + 'Red' => 'Röd', + 'Orange' => 'Orange', + 'Grey' => 'Grå', + 'Brown' => 'Brun', + 'Deep Orange' => 'Mörkorange', + 'Dark Grey' => 'Mörkgrå', + 'Pink' => 'Rosa', + 'Teal' => 'Grönblå', + 'Cyan' => 'Cyan', + 'Lime' => 'Lime', + 'Light Green' => 'Ljusgrön', + 'Amber' => 'Bärnsten', + 'Save' => 'Spara', + 'Login' => 'Login', + 'Official website:' => 'Officiell webbsida:', + 'Unassigned' => 'Ej tilldelad', + 'View this task' => 'Se denna uppgift', + 'Remove user' => 'Ta bort användare', + 'Do you really want to remove this user: "%s"?' => 'Vill du verkligen ta bort användaren: "%s"?', + 'All users' => 'Alla användare', + 'Username' => 'Användarnamn', + 'Password' => 'Lösenord', + 'Administrator' => 'Administratör', + 'Sign in' => 'Logga in', + 'Users' => 'Användare', + 'Forbidden' => 'Ej tillåten', + 'Access Forbidden' => 'Ej tillåten', + 'Edit user' => 'Ändra användare', + 'Logout' => 'Logga ut', + 'Bad username or password' => 'Fel användarnamn eller lösenord', + 'Edit project' => 'Ändra projekt', + 'Name' => 'Namn', + 'Projects' => 'Projekt', + 'No project' => 'Inget projekt', + 'Project' => 'Projekt', + 'Status' => 'Status', + 'Tasks' => 'Uppgifter', + 'Board' => 'Tavla', + 'Actions' => 'Åtgärder', + 'Inactive' => 'Inaktiv', + 'Active' => 'Aktiv', + 'Unable to update this board.' => 'Kunde inte uppdatera tavlan', + 'Disable' => 'Inaktivera', + 'Enable' => 'Aktivera', + 'New project' => 'Nytt projekt', + 'Do you really want to remove this project: "%s"?' => 'Vill du verkligen ta bort projektet: "%s" ?', + 'Remove project' => 'Ta bort projekt', + 'Edit the board for "%s"' => 'Ändra tavlan för "%s"', + 'Add a new column' => 'Lägg till ny kolumn', + 'Title' => 'Titel', + 'Assigned to %s' => 'Tilldelad %s', + 'Remove a column' => 'Ta bort en kolumn', + 'Unable to remove this column.' => 'Kunde inte ta bort kolumnen.', + 'Do you really want to remove this column: "%s"?' => 'Vill du verkligen ta bort kolumnen: "%s"?', + 'Settings' => 'Inställningar', + 'Application settings' => 'Applikationsinställningar', + 'Language' => 'Språk', + 'Webhook token:' => 'Token för webhooks:', + 'API token:' => 'API token:', + 'Database size:' => 'Databasstorlek:', + 'Download the database' => 'Ladda ner databasen', + 'Optimize the database' => 'Optimera databasen', + '(VACUUM command)' => '(Vacuum-kommando)', + '(Gzip compressed Sqlite file)' => '(Gzip-komprimera Sqlite-filen)', + 'Close a task' => 'Stäng en uppgift', + 'Column' => 'Kolumn', + 'Color' => 'Färg', + 'Assignee' => 'Uppdragsinnehavare', + 'Create another task' => 'Skapa ännu en uppgift', + 'New task' => 'Ny uppgift', + 'Open a task' => 'Öppna en uppgift', + 'Do you really want to open this task: "%s"?' => 'Vill du verkligen öppna denna uppgift: "%s"?', + 'Back to the board' => 'Tillbaka till tavlan', + 'There is nobody assigned' => 'Det finns ingen tilldelad', + 'Column on the board:' => 'Kolumn på tavlan:', + 'Close this task' => 'Stäng uppgiften', + 'Open this task' => 'Öppna uppgiften', + 'There is no description.' => 'Det finns ingen beskrivning.', + 'Add a new task' => 'Lägg till en ny uppgift', + 'The username is required' => 'Användarnamnet måste anges', + 'The maximum length is %d characters' => 'Max antal bokstäver är %d', + 'The minimum length is %d characters' => 'Minst antal bokstäver är %d', + 'The password is required' => 'Lösenordet måste anges.', + 'This value must be an integer' => 'Värdet måste vara ett heltal.', + 'The username must be unique' => 'Användarnamnet måste vara unikt', + 'The user id is required' => 'Användar-ID måste anges', + 'Passwords don\'t match' => 'Lösenorden matchar inte', + 'The confirmation is required' => 'Bekräftelse behövs.', + 'The project is required' => 'Projektet måste anges', + 'The id is required' => 'Aktuellt ID måste anges', + 'The project id is required' => 'Projekt-ID måste anges', + 'The project name is required' => 'Ett projektnamn måste anges', + 'The title is required' => 'En titel måste anges.', + 'Settings saved successfully.' => 'Inställningarna har sparats.', + 'Unable to save your settings.' => 'Kunde inte spara dina ändringar', + 'Database optimization done.' => 'Databasen har optimerats.', + 'Your project has been created successfully.' => 'Ditt projekt har skapats.', + 'Unable to create your project.' => 'Kunde inte skapa ditt projekt.', + 'Project updated successfully.' => 'Projektet har uppdaterats.', + 'Unable to update this project.' => 'Kunde inte uppdatera detta projekt.', + 'Unable to remove this project.' => 'Kunde inte ta bort detta projekt.', + 'Project removed successfully.' => 'Projektet har tagits bort.', + 'Project activated successfully.' => 'Projektet har aktiverats.', + 'Unable to activate this project.' => 'Kunde inte aktivera detta projekt.', + 'Project disabled successfully.' => 'Projektet har inaktiverats.', + 'Unable to disable this project.' => 'Kunde inte inaktivera detta projekt.', + 'Unable to open this task.' => 'Kunde inte öppna denna uppgift.', + 'Task opened successfully.' => 'Uppgiften har öppnats.', + 'Unable to close this task.' => 'Kunde inte stänga denna uppgift.', + 'Task closed successfully.' => 'Uppgiften har stängts.', + 'Unable to update your task.' => 'Kunde inte uppdatera din uppgift.', + 'Task updated successfully.' => 'Uppgiften har uppdaterats.', + 'Unable to create your task.' => 'Kunde inte skapa din uppgift.', + 'Task created successfully.' => 'Uppgiften har skapats.', + 'User created successfully.' => 'Användaren har skapats.', + 'Unable to create your user.' => 'Kunde inte skapa din användare.', + 'User updated successfully.' => 'Användaren har updaterats.', + 'User removed successfully.' => 'Användaren har tagits bort.', + 'Unable to remove this user.' => 'Kunde inte ta bort denna användare.', + 'Board updated successfully.' => 'Tavlan uppdaterad.', + 'Ready' => 'Denna månad', + 'Backlog' => 'Att göra', + 'Work in progress' => 'Pågående', + 'Done' => 'Slutfört', + 'Application version:' => 'Version:', + 'Id' => 'ID', + 'Public link' => 'Publik länk', + 'Timezone' => 'Tidszon', + 'Sorry, I didn\'t find this information in my database!' => 'Informationen kunde inte hittas i databasen.', + 'Page not found' => 'Sidan hittas inte', + 'Complexity' => 'Komplexitet', + 'Task limit' => 'Uppgiftsbegränsning', + 'Task count' => 'Antal uppgifter', + 'User' => 'Användare', + 'Comments' => 'Kommentarer', + 'Comment is required' => 'En kommentar måste lämnas', + 'Comment added successfully.' => 'Kommentaren har lagts till.', + 'Unable to create your comment.' => 'Kommentaren kunde inte laddas upp.', + 'Due Date' => 'Måldatum', + 'Invalid date' => 'Ej tillåtet datum', + 'Automatic actions' => 'Automatiska åtgärder', + 'Your automatic action has been created successfully.' => 'Din automatiska åtgärd har skapats.', + 'Unable to create your automatic action.' => 'Kunde inte skapa din automatiska åtgärd.', + 'Remove an action' => 'Ta bort en åtgärd', + 'Unable to remove this action.' => 'Kunde inte ta bort denna åtgärd.', + 'Action removed successfully.' => 'Åtgärden har tagits bort.', + 'Automatic actions for the project "%s"' => 'Automatiska åtgärder för projektet "%s"', + 'Add an action' => 'Lägg till en åtgärd', + 'Event name' => 'Händelsenamn', + 'Action' => 'Åtgärd', + 'Event' => 'Händelse', + 'When the selected event occurs execute the corresponding action.' => 'När händelsen inträffar, kör inställd åtgärd.', + 'Next step' => 'Nästa steg', + 'Define action parameters' => 'Definiera händelseparametrar', + 'Do you really want to remove this action: "%s"?' => 'Vill du verkligen ta bort denna åtgärd: "%s"?', + 'Remove an automatic action' => 'Ta bort en automatiskt åtgärd', + 'Assign the task to a specific user' => 'Tilldela uppgiften till en viss användare', + 'Assign the task to the person who does the action' => 'Tilldela uppgiften till personen som skapar den', + 'Duplicate the task to another project' => 'Kopiera uppgiften till ett annat projekt', + 'Move a task to another column' => 'Flytta en uppgift till en annan kolumn', + 'Task modification' => 'Ändra uppgift', + 'Task creation' => 'Skapa uppgift', + 'Closing a task' => 'Stänger en uppgift', + 'Assign a color to a specific user' => 'Tilldela en färg till en viss användare', + 'Position' => 'Position', + 'Duplicate to project' => 'Kopiera till ett annat projekt', + 'Duplicate' => 'Kopiera uppgiften', + 'Link' => 'Länk', + 'Comment updated successfully.' => 'Kommentaren har uppdaterats.', + 'Unable to update your comment.' => 'Kunde inte uppdatera din kommentar.', + 'Remove a comment' => 'Ta bort en kommentar', + 'Comment removed successfully.' => 'Kommentaren har tagits bort.', + 'Unable to remove this comment.' => 'Kunde inte ta bort denna kommentar.', + 'Do you really want to remove this comment?' => 'Är du säker på att du vill ta bort denna kommentar?', + 'Current password for the user "%s"' => 'Nuvarande lösenord för användaren "%s"', + 'The current password is required' => 'Nuvarande lösenord måste anges', + 'Wrong password' => 'Fel lösenord', + 'Unknown' => 'Okänd', + 'Last logins' => 'Senaste inloggningarna', + 'Login date' => 'Inloggningsdatum', + 'Authentication method' => 'Autentiseringsmetod', + 'IP address' => 'IP-adress', + 'User agent' => 'Användaragent/webbläsare', + 'Persistent connections' => 'Beständiga anslutningar', + 'No session.' => 'Ingen session.', + 'Expiration date' => 'Förfallodatum', + 'Remember Me' => 'Kom ihåg mig', + 'Creation date' => 'Skapandedatum', + 'Everybody' => 'Alla', + 'Open' => 'Öppen', + 'Closed' => 'Stängd', + 'Search' => 'Sök', + 'Nothing found.' => 'Inget kunde hittas.', + 'Due date' => 'Måldatum', + 'Description' => 'Beskrivning', + '%d comments' => '%d kommentarer', + '%d comment' => '%d kommentar', + 'Email address invalid' => 'Epost-adressen ogiltig', + 'Your external account is not linked anymore to your profile.' => 'Ditt externa konto är inte längre länkat till din profil.', + 'Unable to unlink your external account.' => 'Kunde inte koppla från ditt externa konto.', + 'External authentication failed' => 'Extern autentisering misslyckades', + 'Your external account is linked to your profile successfully.' => 'Ditt externa konto länkades till din profil.', + 'Email' => 'Epost', + 'Task removed successfully.' => 'Uppgiften har tagits bort.', + 'Unable to remove this task.' => 'Kunde inte ta bort denna uppgift.', + 'Remove a task' => 'Ta bort en uppgift', + 'Do you really want to remove this task: "%s"?' => 'Vill du verkligen ta bort denna uppgift: "%s"?', + 'Assign automatically a color based on a category' => 'Tilldela automatiskt en färg baserat på en kategori', + 'Assign automatically a category based on a color' => 'Tilldela automatiskt en kategori baserat på en färg', + 'Task creation or modification' => 'Skapa eller ändra uppgift', + 'Category' => 'Kategori', + 'Category:' => 'Kategori:', + 'Categories' => 'Ange kategorier', + 'Your category has been created successfully.' => 'Din kategori har skapats.', + 'This category has been updated successfully.' => 'Din kategori har uppdaterats.', + 'Unable to update this category.' => 'Kunde inte uppdatera din kategori.', + 'Remove a category' => 'Ta bort en kategori', + 'Category removed successfully.' => 'Kategorin har tagits bort.', + 'Unable to remove this category.' => 'Kunde inte ta bort denna kategori.', + 'Category modification for the project "%s"' => 'Ändring av kategori för projektet "%s"', + 'Category Name' => 'Kategorinamn', + 'Add a new category' => 'Lägg till en kategori', + 'Do you really want to remove this category: "%s"?' => 'Vill du verkligen ta bort denna kategori: "%s"?', + 'All categories' => 'Alla kategorier', + 'No category' => 'Ingen kategori', + 'The name is required' => 'Namnet måste anges', + 'Remove a file' => 'Ta bort en fil', + 'Unable to remove this file.' => 'Kunde inte ta bort denna fil.', + 'File removed successfully.' => 'Filen har tagits bort.', + 'Attach a document' => 'Bifoga ett dokument', + 'Do you really want to remove this file: "%s"?' => 'Vill du verkligen ta bort denna fil: "%s"?', + 'Attachments' => 'Bifogade filer', + 'Edit the task' => 'Ändra uppgiften', + 'Add a comment' => 'Lägg till kommentar', + 'Edit a comment' => 'Ändra en kommentar', + 'Summary' => 'Sammanfattning', + 'Time tracking' => 'Tidsåtgång', + 'Estimate:' => 'Uppskattning', + 'Spent:' => 'Nedlagd tid', + 'Do you really want to remove this sub-task?' => 'Vill du verkligen ta bort deluppgiften?', + 'Remaining:' => 'Återstående:', + 'hours' => 'timmar', + 'estimated' => 'uppskattat', + 'Sub-Tasks' => 'Deluppgifter', + 'Add a sub-task' => 'Lägg till deluppgift', + 'Original estimate' => 'Ursprunglig uppskattning', + 'Create another sub-task' => 'Skapa en till deluppgift', + 'Time spent' => 'Nedlagd tid', + 'Edit a sub-task' => 'Ändra en deluppgift', + 'Remove a sub-task' => 'Ta bort en deluppgift', + 'The time must be a numeric value' => 'Tiden måste ha ett numeriskt värde', + 'Todo' => 'Att göra', + 'In progress' => 'Pågående', + 'Sub-task removed successfully.' => 'Deluppgiften har tagits bort.', + 'Unable to remove this sub-task.' => 'Kunde inte ta bort denna deluppgift.', + 'Sub-task updated successfully.' => 'Deluppgiften har uppdaterats.', + 'Unable to update your sub-task.' => 'Kunde inte uppdatera din deluppgift.', + 'Unable to create your sub-task.' => 'Kunde inte skapa din deluppgift.', + 'Maximum size: ' => 'Maxstorlek: ', + 'Display another project' => 'Visa ett annat projekt', + 'Created by %s' => 'Skapad av %s', + 'Tasks Export' => 'Exportera uppgifter', + 'Start Date' => 'Startdatum', + 'Execute' => 'Utför', + 'Task Id' => 'Uppgift-ID', + 'Creator' => 'Skapare', + 'Modification date' => 'Ändringsdatum', + 'Completion date' => 'Slutfört datum', + 'Clone' => 'Klona', + 'Project cloned successfully.' => 'Projektet har klonats.', + 'Unable to clone this project.' => 'Kunde inte klona projektet.', + 'Enable email notifications' => 'Aktivera e-postnotiser', + 'Task position:' => 'Uppgiftsposition:', + 'The task #%d has been opened.' => 'Uppgiften #%d har öppnats.', + 'The task #%d has been closed.' => 'Uppgiften #%d har stängts.', + 'Sub-task updated' => 'Deluppgift uppdaterad', + 'Title:' => 'Titel:', + 'Status:' => 'Status:', + 'Assignee:' => 'Tilldelad:', + 'Time tracking:' => 'Tidsspårning', + 'New sub-task' => 'Ny deluppgift', + 'New attachment added "%s"' => 'Ny bifogning tillagd "%s"', + 'New comment posted by %s' => 'Ny kommentar postad av %s', + 'New comment' => 'Ny kommentar', + 'Comment updated' => 'Kommentaren har uppdaterats', + 'New subtask' => 'Ny deluppgift', + 'I only want to receive notifications for these projects:' => 'Jag vill endast få notiser för dessa projekt:', + 'view the task on Kanboard' => 'Visa uppgiften på Kanboard', + 'Public access' => 'Publik åtkomst', + 'Disable public access' => 'Inaktivera publik åtkomst', + 'Enable public access' => 'Aktivera publik åtkomst', + 'Public access disabled' => 'Publik åtkomst har inaktiverats', + 'Move the task to another project' => 'Flytta uppgiften till ett annat projekt', + 'Move to project' => 'Flytta till ett annat projekt', + 'Do you really want to duplicate this task?' => 'Vill du verkligen kopiera denna uppgift?', + 'Duplicate a task' => 'Kopiera en uppgift', + 'External accounts' => 'Externa konton', + 'Account type' => 'Kontotyp', + 'Local' => 'Lokal', + 'Remote' => 'Fjärr', + 'Enabled' => 'Aktiverad', + 'Disabled' => 'Inaktiverad', + 'Login:' => 'Användarnam:', + 'Full Name:' => 'Namn:', + 'Email:' => 'E-post:', + 'Notifications:' => 'Notiser:', + 'Notifications' => 'Notiser', + 'Account type:' => 'Kontotyp:', + 'Edit profile' => 'Ändra profil', + 'Change password' => 'Byt lösenord', + 'Password modification' => 'Ändra lösenord', + 'External authentications' => 'Extern autentisering', + 'Never connected.' => 'Inte ansluten.', + 'No external authentication enabled.' => 'Ingen extern autentisering aktiverad.', + 'Password modified successfully.' => 'Lösenordet har ändrats.', + 'Unable to change the password.' => 'Kunde inte byta lösenord.', + 'Change category' => 'Byt kategori', + '%s updated the task %s' => '%s uppdaterade uppgiften %s', + '%s opened the task %s' => '%s öppna uppgiften %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s flyttade uppgiften %s till positionen #%d i kolumnen "%s"', + '%s moved the task %s to the column "%s"' => '%s flyttade uppgiften %s till kolumnen "%s"', + '%s created the task %s' => '%s skapade uppgiften %s', + '%s closed the task %s' => '%s stängde uppgiften %s', + '%s created a subtask for the task %s' => '%s skapade en deluppgift för uppgiften %s', + '%s updated a subtask for the task %s' => '%s uppdaterade en deluppgift för uppgiften %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Tilldelades %s med en uppskattning på %s/%sh', + 'Not assigned, estimate of %sh' => 'Inte tilldelade, uppskattat %sh', + '%s updated a comment on the task %s' => '%s uppdaterade en kommentar till uppgiften %s', + '%s commented the task %s' => '%s kommenterade uppgiften %s', + '%s\'s activity' => '%ss aktivitet', + 'RSS feed' => 'RSS-flöde', + '%s updated a comment on the task #%d' => '%s uppdaterade en kommentar på uppgiften #%d', + '%s commented on the task #%d' => '%s kommenterade uppgiften #%d', + '%s updated a subtask for the task #%d' => '%s uppdaterade en deluppgift för uppgiften #%d', + '%s created a subtask for the task #%d' => '%s skapade en deluppgift för uppgiften #%d', + '%s updated the task #%d' => '%s uppdaterade uppgiften #%d', + '%s created the task #%d' => '%s skapade uppgiften #%d', + '%s closed the task #%d' => '%s stängde uppgiften #%d', + '%s opened the task #%d' => '%s öppnade uppgiften #%d', + 'Activity' => 'Aktivitet', + 'Default values are "%s"' => 'Standardvärden är "%s"', + 'Default columns for new projects (Comma-separated)' => 'Standardkolumner för nya projekt (kommaseparerade)', + 'Task assignee change' => 'Ändra tilldelning av uppgiften', + '%s changed 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', + 'New password for the user "%s"' => 'Nytt lösenord för användaren "%s"', + 'Choose an event' => 'Välj en händelse', + 'Create a task from an external provider' => 'Skapa en uppgift från en extern leverantör', + 'Change the assignee based on an external username' => 'Ändra tilldelning baserat på ett externt användarnamn', + 'Change the category based on an external label' => 'Ändra kategori baserat på en extern etikett', + 'Reference' => 'Referens', + 'Label' => 'Etikett', + 'Database' => 'Databas', + 'About' => 'Om', + 'Database driver:' => 'Databasdrivrutin:', + 'Board settings' => 'Inställningar för tavla', + 'Webhook settings' => 'Webhook-inställningar', + 'Reset token' => 'Nollställ token', + 'API endpoint:' => 'API-ändpunkt:', + 'Refresh interval for personal board' => 'Uppdateringsintervall för privat tavla', + 'Refresh interval for public board' => 'Uppdateringsintervall för publik tavla', + 'Task highlight period' => 'Period att framhäva ny uppgift', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Period (i sekunder) att betrakta en uppgifts ändring som ny (ange 0 för att inaktivera, 2 dagar är standard)', + 'Frequency in second (60 seconds by default)' => 'Frekvens i sekunder (60 sekunder är standard)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frekvens i sekunder (ange 0 för att inaktivera, 10 sekunder är standard)', + 'Application URL' => 'Applikations-URL', + 'Token regenerated.' => 'Token nyskapad.', + 'Date format' => 'Datumformat', + 'ISO format is always accepted, example: "%s" and "%s"' => 'ISO-format är alltid tillåtet, exempel: "%s" och "%s"', + 'New personal project' => 'Nytt privat projekt', + 'This project is personal' => 'Det här projektet är privat', + 'Add' => 'Lägg till', + 'Start date' => 'Startdatum', + 'Time estimated' => 'Uppskattad tid', + 'There is nothing assigned to you.' => 'Du har inget tilldelat till dig.', + 'My tasks' => 'Mina uppgifter', + 'Activity stream' => 'Aktivitetsström', + 'Dashboard' => 'Översiktspanel', + 'Confirmation' => 'Bekräftelse', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Create a comment from an external provider' => 'Skapa en kommentar från en extern leverantör', + 'Project management' => 'Projekthantering', + 'Columns' => 'Kolumner', + 'Task' => 'Uppgift', + 'Percentage' => 'Procent', + 'Number of tasks' => 'Antal uppgifter', + 'Task distribution' => 'Uppgiftsfördelning', + 'Analytics' => 'Analyser', + 'Subtask' => 'Deluppgift', + 'User repartition' => 'Användardeltagande', + 'Clone this project' => 'Klona projektet', + 'Column removed successfully.' => 'Kolumnen togs bort', + 'Not enough data to show the graph.' => 'Inte tillräckligt med data för att visa graf', + 'Previous' => 'Föregående', + 'The id must be an integer' => 'ID måste vara ett heltal', + 'The project id must be an integer' => 'Projekt-ID måste vara ett heltal', + 'The status must be an integer' => 'Status måste vara ett heltal', + 'The subtask id is required' => 'Deluppgifts-ID behövs', + 'The subtask id must be an integer' => 'Deluppgifts-ID måste vara ett heltal', + 'The task id is required' => 'Uppgifts-ID behövs', + 'The task id must be an integer' => 'Uppgifts-ID måste vara ett heltal', + 'The user id must be an integer' => 'Användar-ID måste vara ett heltal', + 'This value is required' => 'Värdet behövs', + 'This value must be numeric' => 'Värdet måste vara numeriskt', + 'Unable to create this task.' => 'Kunde inte skapa uppgiften.', + 'Cumulative flow diagram' => 'Diagram med kumulativt flöde', + 'Daily project summary' => 'Daglig projektsummering', + 'Daily project summary export' => 'Export av daglig projektsummering', + 'Exports' => 'Exporter', + 'This export contains the number of tasks per column grouped per day.' => 'Denna export innehåller antalet uppgifter per kolumn grupperade per dag.', + 'Active swimlanes' => 'Aktiva simbanor', + 'Add a new swimlane' => 'Lägg till en ny simbana', + 'Default swimlane' => 'Standard-simbana', + 'Do you really want to remove this swimlane: "%s"?' => 'Vill du verkligen ta bort simbanan "%s"?', + 'Inactive swimlanes' => 'Inaktiv simbana', + 'Remove a swimlane' => 'Ta bort en simbana', + 'Swimlane modification for the project "%s"' => 'Ändra simbana för projekt "%s"', + 'Swimlane removed successfully.' => 'Simbanan togs bort', + 'Swimlanes' => 'Simbanor', + 'Swimlane updated successfully.' => 'Simbana uppdaterad', + 'Unable to remove this swimlane.' => 'Kunde inte ta bort simbanan', + 'Unable to update this swimlane.' => 'Kunde inte uppdatera simbanan', + 'Your swimlane has been created successfully.' => 'Din simbana har skapats', + 'Example: "Bug, Feature Request, Improvement"' => 'Exempel: "Bug, ny funktionalitet, förbättringar"', + 'Default categories for new projects (Comma-separated)' => 'Standardkategorier för nya projekt (komma-separerade)', + 'Integrations' => 'Integrationer', + 'Integration with third-party services' => 'Integration med tredjepartstjänster', + 'Subtask Id' => 'Deluppgifts-ID', + 'Subtasks' => 'Deluppgift', + 'Subtasks Export' => 'Exportering av deluppgifter', + 'Task Title' => 'Uppgiftstitel', + 'Untitled' => 'Titel saknas', + 'Application default' => 'Applikationsstandard', + 'Language:' => 'Språk', + 'Timezone:' => 'Tidszon', + 'All columns' => 'Alla kolumner', + 'Next' => 'Nästa', + '#%d' => '#%d', + 'All swimlanes' => 'Alla simbanor', + 'All colors' => 'Alla färger', + 'Moved to column %s' => 'Flyttad till kolumn %s', + 'User dashboard' => 'Användarens översiktspanel', + '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"', + '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?', + 'Disallow login form' => 'Förbjud inloggningsformulär', + 'Start' => 'Start', + 'End' => 'Slut', + 'Task age in days' => 'Uppgiftsålder i dagar', + 'Days in this column' => 'Dagar i denna kolumn', + '%dd' => '%dd', + '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', + 'Opposite label' => 'Motpartslänk', + 'Remove a link' => 'Ta bort en länk', + 'The labels must be different' => 'Etiketterna måste vara olika', + 'There is no link.' => 'Det finns ingen länk', + 'This label must be unique' => 'Etiketten 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 länken', + 'relates to' => 'relaterar till', + 'blocks' => 'blockerar', + 'is blocked by' => 'blockeras av', + 'duplicates' => 'duplicerar', + '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', + 'Expand tasks' => 'Fäll ut uppgifter', + 'Collapse tasks' => 'Fäll ihop uppgifter', + 'Expand/collapse tasks' => 'Fäll ut/fäll ihop 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', + 'Compact view' => 'Kompakt vy', + 'Horizontal scrolling' => 'Horisontell scroll', + 'Compact/wide view' => 'Kompakt/bred vy', + 'Currency' => 'Valuta', + 'Personal project' => 'Privat projekt', + 'AUD - Australian Dollar' => 'AUD - Australiska dollar', + 'CAD - Canadian Dollar' => 'CAD - Kanadensiska dollar', + 'CHF - Swiss Francs' => 'CHF - Schweiziska franc', + 'Custom Stylesheet' => 'Anpassad stilmall', + 'EUR - Euro' => 'EUR - Euro', + 'GBP - British Pound' => 'GBP - Brittiska pund', + 'INR - Indian Rupee' => 'INR - Indiska rupier', + 'JPY - Japanese Yen' => 'JPY - Japanska yen', + 'NZD - New Zealand Dollar' => 'NZD - Nya Zeeländska dollar', + 'PEN - Peruvian Sol' => 'PEN – Peruansk sol', + 'RSD - Serbian dinar' => 'RSD - Serbiska dinarer', + 'CNY - Chinese Yuan' => 'CNY - Kinesisk yuan', + 'USD - US Dollar' => 'USD - Amerikanska dollar', + 'VES - Venezuelan Bolívar' => 'VES - Venezuelanska bolívar', + 'Destination column' => 'Målkolumn', + 'Move the task to another column when assigned to a user' => 'Flytta uppgiften till en annan kolumn när den tilldelats en användare', + 'Move the task to another column when assignee is cleared' => 'Flytta uppgiften till en annan kolumn när tilldelningen tas bort.', + 'Source column' => 'Källkolumn', + 'Transitions' => 'Övergångar', + 'Executer' => 'Verkställare', + 'Time spent in the column' => 'Tid i kolumnen.', + 'Task transitions' => 'Uppgiftsövergångar', + 'Task transitions export' => 'Export av uppgiftsövergångar', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Denna rapport innehåller alla kolumnförflyttningar för varje uppgift med datum, användare och nedlagd tid vid varje övergång.', + 'Currency rates' => 'Valutakurser', + 'Rate' => 'Kurs', + 'Change reference currency' => 'Ändra referenskurs', + 'Reference currency' => 'Referensvaluta', + 'The currency rate has been added successfully.' => 'Valutakursen har lagts till.', + 'Unable to add this currency rate.' => 'Kunde inte lägga till valutakursen.', + 'Webhook URL' => 'Webhook URL', + '%s removed the assignee of the task %s' => '%s ta bort tilldelningen av uppgiften %s', + 'Information' => 'Information', + 'Check two factor authentication code' => 'Kolla tvåfaktorsautentiseringsngskod', + 'The two factor authentication code is not valid.' => 'Tvåfaktorsautentiseringskoden är inte giltig.', + 'The two factor authentication code is valid.' => 'Tvåfaktorsautentiseringskoden är giltig.', + 'Code' => 'Kod', + 'Two factor authentication' => 'Tvåfaktorsautentisering', + 'This QR code contains the key URI: ' => 'Denna QR-kod innehåller nyckel-URI:n', + 'Check my code' => 'Verifiera min kod', + 'Secret key: ' => 'Säkerhetsnyckel:', + 'Test your device' => 'Testa din enhet', + 'Assign a color when the task is moved to a specific column' => 'Tilldela en färg när uppgiften flyttas till en viss kolumn', + '%s via Kanboard' => '%s via Kanboard', + 'Burndown chart' => 'Burndown-diagram', + 'This chart show the task complexity over the time (Work Remaining).' => 'Diagrammet visar uppgiftens svårighet över tid (återstående arbete).', + 'Screenshot taken %s' => 'Skärmdump tagen %s', + 'Add a screenshot' => 'Lägg till en skärmdump', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Ta en skärmdump och tryck CTRL+V för att klistra in här.', + 'Screenshot uploaded successfully.' => 'Skärmdumpen laddades upp.', + 'SEK - Swedish Krona' => 'SEK - Svensk krona', + 'Identifier' => 'Identifierare', + 'Disable two factor authentication' => 'Inaktivera tvåfaktorsautentisering', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Vill du verkligen inaktivera tvåfaktorsautentisering för denna användare: "%s"?', + 'Edit link' => 'Ändra länk', + 'Start to type task title...' => 'Börja skriv uppgiftstitel...', + 'A task cannot be linked to itself' => 'En uppgift kan inte länkas till sig själv', + 'The exact same link already exists' => 'Länken existerar redan', + 'Recurrent task is scheduled to be generated' => 'Återkommande uppgift är schemalagd att genereras', + 'Score' => 'Poäng', + 'The identifier must be unique' => 'Identifieraren måste vara unik', + 'This linked task id doesn\'t exists' => 'Detta länkade uppgifts-ID existerar inte', + 'This value must be alphanumeric' => 'Värdet måste vara alfanumeriskt', + 'Edit recurrence' => 'Ändra återkommande', + 'Generate recurrent task' => 'Generera återkommande uppgift', + 'Trigger to generate recurrent task' => 'Aktivera att generera återkommande uppgift', + 'Factor to calculate new due date' => 'Faktor för att beräkna nytt förfallodatum', + 'Timeframe to calculate new due date' => 'Tidsram för att beräkna nytt förfallodatum', + 'Base date to calculate new due date' => 'Basdatum för att beräkna nytt förfallodatum', + 'Action date' => 'Händelsedatum', + 'Base date to calculate new due date: ' => 'Basdatum för att beräkna nytt förfallodatum: ', + 'This task has created this child task: ' => 'Uppgiften har skapat denna underliggande uppgift: ', + 'Day(s)' => 'Dag(ar)', + 'Existing due date' => 'Existerande förfallodatum', + 'Factor to calculate new due date: ' => 'Faktor för att beräkna nytt förfallodatum: ', + 'Month(s)' => 'Månad(er)', + 'This task has been created by: ' => 'Uppgiften har skapats av: ', + 'Recurrent task has been generated:' => 'Återkommande uppgift har genererats:', + 'Timeframe to calculate new due date: ' => 'Tidsram för att beräkna nytt förfallodatum: ', + 'Trigger to generate recurrent task: ' => 'Aktivera att generera återkommande uppgift: ', + 'When task is closed' => 'När uppgiften är stängd', + 'When task is moved from first column' => 'När uppgiften flyttas från första kolumnen', + 'When task is moved to last column' => 'När uppgiften flyttas till sista kolumnen', + 'Year(s)' => 'År', + 'Project settings' => 'Projektinställningar', + 'Automatically update the start date' => 'Automatisk uppdatering av startdatum', + 'iCal feed' => 'iCal-flöde', + 'Preferences' => 'Preferenser', + 'Security' => 'Säkerhet', + 'Two factor authentication disabled' => 'Tvåfaktorsautentisering inaktiverad', + 'Two factor authentication enabled' => 'Tvåfaktorsautentisering aktiverad', + 'Unable to update this user.' => 'Kunde inte uppdatera användaren.', + 'There is no user management for personal projects.' => 'Det finns ingen användarhantering för privata projekt.', + 'User that will receive the email' => 'Användare som kommer att ta emot e-brevet', + 'Email subject' => 'E-postämne', + 'Date' => 'Datum', + 'Add a comment log when moving the task between columns' => 'Lägg till en kommentarslogg när en uppgift flyttas mellan kolumner', + 'Move the task to another column when the category is changed' => 'Flyttas uppgiften till en annan kolumn när kategorin ändras', + 'Send a task by email to someone' => 'Skicka en uppgift till någon via e-post', + 'Reopen a task' => 'Återöppna en uppgift', + 'Notification' => 'Notis', + '%s moved the task #%d to the first swimlane' => '%s flyttade uppgiften #%d till första simbanan', + 'Swimlane' => 'Simbana', + '%s moved the task %s to the first swimlane' => '%s flyttade uppgiften %s till första simbanan', + '%s moved the task %s to the swimlane "%s"' => '%s flyttade uppgiften %s till simbana "%s"', + 'This report contains all subtasks information for the given date range.' => 'Denna rapport innehåller all deluppgiftsinformation för det givna datumintervallet.', + 'This report contains all tasks information for the given date range.' => 'Denna rapport innehåller all uppgiftsinformation för det givna datumintervallet.', + 'Project activities for %s' => 'Projektaktiviteter för %s', + 'view the board on Kanboard' => 'visa tavlan på Kanboard', + 'The task has been moved to the first swimlane' => 'Uppgiften har flyttats till första simbanan', + 'The task has been moved to another swimlane:' => 'Uppgiften har flyttats till en annan simbana::', + 'New title: %s' => 'Ny titel: %s', + 'The task is not assigned anymore' => 'Uppgiften är inte länge tilldelad', + 'New assignee: %s' => 'Ny tilldelning: %s', + 'There is no category now' => 'Det finns ingen kategori nu', + 'New category: %s' => 'Ny kategori: %s', + 'New color: %s' => 'Ny färg: %s', + 'New complexity: %d' => 'Ny komplexitet: %d', + 'The due date has been removed' => 'Förfallodatumet har tagits bort', + 'There is no description anymore' => 'Det finns ingen beskrivning längre', + 'Recurrence settings has been modified' => 'Återkommande inställning har ändrats', + 'Time spent changed: %sh' => 'Spenderad tid har ändrats: %sh', + 'Time estimated changed: %sh' => 'Tidsuppskattning ändrad: %sh', + 'The field "%s" has been updated' => 'Fältet "%s" har uppdaterats', + 'The description has been modified:' => 'Beskrivningen har modifierats', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Vill du verkligen stänga uppgiften "%s" och alla deluppgifter?', + 'I want to receive notifications for:' => 'Jag vill få notiser för:', + 'All tasks' => 'Alla uppgifter', + 'Only for tasks assigned to me' => 'Bara för uppgifter tilldelade mig', + 'Only for tasks created by me' => 'Bara för uppgifter skapade av mig', + 'Only for tasks created by me and tasks assigned to me' => 'Bara för uppgifter skapade av mig och tilldelade till mig', + '%%Y-%%m-%%d' => '%%Y-%%m-%%d', + 'Total for all columns' => 'Totalt för alla kolumner', + 'You need at least 2 days of data to show the chart.' => 'Du behöver minst två dagars data för att visa diagrammet.', + '<15m' => '<15m', + '<30m' => '<30m', + 'Stop timer' => 'Stoppa timer', + 'Start timer' => 'Starta timer', + 'My activity stream' => 'Min aktivitetsström', + 'Search tasks' => 'Sök uppgifter', + 'Reset filters' => 'Återställ filter', + 'My tasks due tomorrow' => 'Mina uppgifter förfaller imorgon', + 'Tasks due today' => 'Uppgifter förfaller idag', + 'Tasks due tomorrow' => 'Uppgifter förfaller imorgon', + 'Tasks due yesterday' => 'Uppgifter förföll igår', + 'Closed tasks' => 'Stängda uppgifter', + 'Open tasks' => 'Öppna uppgifter', + 'Not assigned' => 'Inte tilldelad', + 'View advanced search syntax' => 'Visa avancerad söksyntax', + 'Overview' => 'Översikt', + 'Board/Calendar/List view' => 'Tavelvy/kalendervy/listvy', + 'Switch to the board view' => 'Växla till tavelvy', + 'Switch to the list view' => 'Växla till listvy', + 'Go to the search/filter box' => 'Gå till sök/filter box', + 'There is no activity yet.' => 'Det finns ingen aktivitet ännu.', + 'No tasks found.' => 'Inga uppgifter hittades.', + 'Keyboard shortcut: "%s"' => 'Tangentbordsgenväg: "%s"', + 'List' => 'Lista', + 'Filter' => 'Filter', + 'Advanced search' => 'Avancerad sök', + 'Example of query: ' => 'Exempel på fråga', + 'Search by project: ' => 'Sök efter projekt:', + 'Search by column: ' => 'Sök efter kolumn:', + 'Search by assignee: ' => 'Sök efter tilldelad:', + 'Search by color: ' => 'Sök efter färg:', + 'Search by category: ' => 'Sök efter kategori:', + 'Search by description: ' => 'Sök efter beskrivning', + 'Search by due date: ' => 'Sök efter förfallodatum', + 'Average time spent in each column' => 'Medeltidsåtgång i varje kolumn', + 'Average time spent' => 'Medeltidsåtgång', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Diagramet visar medeltidsåtgång i varje kolumn för de senaste %d uppgifterna.', + 'Average Lead and Cycle time' => 'Medel av led och cykeltid', + 'Average lead time: ' => 'Medel av ledtid', + 'Average cycle time: ' => 'Medel av cykeltid', + 'Cycle Time' => 'Cykeltid', + 'Lead Time' => 'Ledtid', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Diagramet visar medel av led och cykeltid för de senaste %d uppgifterna över tiden.', + 'Average time into each column' => 'Medeltidsåtgång i varje kolumn', + 'Lead and cycle time' => 'Led och cykeltid', + 'Lead time: ' => 'Ledtid', + 'Cycle time: ' => 'Cykeltid', + 'Time spent in each column' => 'Tidsåtgång per kolumn', + 'The lead time is the duration between the task creation and the completion.' => 'Ledtiden är varaktigheten mellan uppgiftens skapande och slutförande.', + 'The cycle time is the duration between the start date and the completion.' => 'Cykeltiden är varaktigheten mellan uppgiftens startdatum och slut.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Om uppgiften inte är stängd används nuvarande tid istället för slutförandedatum.', + 'Set the start date automatically' => 'Sätt startdatum automatiskt', + 'Edit Authentication' => 'Ändra autentisering', + 'Remote user' => 'Fjärranvändare', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Fjärranvändares lösenord lagras inte i Kanboard-databasen, exempel: LDAP, Google och Github-konton.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Om du aktiverar boxen "Tillåt inte loginformulär" kommer inloggningsuppgifter i formuläret att ignoreras.', + 'Default task color' => 'Standardfärg för uppgifter', + 'This feature does not work with all browsers.' => 'Denna funktion fungerar inte i alla webbläsare.', + 'There is no destination project available.' => 'Det finns inget destinationsprojekt tillgängligt.', + 'Trigger automatically subtask time tracking' => 'Aktivera automatisk tidsbevakning av deluppgifter', + 'Include closed tasks in the cumulative flow diagram' => 'Inkludera stängda uppgifter i digrammet med kumulativt flöde', + 'Current swimlane: %s' => 'Nuvarande simbana: %s', + 'Current column: %s' => 'Nuvarande kolumn: %s', + 'Current category: %s' => 'Nuvarande kategori: %s', + 'no category' => 'ingen kategori', + 'Current assignee: %s' => 'Nuvarande tilldelning: %s', + 'not assigned' => 'inte tilldelad', + 'Author:' => 'Upphovsman:', + 'contributors' => 'medarbetare', + 'License:' => 'Licens:', + 'License' => 'Licens', + 'Enter the text below' => 'Fyll i texten nedan', + 'Start date:' => 'Startdatum:', + 'Due date:' => 'Slutdatum:', + 'People who are project managers' => 'Personer som är projektledare', + 'People who are project members' => 'Personer som är projektdeltagare', + 'NOK - Norwegian Krone' => 'NOK - Norsk krona', + 'Show this column' => 'Visa kolumn', + 'Hide this column' => 'Dölj kolumn', + 'End date' => 'Slutdatum', + 'Users overview' => 'Användaröversikt', + 'Members' => 'Medlemmar', + 'Shared project' => 'Delat projekt', + 'Project managers' => 'Projektledare', + 'Projects list' => 'Projektlista', + 'End date:' => 'Slutdatum:', + 'Change task color when using a specific task link' => 'Ändra uppgiftsfärg när en viss uppgiftslänk används', + 'Task link creation or modification' => 'Uppgiftslänk skapad eller ändrad', + 'Milestone' => 'Milstolpe', + 'Reset the search/filter box' => 'Återställ sökning/fitrering', + 'Documentation' => 'Dokumentation', + 'Author' => 'Skapad av', + 'Version' => 'Version', + 'Plugins' => 'Insticksprogram', + 'There is no plugin loaded.' => 'Inget insticksprogram har laddats', + 'My notifications' => 'Mina notifieringar', + 'Custom filters' => 'Anpassade filter', + 'Your custom filter has been created successfully.' => 'Ditt anpassade filter har skapats.', + 'Unable to create your custom filter.' => 'Ditt anpassade filter kunde inte skapas.', + 'Custom filter removed successfully.' => 'Ditt anpassade filter har raderats.', + 'Unable to remove this custom filter.' => 'Ditt anpassade filter kunde inte raderas.', + 'Edit custom filter' => 'Redigera anpassat filter', + 'Your custom filter has been updated successfully.' => 'Ditt anpassade filter har uppdaterats', + 'Unable to update custom filter.' => 'Kunde inte uppdatera anpassat filter', + 'Web' => 'Webb', + 'New attachment on task #%d: %s' => 'Ny bilaga i uppgift #%d: %s', + 'New comment on task #%d' => 'Ny kommentar i uppgift #%d', + 'Comment updated on task #%d' => 'Kommentar uppdaterad i uppgift #%d', + 'New subtask on task #%d' => 'Ny deluppgift till uppgift #%d', + 'Subtask updated on task #%d' => 'Deluppgift till uppgift #%d uppdaterad', + 'New task #%d: %s' => 'Ny uppgift #%d: %s', + 'Task updated #%d' => 'Uppgift uppdaterad #%d', + 'Task #%d closed' => 'Uppgift #%d stängd', + 'Task #%d opened' => 'Uppgift #%d öppnad', + 'Column changed for task #%d' => 'Byte av kolumn för uppgift #%d', + 'New position for task #%d' => 'Ny plats för uppgift #%d', + 'Swimlane changed for task #%d' => 'Simbana uppdaterad för uppgift #%d', + 'Assignee changed on task #%d' => 'Uppgiftsinnehavare ändrades på uppgift #%d', + '%d overdue tasks' => '%d försenade uppgifter', + 'No notification.' => 'Ingen notifiering', + 'Mark all as read' => 'Markera alla som lästa', + 'Mark as read' => 'Markera som läst', + 'Total number of tasks in this column across all swimlanes' => 'Totalt antal uppgifter i denna kolumnen över samtliga simbanor', + 'Collapse swimlane' => 'Fäll ihop simbana', + 'Expand swimlane' => 'Fäll ut simbana', + 'Add a new filter' => 'Lägg till nytt filter', + 'Share with all project members' => 'Dela med alla projektmedlemmar', + 'Shared' => 'Delad', + 'Owner' => 'Ägare', + 'Unread notifications' => 'Olästa notiser', + 'Notification methods:' => 'Notifieringsmetoder:', + 'Unable to read your file' => 'Kunde inte läsa din fil', + '%d task(s) have been imported successfully.' => '%d uppgift(er) har importerats.', + 'Nothing has been imported!' => 'Inget har importerats!', + 'Import users from CSV file' => 'Importera användare från CSV-fil', + '%d user(s) have been imported successfully.' => '%d användare har importerats.', + 'Comma' => 'Kommatecken', + 'Semi-colon' => 'Semikolon', + 'Tab' => 'Tabb', + 'Vertical bar' => 'Lodstreck', + 'Double Quote' => 'Dubbla citattecken', + 'Single Quote' => 'Enkla citattecken', + '%s attached a file to the task #%d' => '%s bifogade en fil till uppgift #%d', + 'There is no column or swimlane activated in your project!' => 'Ingen kolumn eller simbana har aktiverats i ditt projekt!', + 'Append filter (instead of replacement)' => 'Lägg till filter (istället för ersättning)', + 'Append/Replace' => 'Lägg till/ersätt', + 'Append' => 'Lägg till', + 'Replace' => 'Ersätt', + 'Import' => 'Importera', + 'Change sorting' => 'Ändra sortering', + 'Tasks Importation' => 'Importera uppgifter', + 'Delimiter' => 'Avskiljare', + 'Enclosure' => 'Bilaga', + 'CSV File' => 'CSV-fil', + 'Instructions' => 'Instruktioner', + 'Your file must use the predefined CSV format' => 'Din fil måste använda fördefinierat CSV-format', + 'Your file must be encoded in UTF-8' => 'Din fil måste teckenkodas med UTF-8', + 'The first row must be the header' => 'Första raden måste vara rubrik.', + 'Duplicates are not verified for you' => 'Dupliceringar verifieras inte för din användare', + 'The due date must use the ISO format: YYYY-MM-DD' => 'Förfallodatum måste använda ISO-datumformat: ÅÅÅÅ-MM-DD', + 'Download CSV template' => 'Ladda ner CSV-mall', + 'No external integration registered.' => 'Ingen extern integration registrerad.', + 'Duplicates are not imported' => 'Dubletter importeras inte', + 'Usernames must be lowercase and unique' => 'Användarnamn måste vara unika och endast bestå av gemener ', + 'Passwords will be encrypted if present' => 'Eventuella lösenord kommer krypteras', + '%s attached a new file to the task %s' => '%s bifogade en ny fil till uppgiften %s', + 'Link type' => 'Länktyp', + 'Assign automatically a category based on a link' => 'Tilldela kategori automatiskt baserat på en länk', + 'BAM - Konvertible Mark' => 'BAM - Konvertibla mark', + 'Assignee Username' => 'Uppdragsinnehavares användarnamn', + 'Assignee Name' => 'Uppdragsinnehavares namn', + 'Groups' => 'Grupper', + 'Members of %s' => 'Medlemmar i %s', + 'New group' => 'Ny grupp', + 'Group created successfully.' => 'Grupp skapades', + 'Unable to create your group.' => 'Kunde inte skapa gruppen', + 'Edit group' => 'Redigera grupp', + 'Group updated successfully.' => 'Gruppen uppdaterades.', + 'Unable to update your group.' => 'Kunde inte uppdatera gruppen', + 'Add group member to "%s"' => 'Lägg till gruppmedlem till "%s"', + 'Group member added successfully.' => 'Gruppmedlem lades till.', + 'Unable to add group member.' => 'Kunde inte lägga till gruppmedlem.', + 'Remove user from group "%s"' => 'Ta bort användare från grupp "%s"', + 'User removed successfully from this group.' => 'Användaren har tagits bort från gruppen.', + 'Unable to remove this user from the group.' => 'Kunde inte ta bort användaren från gruppen.', + 'Remove group' => 'Ta bort grupp', + 'Group removed successfully.' => 'Gruppen har raderats', + 'Unable to remove this group.' => 'Kunde inte radera gruppen.', + 'Project Permissions' => 'Projektbehörighet', + 'Manager' => 'Ledare', + 'Project Manager' => 'Projektägare', + 'Project Member' => 'Projektmedlem', + 'Project Viewer' => 'Projektläsare', + 'Your account is locked for %d minutes' => 'Ditt konto har låsts i %d minuter', + 'Invalid captcha' => 'Ogiltig captcha', + 'The name must be unique' => 'Namnet måste vara unikt', + 'View all groups' => 'Visa alla grupper', + 'There is no user available.' => 'Ingen användare tillgänglig', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Vill du verkligen ta bort användaren "%s" från gruppen "%s"?', + 'There is no group.' => 'Inga grupper existerar.', + 'Add group member' => 'Lägg till gruppmedlem', + 'Do you really want to remove this group: "%s"?' => 'Vill du verkligen ta bort gruppen: "%s"?', + 'There is no user in this group.' => 'Det finns inga användare i gruppen.', + 'Permissions' => 'Behörigheter', + 'Allowed Users' => 'Behöriga användare', + 'No specific user has been allowed.' => 'Ingen särskild användare har beviljats.', + 'Role' => 'Roll', + 'Enter user name...' => 'Mata in användarnamn...', + 'Allowed Groups' => 'Tillåtna grupper', + 'No group has been allowed.' => 'Ingen grupp har beviljats.', + 'Group' => 'Grupp', + 'Group Name' => 'Gruppnamn', + 'Enter group name...' => 'Mata in gruppnamn...', + 'Role:' => 'Roll:', + 'Project members' => 'Projektmedlemmar', + '%s mentioned you in the task #%d' => '%s nämnde dig i uppgift #%d', + '%s mentioned you in a comment on the task #%d' => '%s nämnde dig i en kommentar till uppgift #%d', + 'You were mentioned in the task #%d' => 'Du nämndes i uppgift #%d', + 'You were mentioned in a comment on the task #%d' => 'Du nämndes i en kommentar till uppgift #%d', + 'Estimated hours: ' => 'Uppskattat antal timmar', + 'Actual hours: ' => 'Verkligt antal timmar', + 'Hours Spent' => 'Upparbetade timmar', + 'Hours Estimated' => 'Uppskattat antal timmar', + 'Estimated Time' => 'Estimerad tid', + 'Actual Time' => 'Faktisk tid', + 'Estimated vs actual time' => 'Estimerad vs faktisk tid', + 'RUB - Russian Ruble' => 'RUB - Rysk rubel', + 'Assign the task to the person who does the action when the column is changed' => 'Tilldela uppgiften till personen som utför åtgärden när kolumnen ändras', + 'Close a task in a specific column' => 'Stäng en uppgift i en viss kolumn', + 'Time-based One-time Password Algorithm' => 'Tidsbaserade engångslösenord', + 'Two-Factor Provider: ' => 'Tvåfaktorsleverantör:', + 'Disable two-factor authentication' => 'Inaktivera tvåfaktorsautentisering', + 'Enable two-factor authentication' => 'Aktivera tvåfaktorsautentisering', + 'There is no integration registered at the moment.' => 'Ingen integration har registrerats.', + 'Password Reset for Kanboard' => 'Lösenordsåterställning för Kanboard', + 'Forgot password?' => 'Glömt lösenordet?', + 'Enable "Forget Password"' => 'Aktivera "Glömt lösenord"', + 'Password Reset' => 'Återställ lösenord', + 'New password' => 'Nytt lösenord', + 'Change Password' => 'Ändra lösenord', + 'To reset your password click on this link:' => 'För att ändra lösenord klicka på denna länk:', + 'Last Password Reset' => 'Senaste lösenordsåterställning', + 'The password has never been reinitialized.' => 'Lösenordet har aldrig nollställts.', + 'Creation' => 'Skapat', + 'Expiration' => 'Utgång', + 'Password reset history' => 'Historik för lösenordsåterställning', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Alla uppgifter i kolumnen "%s" samt simbanan "%s" har stängts.', + 'Do you really want to close all tasks of this column?' => 'Vill du verkligen stänga alla uppgifter i denna kolumn?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d uppgift(er) i kolumnen "%s" och simbanan "%s" kommer stängas.', + 'Close all tasks in this column and this swimlane' => 'Stäng alla uppgifter i denna kolumn', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Inget insticksprogram har registrerat notifieringsmetod för projektet. Du kan fortfarande konfigurera personliga notifikationer i din användarprofil.', + 'My dashboard' => 'Min översiktspanel', + 'My profile' => 'Min profil', + 'Project owner: ' => 'Projektägare: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'Identifierare för projektet är inte obligatorisk men måste vara alfanumerisk, t.ex.: AVDELNINGX', + 'Project owner' => 'Projektägare', + 'Personal projects do not have users and groups management.' => 'Privata projekt saknar hantering för användare och grupper.', + 'There is no project member.' => 'Det finns ingen projekmedlem.', + 'Priority' => 'Prioritet', + 'Task priority' => 'Uppgiftsprioritet', + 'General' => 'Allmänt', + 'Dates' => 'Datu', + 'Default priority' => 'Standardprioritet', + 'Lowest priority' => 'Låg prioritet', + 'Highest priority' => 'Hög prioritet', + 'Close a task when there is no activity' => 'Stäng uppgift utan aktivitet', + 'Duration in days' => 'Varaktighet i dagar', + 'Send email when there is no activity on a task' => 'Skicka e-post när aktivitet i en uppgift upphör', + 'Unable to fetch link information.' => 'Kunde inte hämta länkinformation.', + 'Daily background job for tasks' => 'Dagligt bakgrundsjobb för uppgifter', + 'Auto' => 'Auto', + 'Related' => 'Relaterat', + 'Attachment' => 'Bilaga', + 'Web Link' => 'Webblänk', + 'External links' => 'Externa länkar', + 'Add external link' => 'Skapa extern länk', + 'Type' => 'Typ', + 'Dependency' => 'Beroende', + 'Add internal link' => 'Lägg till intern länk', + 'Add a new external link' => 'Lägg till extern länk', + 'Edit external link' => 'Redigera extern länk', + 'External link' => 'Extern länk', + 'Copy and paste your link here...' => 'Klistra in länken här...', + 'URL' => 'URL', + 'Internal links' => 'Intern länk', + 'Assign to me' => 'Tilldela mig', + 'Me' => 'Jag', + 'Do not duplicate anything' => 'Kopiera inte', + 'Projects management' => 'Projekthantering', + 'Users management' => 'Användarhantering', + 'Groups management' => 'Grupphantering', + 'Create from another project' => 'Skapa från annat projekt', + 'open' => 'öppen', + 'closed' => 'stängd', + 'Priority:' => 'Prioritet:', + 'Reference:' => 'Referens:', + 'Complexity:' => 'Komplexitet:', + 'Swimlane:' => 'Simbana:', + 'Column:' => 'Kolumn', + 'Position:' => 'Position:', + 'Creator:' => 'Skapad av:', + 'Time estimated:' => 'Uppskattad tid:', + '%s hours' => '%s timmar', + 'Time spent:' => 'Spenderad tid:', + 'Created:' => 'Skapad:', + 'Modified:' => 'Ändrad', + 'Completed:' => 'Färdig:', + 'Started:' => 'Påbörjad:', + 'Moved:' => 'Flyttad:', + 'Task #%d' => 'Uppgift #%d', + 'Time format' => 'Tidsformat', + 'Start date: ' => 'Startdatum:', + 'End date: ' => 'Slutdatum: ', + 'New due date: ' => 'Nytt förfallodatum: ', + 'Start date changed: ' => 'Startdatum ändrat:', + 'Disable personal projects' => 'Inaktivera personliga projekt', + 'Do you really want to remove this custom filter: "%s"?' => 'Vill du verkligen ta bort anpassat filter: "%s"?', + 'Remove a custom filter' => 'Ta bort ett anpassat filter', + 'User activated successfully.' => 'Användare aktiverad.', + 'Unable to enable this user.' => 'Kunde inte aktivera användaren.', + 'User disabled successfully.' => 'Användaren inaktiverad.', + 'Unable to disable this user.' => 'Kunde inte inaktivera användaren.', + 'All files have been uploaded successfully.' => 'Samtliga filer har laddats upp.', + 'The maximum allowed file size is %sB.' => 'Maximal filstorlek är %sB.', + 'Drag and drop your files here' => 'Dra och släpp dina filer här', + 'choose files' => 'välj filer', + 'View profile' => 'Visa profil', + 'Two Factor' => 'Tvåfaktor', + 'Disable user' => 'Inaktivera användare', + 'Do you really want to disable this user: "%s"?' => 'Vill du verkligen inaktivera följande användare: "%s"?', + 'Enable user' => 'Aktivera användare', + 'Do you really want to enable this user: "%s"?' => 'Vill du verkligen aktivera följande användare: "%s"?', + 'Download' => 'Ladda ner', + 'Uploaded: %s' => 'Laddat upp: %s', + 'Size: %s' => 'Storlek: %s', + 'Uploaded by %s' => 'Uppladdat av %s', + 'Filename' => 'Filnamn', + 'Size' => 'Storlek', + 'Column created successfully.' => 'Kolumnen skapades.', + 'Another column with the same name exists in the project' => 'En annan kolumn med samma namn finns redan i projektet', + 'Default filters' => 'Standardfilter', + 'Your board doesn\'t have any columns!' => 'Tavlan har inga kolumner!', + 'Change column position' => 'Ändra kolumnposition', + 'Switch to the project overview' => 'Gå till projektöversikt', + 'User filters' => 'Användarfilter', + 'Category filters' => 'Kategorifilter', + 'Upload a file' => 'Ladda upp en fil', + 'View file' => 'Visa fil', + 'Last activity' => 'Senaste aktivitet', + 'Change subtask position' => 'Ändra position på deluppgift', + 'This value must be greater than %d' => 'Detta värde måste vara större än %d', + 'Another swimlane with the same name exists in the project' => 'En simbana med samma namn finns redan i projektet', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Exempel: https://example.kanboard.org/ (används för att generera absoluta URL:er)', + 'Actions duplicated successfully.' => 'Åtgärder duplicerades.', + 'Unable to duplicate actions.' => 'Kunde inte duplicera åtgärder.', + 'Add a new action' => 'Skapa en ny åtgärd', + 'Import from another project' => 'Importera från ett annat projekt', + 'There is no action at the moment.' => 'Det finns ingen åtgärd.', + 'Import actions from another project' => 'Importera åtgärder från ett annat projekt', + 'There is no available project.' => 'Inget tillgängligt projekt.', + 'Local File' => 'Lokal fil', + 'Configuration' => 'Konfiguration', + 'PHP version:' => 'PHP-version:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'OS-version:', + 'Database version:' => 'Databas-version:', + 'Browser:' => 'Webbläsare:', + 'Task view' => 'Uppgiftsvy', + 'Edit task' => 'Ändra uppgift', + 'Edit description' => 'Ändra beskrivning', + 'New internal link' => 'Ny intern länk', + 'Display list of keyboard shortcuts' => 'Visa lista av kortkommandon', + 'Avatar' => 'Avatar', + 'Upload my avatar image' => 'Ladda upp min avatar', + 'Remove my image' => 'Radera min bild', + 'The OAuth2 state parameter is invalid' => 'Tillståndsparametern till OAuth2 är ogiltig', + 'User not found.' => 'Användaren hittades inte.', + 'Search in activity stream' => 'Sök i aktivitetsström', + 'My activities' => 'Mina aktiviteter', + 'Activity until yesterday' => 'Aktivitet fram till igår', + 'Activity until today' => 'Aktivitet fram till idag', + 'Search by creator: ' => 'Sök på skapare: ', + 'Search by creation date: ' => 'Sök på skapandedatum: ', + 'Search by task status: ' => 'Sök på uppgiftsstatus: ', + 'Search by task title: ' => 'Sök på uppgiftstitel: ', + 'Activity stream search' => 'Sök i aktivitetsström', + 'Projects where "%s" is manager' => 'Projekt där "%s" är ledare', + 'Projects where "%s" is member' => 'Projekt där "%s" är medlem', + 'Open tasks assigned to "%s"' => 'Öppna uppgifter tilldelade till "%s"', + 'Closed tasks assigned to "%s"' => 'Stängda uppgifter tilldelade till "%s"', + 'Assign automatically a color based on a priority' => 'Tilldela färg baserat på prioritet automatiskt', + 'Overdue tasks for the project(s) "%s"' => 'Försenade uppgifter för projekt(en) "%s"', + 'Upload files' => 'Ladda upp filer', + 'Installed Plugins' => 'Installerade insticksprogram', + 'Plugin Directory' => 'Katalog för insticksprogram', + 'Plugin installed successfully.' => 'Insticksprogram installerat.', + 'Plugin updated successfully.' => 'Insticksprogram uppdaterat.', + 'Plugin removed successfully.' => 'Insticksprogram borttaget.', + 'Subtask converted to task successfully.' => 'Deluppgift konverterades till uppgift.', + 'Unable to convert the subtask.' => 'Kunde inte konvertera deluppgift.', + 'Unable to extract plugin archive.' => 'Kunde inte packa upp arkivet med insticksprogram.', + 'Plugin not found.' => 'Insticksprogram kunde inte hittas.', + 'You don\'t have the permission to remove this plugin.' => 'Du har inte behörighet att radera insticksprogrammet.', + 'Unable to download plugin archive.' => 'Kunde inte ladda ner arkiv med insticksprogram.', + 'Unable to write temporary file for plugin.' => 'Kunde inte skriva temporär fil för insticksprogram.', + 'Unable to open plugin archive.' => 'Kunde inte öppna arkiv med insticksprogram.', + 'There is no file in the plugin archive.' => 'Filer saknas i insticksprogramarkivet.', + 'Create tasks in bulk' => 'Skapa uppgifter i bulk', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Din instans av Kanboard är inte konfigurerad för att installera insticksprogram från användergränssnittet.', + 'There is no plugin available.' => 'Inget insticksprogram tillgängligt.', + 'Install' => 'Installera', + 'Update' => 'Uppdatera', + 'Up to date' => 'Uppdaterad', + 'Not available' => 'Inte tillgänglig', + 'Remove plugin' => 'Radera insticksprogram', + 'Do you really want to remove this plugin: "%s"?' => 'Vill du verkligen radera insticksprogram: "%s"?', + 'Uninstall' => 'Avinstallera', + 'Listing' => 'Listar', + 'Metadata' => 'Metadata', + 'Manage projects' => 'Hantera projekt', + 'Convert to task' => 'Konvertera till uppgift', + 'Convert sub-task to task' => 'Konvertera deluppgift till uppgift', + 'Do you really want to convert this sub-task to a task?' => 'Vill du verkligen konvertera deluppgiften till en uppgift?', + 'My task title' => 'Titel på min uppgift', + 'Enter one task by line.' => 'Fyll i en uppgift per rad.', + 'Number of failed login:' => 'Antal misslyckade inloggningar:', + 'Account locked until:' => 'Kontot låst till:', + 'Email settings' => 'E-postinställningar', + 'Email sender address' => 'E-postavsändare', + 'Email transport' => 'Transportval för e-post', + 'Webhook token' => 'Token för webhooks', + 'Project tags management' => 'Projektmärkningshantering', + 'Tag created successfully.' => 'Märkning skapades.', + 'Unable to create this tag.' => 'Kunde inte skapa märkning.', + 'Tag updated successfully.' => 'Märkning uppdaterad.', + 'Unable to update this tag.' => 'Kunde inte uppdatera märkning.', + 'Tag removed successfully.' => 'Märkning har tagits bort.', + 'Unable to remove this tag.' => 'Kunde inte ta bort märkningen.', + 'Global tags management' => 'Global märkningshantering', + 'Tags' => 'Märkningar', + 'Tags management' => 'Märkningshantering', + 'Add new tag' => 'Lägg till ny märkning', + 'Edit a tag' => 'Ändra en märkning', + 'Project tags' => 'Projektmärkningar', + 'There is no specific tag for this project at the moment.' => 'Det finns ingen märkning i projektet ännu.', + 'Tag' => 'Märkning', + 'Remove a tag' => 'Ta bort en märkning', + 'Do you really want to remove this tag: "%s"?' => 'Vill du verkligen ta bort märkningen "%s"?', + 'Global tags' => 'Globala märkningar', + 'There is no global tag at the moment.' => 'Det finns ingen global märkning ännu.', + 'This field cannot be empty' => 'Detta fältet kan inte lämnas tomt', + 'Close a task when there is no activity in a specific column' => 'Stäng en uppgift om aktivitet saknas i en viss kolumn', + '%s removed a subtask for the task #%d' => '%s tog bort en deluppgift i aktivitet #%d', + '%s removed a comment on the task #%d' => '%s tog bort en kommentar på aktivitet #%d', + 'Comment removed on task #%d' => 'Kommentar borttagen från aktivitet #%d', + 'Subtask removed on task #%d' => 'Deluppgift borttagen från aktivitet #%d', + 'Hide tasks in this column in the dashboard' => 'Göm uppgifter i denna kolumn på översiktspanelen', + '%s removed a comment on the task %s' => '%s tog bort en kommentar från uppgiften %s', + '%s removed a subtask for the task %s' => '%s tog bort en deluppgift från uppgiften %s', + 'Comment removed' => 'Kommentar borttagen', + 'Subtask removed' => 'Deluppgift borttagen', + '%s set a new internal link for the task #%d' => '%s tilldelade en ny intern länk till uppgift #%d', + '%s removed an internal link for the task #%d' => '%s tog bort en intern länk från uppgift #%d', + 'A new internal link for the task #%d has been defined' => 'En ny intern länk har skapats till uppgift #%d', + 'Internal link removed for the task #%d' => 'Intern länk togs bort från uppgift #%d', + '%s set a new internal link for the task %s' => '%s tilldelade en ny intern länk till uppgiften %s', + '%s removed an internal link for the task %s' => '%s tog bort en intern länk från uppgiften %s', + 'Automatically set the due date on task creation' => 'Sätt förfallodatum automatiskt vid skapande av uppgift', + 'Move the task to another column when closed' => 'Flytta uppgiften till en annan kolumn när den stängs', + 'Move the task to another column when not moved during a given period' => 'Flytta uppgiften till en annan kolumn när den inte flyttats under en given tidsperiod', + 'Dashboard for %s' => 'Översiktspanel för %s', + 'Tasks overview for %s' => 'Uppgiftsöversikt för %s', + 'Subtasks overview for %s' => 'Deluppgiftsöversikt för %s', + 'Projects overview for %s' => 'Projektöversikt för %s', + 'Activity stream for %s' => 'Aktivitetsström för %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Tilldela en färg till uppgiften när den flyttas till en viss simbana', + 'Assign a priority when the task is moved to a specific swimlane' => 'Tilldela en prioritet till uppgiften när den flyttas till en viss simbana', + 'User unlocked successfully.' => 'Användare upplåst.', + 'Unable to unlock the user.' => 'Kunde inte låsa upp användare.', + 'Move a task to another swimlane' => 'Flytta en uppgift till en annan simbana', + 'Creator Name' => 'Namn på skapare', + 'Time spent and estimated' => 'Förbrukad och uppskattad tid', + 'Move position' => 'Flytta position', + 'Move task to another position on the board' => 'Flytta uppgiften till en annan position på tavlan', + 'Insert before this task' => 'Infoga före denna uppgift', + 'Insert after this task' => 'Infoga efter denna uppgift', + 'Unlock this user' => 'Lås upp denna användare', + 'Custom Project Roles' => 'Anpassade projektroller', + 'Add a new custom role' => 'Skapa ny anpassad roll', + 'Restrictions for the role "%s"' => 'Restriktioner för roll "%s"', + 'Add a new project restriction' => 'Lägg till ny projektrestriktion', + 'Add a new drag and drop restriction' => 'Lägg till ny dra-och-släpprestriktion', + 'Add a new column restriction' => 'Lägg till ny kolumnrestriktion', + 'Edit this role' => 'Ändra denna rollen', + 'Remove this role' => 'Ta bort denna rollen', + 'There is no restriction for this role.' => 'Det finns inga restriktioner för denna rollen', + 'Only moving task between those columns is permitted' => 'Endast tillåtet att flytta uppgifter mellan dessa kolumner', + 'Close a task in a specific column when not moved during a given period' => 'Stäng en uppgift i en specifik kolumn om den inte flyttas under en viss period', + 'Edit columns' => 'Redigera kolumner', + 'The column restriction has been created successfully.' => 'Kolumnbegränsningen har skapats.', + 'Unable to create this column restriction.' => 'Det gick inte att skapa denna kolumnbegränsning.', + 'Column restriction removed successfully.' => 'Kolumnbegränsningen har tagits bort.', + 'Unable to remove this restriction.' => 'Det gick inte att ta bort denna begränsning.', + 'Your custom project role has been created successfully.' => 'Din anpassade projektroll har skapats.', + 'Unable to create custom project role.' => 'Det gick inte att skapa anpassad projektroll.', + 'Your custom project role has been updated successfully.' => 'Din anpassade projektroll har uppdaterats.', + 'Unable to update custom project role.' => 'Det gick inte att uppdatera anpassad projektroll.', + 'Custom project role removed successfully.' => 'Anpassad projektroll har tagits bort.', + 'Unable to remove this project role.' => 'Det gick inte att ta bort denna projektroll.', + 'The project restriction has been created successfully.' => 'Projektbegränsningen har skapats.', + 'Unable to create this project restriction.' => 'Det gick inte att skapa denna projektbegränsning.', + 'Project restriction removed successfully.' => 'Projektbegränsningen har tagits bort.', + 'You cannot create tasks in this column.' => 'Du kan inte skapa uppgifter i denna kolumn.', + 'Task creation is permitted for this column' => 'Uppgiftsskapande är tillåtet för denna kolumn', + 'Closing or opening a task is permitted for this column' => 'Att stänga eller öppna en uppgift är tillåtet för denna kolumn', + 'Task creation is blocked for this column' => 'Uppgiftsskapande är blockerat för denna kolumn', + 'Closing or opening a task is blocked for this column' => 'Att stänga eller öppna en uppgift är blockerat för denna kolumn', + 'Task creation is not permitted' => 'Uppgiftsskapande är inte tillåtet', + 'Closing or opening a task is not permitted' => 'Att stänga eller öppna en uppgift är inte tillåtet', + 'New drag and drop restriction for the role "%s"' => 'Ny dra-och-släpp-begränsning för rollen "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Personer med denna roll kan endast flytta uppgifter mellan käll- och destinationskolumnen.', + 'Remove a column restriction' => 'Ta bort en kolumnbegränsning', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Vill du verkligen ta bort denna kolumnbegränsning: "%s" till "%s"?', + 'New column restriction for the role "%s"' => 'Ny kolumnbegränsning för rollen "%s"', + 'Rule' => 'Regel', + 'Do you really want to remove this column restriction?' => 'Vill du verkligen ta bort denna kolumnbegränsning?', + 'Custom roles' => 'Anpassade roller', + 'New custom project role' => 'Ny anpassad projektroll', + 'Edit custom project role' => 'Redigera anpassad projektroll', + 'Remove a custom role' => 'Ta bort en anpassad roll', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Vill du verkligen ta bort denna anpassade roll: "%s"? Alla personer tilldelade denna roll kommer att bli projektmedlemmar.', + 'There is no custom role for this project.' => 'Det finns ingen anpassad roll för detta projekt.', + 'New project restriction for the role "%s"' => 'Ny projektrestriktion för roll "%s"', + 'Restriction' => 'Restriktion', + 'Remove a project restriction' => 'Ta bort en projektrestriktion', + 'Do you really want to remove this project restriction: "%s"?' => 'Vill du verkligen ta bort projektrestriktion: "%s"?', + 'Duplicate to multiple projects' => 'Duplicera till multipla projekt', + 'This field is required' => 'Detta fält måste vara ifyllt', + 'Moving a task is not permitted' => 'Flytt av uppgift ej tillåten', + 'This value must be in the range %d to %d' => 'Värdet måste vara mellan %d och %d', + 'You are not allowed to move this task.' => 'Du har inte behörighet att flytta uppgiften.', + 'API User Access' => 'Användarbehörighet för API', + 'Preview' => 'Förhandsvisning', + 'Write' => 'Skriv', + 'Write your text in Markdown' => 'Uppgiftsbeskrivning i Markdown-format', + 'No personal API access token registered.' => 'Ingen personlig API-token registrerad.', + 'Your personal API access token is "%s"' => 'Din personliga API-token är "%s"', + 'Remove your token' => 'Radera din token', + 'Generate a new token' => 'Generera ny token', + 'Showing %d-%d of %d' => 'Visar %d-%d av %d', + 'Outgoing Emails' => 'Utgående e-post', + 'Add or change currency rate' => 'Lägg till eller ändra valutakurs', + 'Reference currency: %s' => 'Referensvaluta: %s', + 'Add custom filters' => 'Lägg till anpassade filter', + 'Export' => 'Exportera', + 'Add link label' => 'Lägg till länketikett', + 'Incompatible Plugins' => 'Okompatibla insticksprogram', + 'Compatibility' => 'Kompatibilitet', + 'Permissions and ownership' => 'Behörighet och ägarskap', + 'Priorities' => 'Prioriteringar', + 'Close this window' => 'Stäng detta fönster', + 'Unable to upload this file.' => 'Kunde inte ladda upp filen.', + 'Import tasks' => 'Importera uppgifter', + 'Choose a project' => 'Välj ett projekt', + 'Profile' => 'Profil', + 'Application role' => 'Applikationsroll', + '%d invitations were sent.' => '%d inbjudningar skickades.', + '%d invitation was sent.' => '%d inbjudan skickades.', + 'Unable to create this user.' => 'Kunde inte skapa användaren.', + 'Kanboard Invitation' => 'Kanboard-inbjudan', + 'Visible on dashboard' => 'Synlig på översiktspanel', + 'Created at:' => 'Skapad vid:', + 'Updated at:' => 'Uppdaterad vid;', + 'There is no custom filter.' => 'Det finns inget anpassat filter.', + 'New User' => 'Ny användare', + 'Authentication' => 'Autentisering', + 'If checked, this user will use a third-party system for authentication.' => 'Vid ikryssning får denna användare använda ett tredjepartssystem för autentisering.', + 'The password is necessary only for local users.' => 'Lösenordet är endast nödvändigt för lokala användare.', + 'You have been invited to register on Kanboard.' => 'Du har blivit inbjuden att registrera dig på Kanboard', + 'Click here to join your team' => 'Klicka här för att gå med i gruppen', + 'Invite people' => 'Bjud in', + 'Emails' => 'E-brev', + 'Enter one email address by line.' => 'Skriv en e-postadress per rad.', + 'Add these people to this project' => 'Lägg till dessa personer till projektet', + 'Add this person to this project' => 'Lägg till denna person till projektet', + 'Sign-up' => 'Registrera', + 'Credentials' => 'Inloggningsuppgifter', + 'New user' => 'Ny användare', + 'This username is already taken' => 'Detta användarnamnet är redan upptaget', + 'Your profile must have a valid email address.' => 'Din profil måste ha en giltig e-postadress.', + 'TRL - Turkish Lira' => 'TRL - Turkisk lira', + 'The project email is optional and could be used by several plugins.' => 'Att ange en e-post för projektet är frivilligt och kan användas av flera insticksmoduler.', + 'The project email must be unique across all projects' => 'Projektets e-post måste vara unik för projektet', + 'The email configuration has been disabled by the administrator.' => 'E-postkonfigurationen har inaktiverats av administratören.', + 'Close this project' => 'Stäng projektet', + 'Open this project' => 'Öppna projektet', + 'Close a project' => 'Stäng ett projekt', + 'Do you really want to close this project: "%s"?' => 'Vill du verkligen stänga projektet "%s"?', + 'Reopen a project' => 'Återöppna ett projekt', + 'Do you really want to reopen this project: "%s"?' => 'Vill du verkligen återöppna projektet "%s"?', + 'This project is open' => 'Projektet är öppet', + 'This project is closed' => 'Projektet är stängt', + 'Unable to upload files, check the permissions of your data folder.' => 'Kunde inte ladda upp filer. Kontrollera rättigheterna på din datakatalog.', + 'Another category with the same name exists in this project' => 'En annan kategori med samma namn finns redan i projektet', + 'Comment sent by email successfully.' => 'Kommentar skickad via e-post.', + 'Sent by email to "%s" (%s)' => 'Skickad via e-post till "%s" (%s)', + 'Unable to read uploaded file.' => 'Kan inte läsa uppladdad fil.', + 'Database uploaded successfully.' => 'Databas uppladdad.', + 'Task sent by email successfully.' => 'Uppgifter skickade via mail.', + 'There is no category in this project.' => 'Det finns ingen kategori i detta projektet.', + 'Send by email' => 'Skicka via e-post', + 'Create and send a comment by email' => 'Skapa och skicka en kommentar via e-post', + 'Subject' => 'Ärende', + 'Upload the database' => 'Ladda upp databasen', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Du kan ladda upp den tidigare nedladdade Sqlite-databasen (i Gzip-format).', + 'Database file' => 'Databasfil', + 'Upload' => 'Ladda upp', + 'Your project must have at least one active swimlane.' => 'Ditt projekt måste ha åtminstone en aktiv simbana.', + 'Project: %s' => 'Projekt: %s', + 'Automatic action not found: "%s"' => 'Automatisk åtgärd kunde inte hittas: "%s"', + '%d projects' => '%d projekt', + '%d project' => '%d projekt', + 'There is no project.' => 'Det finns inget projekt', + 'Sort' => 'Sortera', + 'Project ID' => 'Projekt-id', + 'Project name' => 'Projektnamn', + 'Public' => 'Offentlig', + 'Personal' => 'Personlig', + '%d tasks' => '%d uppgifter', + '%d task' => '%d uppgift', + 'Task ID' => 'Uppgift-id', + 'Assign automatically a color when due date is expired' => 'Tilldela en färg automatiskt när förfallodatum har passerat', + 'Total score in this column across all swimlanes' => 'Total poäng i denna kolumn över samtliga simbanor', + 'HRK - Kuna' => 'HRK - kuna', + 'ARS - Argentine Peso' => 'ARS - Argentisk peso', + 'COP - Colombian Peso' => 'COP - Colombiansk peso', + '%d groups' => '%d grupper', + '%d group' => '%d grupp', + 'Group ID' => 'Grupp-id', + 'External ID' => 'Externt id', + '%d users' => '%d användare', + '%d user' => '%d användare', + 'Hide subtasks' => 'Göm deluppgifter', + 'Show subtasks' => 'Visa deluppgifter', + 'Authentication Parameters' => 'Autentiseringsparametrar', + 'API Access' => 'API-tillgång', + 'No users found.' => 'Inga användare hittades.', + 'User ID' => 'Användar-id', + 'Notifications are activated' => 'Notifieringar är aktiverade', + 'Notifications are disabled' => 'Notifieringar är inaktiverade', + 'User disabled' => 'Användare inaktiverad', + '%d notifications' => '%d notifieringar', + '%d notification' => '%d notifiering', + 'There is no external integration installed.' => 'Ingen extern integration har installerats.', + 'You are not allowed to update tasks assigned to someone else.' => 'Du har inte tillåtelse att uppdatera uppgifter som är tilldelade till någon annan.', + 'You are not allowed to change the assignee.' => 'Du har inte behörighet att ändra uppgiftstilldelning.', + 'Task suppression is not permitted' => 'Borttagning av uppgift ej tillåten', + 'Changing assignee is not permitted' => 'Ändring av uppgiftstilldelning är inte tillåten', + 'Update only assigned tasks is permitted' => 'Endast ändring av tilldelade uppgifter tillåten', + 'Only for tasks assigned to the current user' => 'Endast för uppgifter tilldelade till nuvarande användare', + 'My projects' => 'Mina projekt', + 'You are not a member of any project.' => 'Du är inte medlem i något projekt.', + 'My subtasks' => 'Mina deluppgifter', + '%d subtasks' => '%d deluppgifter', + '%d subtask' => '%d deluppgift', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Endast förflyttning mellan dessa kolumner är tillåten för uppgifter som är tilldelade den nuvarande användaren', + '[DUPLICATE]' => '[DUPLICERAD]', + 'DKK - Danish Krona' => 'DKK - Dansk krona', + 'Remove user from group' => 'Ta bort användare från grupp', + 'Assign the task to its creator' => 'Tilldela uppgiften till sin skapare', + 'This task was sent by email to "%s" with subject "%s".' => 'Uppgiften skickades per e-post till "%s" med titeln "%s".', + 'Predefined Email Subjects' => 'Fördefinierade e-postmottagare', + 'Write one subject by line.' => 'Skriv en titel per rad.', + 'Create another link' => 'Skapa ytterligare en länk', + 'BRL - Brazilian Real' => 'BRL - Brasiliansk real', + 'Add a new Kanboard task' => 'Skapa en ny Kanboard-uppgift', + 'Subtask not started' => 'Deluppgift ej påbörjad', + 'Subtask currently in progress' => 'Deluppgift pågående', + 'Subtask completed' => 'Deluppgift färdig', + 'Subtask added successfully.' => 'Deluppgift tillagd.', + '%d subtasks added successfully.' => '%d deluppgifter tillagda.', + 'Enter one subtask by line.' => 'Skriv en deluppgift per rad.', + 'Predefined Contents' => 'Fördefinierat innehåll', + 'Predefined contents' => 'Fördefinierat innehåll', + 'Predefined Task Description' => 'Förderinierad uppgiftsbeskrivning', + 'Do you really want to remove this template? "%s"' => 'Vill du verkligen ta bort mallen "%s"?', + 'Add predefined task description' => 'Skapa fördefinierad uppgiftsbeskrivning', + 'Predefined Task Descriptions' => 'Fördefinierad uppgiftsbeskrivningar', + 'Template created successfully.' => 'Mallen skapades.', + 'Unable to create this template.' => 'Kunde inte skapa mallen.', + 'Template updated successfully.' => 'Mallen uppdaterades.', + 'Unable to update this template.' => 'Kunde inte uppdatera mallen.', + 'Template removed successfully.' => 'Mallen togs bort.', + 'Unable to remove this template.' => 'Kunde inte ta bort mallen.', + 'Template for the task description' => 'Mall för uppgiftsbeskrivning', + 'The start date is greater than the end date' => 'Startdatum är senare än slutdatum', + 'Tags must be separated by a comma' => 'Märkningar måste vara kommaseparerade', + 'Only the task title is required' => 'Endast märkningens titel krävs', + 'Creator Username' => 'Skapares användarnamn', + 'Color Name' => 'Färgnamn', + 'Column Name' => 'Kolumnnamn', + 'Swimlane Name' => 'Simbanans namn', + 'Time Estimated' => 'Estimerad tid', + 'Time Spent' => 'Spenderad tid', + 'External Link' => 'Extern länk', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Denna funktion aktiverar iCal-flöde, RSS-flöde, samt offentlig tavelvy.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Stoppa timern för samtliga deluppgifter när en uppgift flyttas till en annan kolumn', + 'Subtask Title' => 'Deluppgiftstitel', + 'Add a subtask and activate the timer when moving a task to another column' => 'Lägg till en deluppgift och aktivera timern när en uppgift flyttas till en annan kolumn', + 'days' => 'dagar', + 'minutes' => 'minuter', + 'seconds' => 'sekunder', + 'Assign automatically a color when preset start date is reached' => 'Tilldela automatiskt en färg när förvalt startdatum infaller', + 'Move the task to another column once a predefined start date is reached' => 'Flytta uppgiften till en annan kolumn när ett fördefinierat startdatum infaller', + 'This task is now linked to the task %s with the relation "%s"' => 'Denna uppgift är nu länkad till uppgiften "%s" via relationen "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'Länken med relationen "%s" till uppgiften "%s" har tagits bort', + 'Custom Filter:' => 'Anpassat filter:', + 'Unable to find this group.' => 'Kunde inte hitta gruppen.', + '%s moved the task #%d to the column "%s"' => '%s flyttade uppgift #%d till kolumnen "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => '%s flyttade uppgift #%d till till position %d i kolumnen "%s"', + '%s moved the task #%d to the swimlane "%s"' => '%s flyttade uppgift #%d till simbanan "%s"', + '%sh spent' => '%stim. förbrukat', + '%sh estimated' => '%stim. uppskattat', + 'Select All' => 'Välj alla', + 'Unselect All' => 'Avmarkera alla', + 'Apply action' => 'Genomför åtgärd', + 'Move selected tasks to another column or swimlane' => 'Flytta markerade uppgifter till en annan kolumn', + 'Edit tasks in bulk' => 'Massredigera uppgifer', + 'Choose the properties that you would like to change for the selected tasks.' => 'Välj vilka egenskaper som du vill ändra för de valda uppgifterna.', + 'Configure this project' => 'Konfigurera projekt', + 'Start now' => 'Börja nu', + '%s removed a file from the task #%d' => '%s tog bort en fil från uppgift #%d', + 'Attachment removed from task #%d: %s' => 'Bilaga borttagen från uppgift #%d: %s', + 'No color' => 'Ingen färg', + 'Attachment removed "%s"' => 'Bilaga "%s" borttagen', + '%s removed a file from the task %s' => '%s tog bort en fil från uppgiften %s', + 'Move the task to another swimlane when assigned to a user' => 'Flytta uppgiften till en annan simbana när den tilldelas en användare', + 'Destination swimlane' => 'Destinationssimbana', + 'Assign a category when the task is moved to a specific swimlane' => 'Tilldela en kategori när uppgiften flyttas till en specifik simbana', + 'Move the task to another swimlane when the category is changed' => 'Flytta uppgiften till en annan simbana när kategorin ändras', + 'Reorder this column by priority (ASC)' => 'Ändra ordning i denna kolumnen enligt prioritet (stigande)', + 'Reorder this column by priority (DESC)' => 'Ändra ordning i denna kolumnen enligt prioritet (fallande)', + 'Reorder this column by assignee and priority (ASC)' => 'Ändra ordning i denna kolumnen enligt tilldelning och prioritet (stigande)', + 'Reorder this column by assignee and priority (DESC)' => 'Ändra ordning i denna kolumnen enligt tilldelning och prioritet (fallande)', + 'Reorder this column by assignee (A-Z)' => 'Ändra ordning i denna kolumnen enligt tilldelning (A-Z)', + 'Reorder this column by assignee (Z-A)' => 'Ändra ordning i denna kolumnen enligt tilldelning (Z-A)', + 'Reorder this column by due date (ASC)' => 'Ändra ordning i denna kolumnen enligt förfallodatum (stigande)', + 'Reorder this column by due date (DESC)' => 'Ändra ordning i denna kolumnen enligt förfallodatum (fallande)', + 'Reorder this column by id (ASC)' => 'Sortera denna kolumn efter ID (stigande)', + 'Reorder this column by id (DESC)' => 'Sortera denna kolumn efter ID (fallande)', + '%s moved the task #%d "%s" to the project "%s"' => '%s flyttade uppgift #%d "%s" till projektet "%s"', + 'Task #%d "%s" has been moved to the project "%s"' => 'Uppgift #%d "%s" har flyttats till projektet "%s"', + 'Move the task to another column when the due date is less than a certain number of days' => 'Flytta uppgiften till en annan kolumn när förfallodatum är närmare än ett visst antal dagar', + 'Automatically update the start date when the task is moved away from a specific column' => 'Uppdatera startdatum automatiskt när en uppgift flyttas från en specifik kolumn', + 'HTTP Client:' => 'HTTP-klient;', + 'Assigned' => 'Tilldelad', + 'Task limits apply to each swimlane individually' => 'Uppgiftsbegränsningar tillämpas på varje simbana individuellt', + 'Column task limits apply to each swimlane individually' => 'Kolumners uppgiftsbegränsning tillämpas på varje simbana individuellt', + 'Column task limits are applied to each swimlane individually' => 'Kolumners uppgiftsbegränsning tillämpas på varje simbana individuellt', + 'Column task limits are applied across swimlanes' => 'Kolumners uppgiftsbegränsning tillämpas över simbanor', + 'Task limit: ' => 'Uppgiftsgräns: ', + 'Change to global tag' => 'Ändra till global märkning', + 'Do you really want to make the tag "%s" global?' => 'Vill du verkligen göra märkningen "%s" global?', + 'Enable global tags for this project' => 'Aktivera global märkning för detta projekt', + 'Group membership(s):' => 'Gruppmedlemskap:', + '%s is a member of the following group(s): %s' => '%s är medlem i följande grupp(er): %s', + '%d/%d group(s) shown' => '%d/%d grupp(er) visade', + 'Subtask creation or modification' => 'Delupupgifts skapande eller ändrande', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Tilldela uppgiften till en viss användare när uppgiften flyttas till en viss swimlane', + 'Comment' => 'Kommentar', + 'Collapse vertically' => 'Fäll ihop vertikalt', + 'Expand vertically' => 'Fäll ut vertikalt', + 'MXN - Mexican Peso' => 'MXN - Mexikansk peso', + 'Estimated vs actual time per column' => 'Uppskattad kontra faktisk tid per kolumn', + 'HUF - Hungarian Forint' => 'HUF - Ungersk forint', + 'XBT - Bitcoin' => 'XBT – Bitcoin', + 'You must select a file to upload as your avatar!' => 'Du måste välja en fil att uppladda som din avatar!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'Filen du laddade upp är inte ett giltigt bildformat! (Endast *.gif *.jpg *.jpeg samt *.png är tillåtet!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Ställ automatiskt in förfallodatum när uppgiften flyttas från en specifik kolumn', + 'No other projects found.' => 'Inga andra projekt hittades.', + 'Tasks copied successfully.' => 'Uppgifter kopierades framgångsrikt.', + 'Unable to copy tasks.' => 'Det gick inte att kopiera uppgifter.', + 'Theme' => 'Tema', + 'Theme:' => 'Tema:', + 'Light theme' => 'Ljust tema', + 'Dark theme' => 'Mörkt tema', + 'Automatic theme - Sync with system' => 'Automatiskt tema – synkronisera med systemet', + 'Application managers or more' => 'Applikationsansvariga eller högre', + 'Administrators' => 'Administratörer', + 'Visibility:' => 'Synlighet:', + 'Standard users' => 'Standardanvändare', + 'Visibility is required' => 'Synlighet krävs', + 'The visibility should be an app role' => 'Synligheten bör vara en applikationsroll', + 'Reply' => 'Svara', + '%s wrote: ' => '%s skrev: ', + 'Number of visible tasks in this column and swimlane' => 'Antal synliga uppgifter i denna kolumn och simlinje', + 'Number of tasks in this swimlane' => 'Antal uppgifter i denna simlinje', + 'Unable to find another subtask in progress, you can close this window.' => 'Kunde inte hitta en annan deluppgift i gång, du kan stänga detta fönster.', + 'This theme is invalid' => 'Detta tema är ogiltigt', + 'This role is invalid' => 'Denna roll är ogiltig', + 'This timezone is invalid' => 'Denna tidszon är ogiltig', + 'This language is invalid' => 'Detta språk är ogiltigt', + 'This URL is invalid' => 'Denna URL är ogiltig', + 'Date format invalid' => 'Ogiltigt datumformat', + 'Time format invalid' => 'Ogiltigt tidsformat', + 'Invalid Mail transport' => 'Ogiltig e-posttransport', + 'Color invalid' => 'Ogiltig färg', + 'This value must be greater or equal to %d' => 'Detta värde måste vara större än eller lika med %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Lägg till en BOM i början av filen (krävs för Microsoft Excel)', + 'Just add these tag(s)' => 'Lägg bara till dessa taggar', + 'Remove internal link(s)' => 'Ta bort interna länkar', + 'Import tasks from another project' => 'Importera uppgifter från ett annat projekt', + 'Select the project to copy tasks from' => 'Välj projektet att kopiera uppgifter från', + 'The total maximum allowed attachments size is %sB.' => 'Den totala maximalt tillåtna bilagestorleken är %sB.', + 'Add attachments' => 'Lägg till bilagor', + 'Task #%d "%s" is overdue' => 'Uppgift #%d "%s" är försenad', + 'Enable notifications by default for all new users' => 'Aktivera notifieringar som standard för alla nya användare', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Tilldela uppgiften till dess skapare för specifika kolumner om ingen ansvarig är satt manuellt', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Tilldela en uppgift till den inloggade användaren vid kolumnbyte till angiven kolumn om ingen användare är tilldelad', +]; diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php new file mode 100644 index 0000000..600ba6a --- /dev/null +++ b/app/Locale/th_TH/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => '.', + 'number.thousands_separator' => ',', + 'None' => 'ไม่มี', + 'Edit' => 'แก้ไข', + 'Remove' => 'ลบ', + 'Yes' => 'ใช่', + 'No' => 'ไม่', + 'cancel' => 'ยกเลิก', + 'or' => 'หรือ', + 'Yellow' => 'สีเหลือง', + 'Blue' => 'สีน้ำเงิน', + 'Green' => 'สีเขียว', + 'Purple' => 'สีม่วง', + 'Red' => 'สีแดง', + 'Orange' => 'สีส้ม', + 'Grey' => 'สีเทา', + 'Brown' => 'สีน้ำตาล', + 'Deep Orange' => 'สีส้มเข้ม', + 'Dark Grey' => 'สีเทาเข้ม', + 'Pink' => 'สีชมพู', + 'Teal' => 'สีเขียวหัวเป็ด', + 'Cyan' => 'สีฟ้า', + 'Lime' => 'สีมะนาว', + 'Light Green' => 'สีเขียวสว่าง', + 'Amber' => 'สีเหลืองอำพัน', + 'Save' => 'บันทึก', + 'Login' => 'เข้าสู่ระบบ', + 'Official website:' => 'เวบไซต์อย่างเป็นทางการ:', + 'Unassigned' => 'ไม่กำหนด', + 'View this task' => 'รายละเอียดงานนี้', + 'Remove user' => 'เอาผู้ใช้ออก', + 'Do you really want to remove this user: "%s"?' => 'คุณต้องการเอาผู้ใช้ « %s » ออกใช่หรือไม่?', + 'All users' => 'ผู้ใช้ทั้งหมด', + 'Username' => 'ชื่อผู้ใช้', + 'Password' => 'รหัสผ่าน', + 'Administrator' => 'ผู้ดูแลระบบ', + 'Sign in' => 'เข้าสู่ระบบ', + 'Users' => 'ผู้ใช้', + 'Forbidden' => 'ไม่อนุญาต', + 'Access Forbidden' => 'ไม่อนุญาตให้เข้า', + 'Edit user' => 'แก้ไขผู้ใช้', + 'Logout' => 'ออกจากระบบ', + 'Bad username or password' => 'ชื่อผู้ใช้หรือรหัสผ่านผิด', + 'Edit project' => 'แก้ไขโปรเจค', + 'Name' => 'ชื่อ', + 'Projects' => 'โปรเจค', + 'No project' => 'ไม่มีโปรเจค', + 'Project' => 'โปรเจค', + 'Status' => 'สถานะ', + 'Tasks' => 'งาน', + 'Board' => 'บอร์ด', + 'Actions' => 'การกระทำ', + 'Inactive' => 'ไม่เปิดใช้งาน', + 'Active' => 'เปิดใช้งาน', + 'Unable to update this board.' => 'ไม่สามารถปรับปรุงบอร์ดได้.', + 'Disable' => 'ปิดการทำงาน', + 'Enable' => 'เปิดการทำงาน', + 'New project' => 'โปรเจคใหม่', + 'Do you really want to remove this project: "%s"?' => 'คุณต้องการเอาโปรเจค « %s » ออกใช่หรือไม่?', + 'Remove project' => 'ลบโปรเจค', + 'Edit the board for "%s"' => 'แก้ไขบอร์ดสำหรับ « %s »', + 'Add a new column' => 'เพิ่มคอลัมน์ใหม่', + 'Title' => 'หัวเรื่อง', + 'Assigned to %s' => 'กำหนดให้ %s', + 'Remove a column' => 'ลบคอลัมน์', + 'Unable to remove this column.' => 'ไม่สามารถลบคอลัมน์นี้', + 'Do you really want to remove this column: "%s"?' => 'คุณต้องการลบคอลัมน์ « %s » ออกใช่หรือไม่?', + 'Settings' => 'ตั้งค่า', + 'Application settings' => 'ตั้งค่าการทำงาน', + 'Language' => 'ภาษา', + 'Webhook token:' => 'โทเค็น Webhook:', + 'API token:' => 'API token:', + 'Database size:' => 'ขนาดฐานข้อมูล:', + 'Download the database' => 'ดาวน์โหลดฐานข้อมูล', + 'Optimize the database' => 'ปรับปรุงฐานข้อมูล', + '(VACUUM command)' => '(VACUUM command)', + '(Gzip compressed Sqlite file)' => '(Gzip compressed Sqlite file)', + 'Close a task' => 'ปิดงาน', + 'Column' => 'คอลัมน์', + 'Color' => 'สี', + 'Assignee' => 'กำหนดให้', + 'Create another task' => 'สร้างงานอื่น', + 'New task' => 'งานใหม่', + 'Open a task' => 'เปิดงาน', + 'Do you really want to open this task: "%s"?' => 'คุณต้องการเปิดงาน: « %s » ใช่หรือไม่?', + 'Back to the board' => 'กลับไปที่บอร์ด', + 'There is nobody assigned' => 'ไม่มีใครถูกกำหนด', + 'Column on the board:' => 'คอลัมน์บนบอร์ด:', + 'Close this task' => 'ปิดงานนี้', + 'Open this task' => 'เปิดงานนี้', + 'There is no description.' => 'ไม่มีคำอธิบาย', + 'Add a new task' => 'เพิ่มงานใหม่', + 'The username is required' => 'ต้องการชื่อผู้ใช้', + 'The maximum length is %d characters' => 'จำนวนตัวอักษรสูงสุด %d ตัวอักษร', + 'The minimum length is %d characters' => 'จำนวนตัวอักษรน้อยสุด %d ตัวอักษร', + 'The password is required' => 'ต้องการรหัสผ่าน', + 'This value must be an integer' => 'ต้องเป็นตัวเลข', + 'The username must be unique' => 'ชื่อผู้ใช้ต้องไม่ซ้ำ', + 'The user id is required' => 'ต้องการไอดีผู้ใช้', + 'Passwords don\'t match' => 'รหัสผ่านไม่ถูกต้อง', + 'The confirmation is required' => 'ต้องการการยืนยัน', + 'The project is required' => 'ต้องการโปรเจค', + 'The id is required' => 'ต้องการไอดี', + 'The project id is required' => 'ต้องการไอดีโปรเจค', + 'The project name is required' => 'ต้องการชื่อโปรเจค', + 'The title is required' => 'ต้องการหัวเรื่อง', + 'Settings saved successfully.' => 'บันทึกการตั้งค่าเรียบร้อยแล้ว', + 'Unable to save your settings.' => 'ไม่สามารถบันทึกการตั้งค่าได้', + 'Database optimization done.' => 'ปรับปรุงฐานข้อมูลเรียบร้อยแล้ว', + 'Your project has been created successfully.' => 'สร้างโปรเจคเรียบร้อยแล้ว', + 'Unable to create your project.' => 'ไม่สามารถสร้างโปรเจคได้', + 'Project updated successfully.' => 'ปรับปรุงโปรเจคเรียบร้อยแล้ว', + 'Unable to update this project.' => 'ไม่สามารถปรับปรุงโปรเจคได้', + 'Unable to remove this project.' => 'ไม่สามารถลบโปรเจคได้', + 'Project removed successfully.' => 'ลบโปรเจคเรียบร้อยแล้ว', + 'Project activated successfully.' => 'เปิดใช้งานโปรเจคเรียบร้อยแล้ว', + 'Unable to activate 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.' => 'ไม่สามารถปรับปรุงงานได้', + 'Task updated successfully.' => 'ปรับปรุงงานเรียบร้อยแล้ว', + 'Unable to create your task.' => 'ไม่สามารถสร้างงานได้', + 'Task created successfully.' => 'สร้างงานเรียบร้อยแล้ว', + 'User created successfully.' => 'สร้างผู้ใช้เรียบร้อยแล้ว', + 'Unable to create your user.' => 'ไม่สามารถสร้างผู้ใช้ได้', + 'User updated successfully.' => 'ปรับปรุงผู้ใช้เรียบร้อยแล้ว', + 'User removed successfully.' => 'ลบผู้ใช้เรียบร้อยแล้ว', + 'Unable to remove this user.' => 'ไม่สามารถลบผู้ใช้ได้', + 'Board updated successfully.' => 'ปรับปรุงบอร์ดเรียบร้อยแล้ว', + 'Ready' => 'พร้อม', + 'Backlog' => 'งานค้าง', + 'Work in progress' => 'กำลังทำ', + 'Done' => 'เสร็จ', + 'Application version:' => 'แอพเวอร์ชัน:', + 'Id' => 'ไอดี', + 'Public link' => 'ลิงค์สาธารณะ', + 'Timezone' => 'เขตเวลา', + 'Sorry, I didn\'t find this information in my database!' => 'เสียใจด้วย ไม่สามารถหาข้อมูลในฐานข้อมูลได้', + 'Page not found' => 'ไม่พบหน้า', + 'Complexity' => 'ความซับซ้อน', + 'Task limit' => 'จำกัดงาน', + 'Task count' => 'นับงาน', + 'User' => 'ผู้ใช้', + 'Comments' => 'ความคิดเห็น', + 'Comment is required' => 'ต้องการความคิดเห็น', + 'Comment added successfully.' => 'เพิ่มความคิดเห็นเรียบร้อยแล้ว', + 'Unable to create your comment.' => 'ไม่สามารถสร้างความคิดเห็น', + 'Due Date' => 'วันที่ครบกำหนด', + 'Invalid date' => 'วันที่ผิด', + 'Automatic actions' => 'การกระทำอัตโนมัติ', + 'Your automatic action has been created successfully.' => 'การกระทำอัตโนมัติสร้างเรียบร้อยแล้ว', + 'Unable to create your automatic action.' => 'ไม่สามารถสร้างการกระทำอัตโนมัติได้', + 'Remove an action' => 'ลบการกระทำ', + 'Unable to remove this action.' => 'ไม่สามารถลบการกระทำ', + 'Action removed successfully.' => 'ลบการกระทำเรียบร้อยแล้ว', + 'Automatic actions for the project "%s"' => 'การกระทำอัตโนมัติสำหรับโปรเจค « %s »', + 'Add an action' => 'เพิ่มการกระทำ', + 'Event name' => 'ชื่อเหตุกาณ์', + 'Action' => 'การกระทำ', + 'Event' => 'เหตุการณ์', + 'When the selected event occurs execute the corresponding action.' => 'เหตุการ์ที่เลือกจะเกิดขึ้นเมื่อมีการกระทำที่สอดคล้องกัน', + 'Next step' => 'ขั้นตอนต่อไป', + 'Define action parameters' => 'กำหนดพารามิเตอร์ของการกระทำ', + 'Do you really want to remove this action: "%s"?' => 'คุณต้องการลบการกระทำ « %s » ใช่หรือไม่?', + 'Remove an automatic action' => 'ลบการกระทำอัตโนมัติ', + 'Assign the task to a specific user' => 'กำหนดงานให้ผู้ใช้แบบเจาะจง', + 'Assign the task to the person who does the action' => 'กำหนดงานให้ผู้ใช้งานปัจจุบัน', + 'Duplicate the task to another project' => 'ทำซ้ำงานนี้ในโปรเจคอื่น', + 'Move a task to another column' => 'ย้ายงานไปคอลัมน์อื่น', + 'Task modification' => 'แก้ไขงาน', + 'Task creation' => 'สร้างงาน', + 'Closing a task' => 'กำลังปิดงาน', + 'Assign a color to a specific user' => 'กำหนดสีให้ผู้ใช้แบบเจาะจง', + 'Position' => 'ตำแหน่ง', + 'Duplicate to project' => 'ทำซ้ำในโปรเจคอื่น', + 'Duplicate' => 'ทำซ้ำ', + 'Link' => 'ลิงค์', + 'Comment updated successfully.' => 'ปรับปรุงความคิดเห็นเรียบร้อยแล้ว', + 'Unable to update your comment.' => 'ไม่สามารถปรับปรุงความคิดเห็นได้', + 'Remove a comment' => 'ลบความคิดเห็น', + 'Comment removed successfully.' => 'ลบความคิดเห็นเรียบร้อยแล้ว', + 'Unable to remove this comment.' => 'ไม่สามารถลบความคิดเห็นได้', + 'Do you really want to remove this comment?' => 'คุณต้องการลบความคิดเห็น', + 'Current password for the user "%s"' => 'รหัสผ่านปัจจุบันของผู้ใช้ « %s »', + 'The current password is required' => 'ต้องการรหัสผ่านปัจจุบัน', + 'Wrong password' => 'รหัสผ่านผิด', + 'Unknown' => 'ไม่ทราบ', + 'Last logins' => 'เข้าใช้ล่าสุด', + 'Login date' => 'วันที่เข้าใข้', + 'Authentication method' => 'วิธีการยืนยันตัวตน', + 'IP address' => 'ที่อยู่ไอพี', + 'User agent' => 'User agent', + 'Persistent connections' => 'Persistent connections', + 'No session.' => 'No session.', + 'Expiration date' => 'หมดอายุวันที่', + 'Remember Me' => 'จดจำฉัน', + 'Creation date' => 'สร้างวันที่', + 'Everybody' => 'ทุกคน', + 'Open' => 'เปิด', + 'Closed' => 'ปิด', + 'Search' => 'ค้นหา', + 'Nothing found.' => 'ค้นหาไม่พบ.', + 'Due date' => 'วันที่ครบกำหนด', + 'Description' => 'คำอธิบาย', + '%d comments' => '%d ความคิดเห็น', + '%d comment' => '%d ความคิดเห็น', + 'Email address invalid' => 'ที่อยู่อีเมลไม่ถูกต้อง', + 'Your external account is not linked anymore to your profile.' => 'บัญชีภายนอกของคุณไม่ได้เชื่อมโยงมายังโปรไฟล์ของคุณอีกต่อ', + 'Unable to unlink your external account.' => 'ไม่สามารถยกเลิกการเชื่อมโยงบัญชีภายนอกของคุณ', + 'External authentication failed' => 'การตรวจสอบภายนอกล้มเหลว', + 'Your external account is linked to your profile successfully.' => 'ทำการเชื่อมโยงบัญชีภายนอกของคุณกับโปรไฟล์ของคุณเรียบร้อย', + 'Email' => 'อีเมล', + 'Task removed successfully.' => 'ลบงานเรียบร้อยแล้ว', + 'Unable to remove this task.' => 'ไม่สามารถลบงานนี้', + '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' => 'กำหนดหมวดอัตโนมัติขึ้นอยู่กับสี', + 'Task creation or modification' => 'สร้างหรือแก้ไขงาน', + 'Category' => 'หมวดหมู่', + 'Category:' => 'หมวดหมู่:', + 'Categories' => 'หมวดหมู่', + 'Your category has been created successfully.' => 'สร้างหมวดหมู่เรียบร้อยแล้ว', + 'This category has been updated successfully.' => 'ปรับปรุงหมวดเรียบร้อยแล้ว', + 'Unable to update this category.' => 'ไม่สามารถปรับปรุงหมวดหมู่ได้', + 'Remove a category' => 'ลบหมวดหมู่', + 'Category removed successfully.' => 'ลบหมวดเรียบร้อยแล้ว', + 'Unable to remove this category.' => 'ไม่สามารถลบหมวดหมู่ได้', + 'Category modification for the project "%s"' => 'แก้ไขหมวดหมู่สำหรับโปรเจค "%s"', + 'Category Name' => 'ชื่อหมวดหมู่', + 'Add a new category' => 'เพิ่มหมวดหมู่ใหม่', + 'Do you really want to remove this category: "%s"?' => 'คุณต้องการลบหมวดหมู่ "%s" ใช่หรือไม่?', + 'All categories' => 'หมวดหมู่ทั้งหมด', + 'No category' => 'ไม่มีหมวดหมู่', + 'The name is required' => 'ต้องการชื่อ', + 'Remove a file' => 'ลบไฟล์', + 'Unable to remove this file.' => 'ไม่สามารถลบไฟล์ได้', + 'File removed successfully.' => 'ลบไฟล์เรียบร้อยแล้ว', + 'Attach a document' => 'แนบเอกสาร', + 'Do you really want to remove this file: "%s"?' => 'คุณต้องการลบไฟล์ "%s" ใช่หรือไม่?', + 'Attachments' => 'แนบ', + 'Edit the task' => 'แก้ไขงาน', + 'Add a comment' => 'เพิ่มความคิดเห็น', + 'Edit a comment' => 'แก้ไขความคิดเห็น', + 'Summary' => 'สรุป', + 'Time tracking' => 'การติดตามเวลา', + 'Estimate:' => 'ประมาณ:', + 'Spent:' => 'ใช้:', + 'Do you really want to remove this sub-task?' => 'คุณต้องการลบงานย่อยใช่หรือไม่?', + 'Remaining:' => 'เหลือ:', + 'hours' => 'ชั่วโมง', + 'estimated' => 'ประมาณ', + 'Sub-Tasks' => 'งานย่อย', + 'Add a sub-task' => 'เพิ่มงานย่อย', + 'Original estimate' => 'ประมาณการเดิม', + 'Create another sub-task' => 'สร้างงานย่อยอื่น', + 'Time spent' => 'ใช้เวลา', + 'Edit a sub-task' => 'แก้ไขงานย่อย', + 'Remove a sub-task' => 'ลบงานย่อย', + 'The time must be a numeric value' => 'เวลาที่ต้องเป็นตัวเลข', + 'Todo' => 'สิ่งที่ต้องทำ', + 'In progress' => 'กำลังดำเนินการ', + 'Sub-task removed successfully.' => 'ลบงานย่อยเรียบร้อยแล้ว', + 'Unable to remove this sub-task.' => 'ไม่สามารถลบงานย่อยได้', + 'Sub-task updated successfully.' => 'ปรับปรุงงานย่อย่่เรียบร้อยแล้ว', + 'Unable to update your sub-task.' => 'ไม่สามารถปรับปรุงานย่อยได้', + 'Unable to create your sub-task.' => 'ไม่สามารถสร้างงานย่อยได้', + 'Maximum size: ' => 'ขนาดไฟล์สูงสุด:', + 'Display another project' => 'แสดงโปรเจคอื่น', + 'Created by %s' => 'สร้างโดย %s', + 'Tasks Export' => 'ส่งออกงาน', + 'Start Date' => 'เริ่มวันที่', + 'Execute' => 'ประมวลผล', + 'Task Id' => 'งาน ไอดี', + 'Creator' => 'ผู้สร้าง', + 'Modification date' => 'วันที่แก้ไข', + 'Completion date' => 'วันที่เสร็จสิ้น', + 'Clone' => 'เลียนแบบ', + 'Project cloned successfully.' => 'เลียนแบบโปรเจคเรียบร้อยแล้ว', + 'Unable to clone this project.' => 'ไม่สามารถเลียบแบบโปรเจคได้', + 'Enable email notifications' => 'เปิดอีเมลแจ้งเตือน', + 'Task position:' => 'ตำแหน่งงาน', + 'The task #%d has been opened.' => 'งานที่ #%d ถุกเปิด', + 'The task #%d has been closed.' => 'งานที่ #%d ถูกปิด', + 'Sub-task updated' => 'ปรับปรุงงานย่อย', + 'Title:' => 'หัวเรื่อง:', + 'Status:' => 'สถานะ:', + 'Assignee:' => 'กำหนดให้:', + 'Time tracking:' => 'การติดตามเวลา:', + 'New sub-task' => 'งานย่อยใหม่', + 'New attachment added "%s"' => 'เพิ่มการแนบใหม่ "%s"', + 'New comment posted by %s' => 'ความคิดเห็นใหม่จาก %s', + 'New comment' => 'ความคิดเห็นใหม่', + 'Comment updated' => 'ปรับปรุงความคิดเห็น', + 'New subtask' => 'งานย่อยใหม่', + 'I only want to receive notifications for these projects:' => 'ฉันต้องการรับการแจ้งเตือนสำหรับโปรเจค:', + 'view the task on Kanboard' => 'แสดงงานบน Kanboard', + 'Public access' => 'การเข้าถึงสาธารณะ', + 'Disable public access' => 'ปิดการเข้าถึงสาธารณะ', + 'Enable public access' => 'เปิดการเข้าถึงสาธารณะ', + 'Public access disabled' => 'การเข้าถึงสาธารณะถูกปิด', + 'Move the task to another project' => 'ย้ายงานไปโปรเจคอื่น', + 'Move to project' => 'ย้ายไปโปรเจคอื่น', + 'Do you really want to duplicate this task?' => 'คุณต้องการทำซ้ำงานนี้ใช่หรือไม่?', + 'Duplicate a task' => 'ทำซ้ำงาน', + 'External accounts' => 'บัญชีภายนอก', + 'Account type' => 'ประเภทบัญชี', + 'Local' => 'ท้องถิ่น', + 'Remote' => 'รีโมท', + 'Enabled' => 'เปิดการใช้', + 'Disabled' => 'ปิดการใช้', + 'Login:' => 'ชื่อผู้ใช้:', + 'Full Name:' => 'ชื่อ:', + 'Email:' => 'อีเมล:', + 'Notifications:' => 'แจ้งเตือน:', + 'Notifications' => 'การแจ้งเตือน', + 'Account type:' => 'ประเภทบัญชี:', + 'Edit profile' => 'แก้ไขประวัติ', + 'Change password' => 'เปลี่ยนรหัสผ่าน', + 'Password modification' => 'แก้ไขรหัสผ่าน', + 'External authentications' => 'การยืนยันภายนอก', + 'Never connected.' => 'ไม่เชื่อมต่อ', + 'No external authentication enabled.' => 'ปิดการใช้งานการยืนยันภายนอก', + 'Password modified successfully.' => 'แก้ไขรหัสผ่านเรียบร้อย', + 'Unable to change the password.' => 'ไม่สามารถเปลี่ยนรหัสผ่านได้', + 'Change category' => 'เปลี่ยนหมวดหมู่', + '%s updated the task %s' => '%s ได้ปรับปรุงงาน %s', + '%s opened the task %s' => '%s ได้สร้างงาน %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s ได้ย้ายงาน %s ไปยังตำแหน่ง #%d ในคอลัมน์ "%s"', + '%s moved the task %s to the column "%s"' => '%s ได้ย้ายงาน %s ไปยังคอลัมน์ "%s"', + '%s created the task %s' => '%s ได้สร้างงาน %s', + '%s closed the task %s' => '%s ได้ปิดงาน %s', + '%s created a subtask for the task %s' => '%s ได้สร้างงานย่อยสำหรับงาน %s', + '%s updated a subtask for the task %s' => '%s ได้ปรับปรุงงานย่อยสำหรับงาน %s', + 'Assigned to %s with an estimate of %s/%sh' => 'มอบหมายให้ %s โดยประมาณเวลาที่ใช้ %s/%sh', + 'Not assigned, estimate of %sh' => 'ไม่ระบุผู้รับผิดชอบ, ประมาณเวลาที่ใช้ %s ชั่วโมง', + '%s updated a comment on the task %s' => '%s ได้ปรับปรุงความคิดเห็นในงาน %s', + '%s commented the task %s' => '%s ได้แสดงความคิดเห็นในงาน %s', + '%s\'s activity' => 'กิจกรรม %s', + 'RSS feed' => 'RSS feed', + '%s updated a comment 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', + '%s created the task #%d' => '%s ได้สร้างงาน #%d', + '%s closed the task #%d' => '%s ได้ปิดงาน #%d', + '%s opened the task #%d' => '%s ได้เปิดงาน #%d', + 'Activity' => 'กิจกรรม', + 'Default values are "%s"' => 'ค่าเริ่มต้น "%s"', + 'Default columns for new projects (Comma-separated)' => 'คอลัมน์เริ่มต้นสำหรับโปรเจคใหม่ (Comma-separated)', + 'Task assignee change' => 'เปลี่ยนการกำหนดบุคคลของงาน', + '%s changed the assignee of the task #%d to %s' => '%s ได้เปลี่ยนผู้รับผิดชอบงาน #%d เป็น %s', + '%s changed the assignee of the task %s to %s' => '%s ได้เปลี่ยนผู้รับผิดชอบงาน %s เป็น %s', + 'New password for the user "%s"' => 'รหัสผ่านใหม่สำหรับผู้ใช้ "%s"', + 'Choose an event' => 'เลือกเหตุการณ์', + 'Create a task from an external provider' => 'สร้างงานจากบริการภายนอก', + 'Change the assignee based on an external username' => 'เปลี่ยนผู้รับผิดชอบขึ้นอยู่กับชื่อผู้ใช้ภายนอก', + 'Change the category based on an external label' => 'เปลี่ยนหมวดขึ้นอยู่กับป้ายชื่อภายนอก', + 'Reference' => 'อ้างถึง', + 'Label' => 'ป้ายชื่อ', + 'Database' => 'ฐานข้อมูล', + 'About' => 'เกี่ยวกับ', + 'Database driver:' => 'เครื่องมือฐานขข้อมูล', + 'Board settings' => 'ตั้งค่าบอร์ด', + 'Webhook settings' => 'การตั้งค่า Webhook', + 'Reset token' => 'รีเซ็ตโทเค็น', + 'API endpoint:' => 'ปลายทาง API:', + 'Refresh interval for personal board' => 'ระยะเวลารีเฟรชบอร์ดส่วนตัว', + 'Refresh interval for public board' => 'ระยะเวลารีเฟรชบอร์ดสาธารณะ', + 'Task highlight period' => 'ช่วงเวลาไฮไลต์งาน', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'ช่วงเวลา (เป็นวินาที) ใช้ในการตัดสินใจว่าเป็นการแก้ไขเร็วๆ นี้ (0 ไม่ใช้งาน, ค่าเริ่มต้น 2 วัน)', + 'Frequency in second (60 seconds by default)' => 'ความถี่ (ค่าเริ่มต้นทุก 60 วินาที) ', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'ความถี่ (0 ไม่ใช้คุณลักษณะนี้, ค่าเริ่มต้นทุก 10 วินาที)', + 'Application URL' => 'URL แอปพลิเคชัน', + 'Token regenerated.' => 'สร้างโทเค็นใหม่แล้ว', + 'Date format' => 'รูปแบบวันที่', + 'ISO format is always accepted, example: "%s" and "%s"' => 'ยอมรับรูปแบบ ISO ตัวอย่าง: "%s" และ "%s"', + 'New personal project' => 'เพิ่มโปรเจคส่วนตัวใหม่', + 'This project is personal' => 'โปรเจคนี้เป็นโปรเจคส่วนตัว', + 'Add' => 'เพิ่ม', + 'Start date' => 'เริ่มวันที่', + 'Time estimated' => 'เวลาโดยประมาณ', + 'There is nothing assigned to you.' => 'ไม่มีอะไรกำหนดให้คุณ', + 'My tasks' => 'งานของฉัน', + 'Activity stream' => 'กิจกรรมที่เกิดขึ้น', + 'Dashboard' => 'แดชบอร์ด', + 'Confirmation' => 'ยืนยันรหัสผ่าน', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Create a comment from an external provider' => 'สร้างความคิดเห็นจากบริการภายนอก', + 'Project management' => 'การจัดการโปรเจค', + 'Columns' => 'คอลัมน์', + 'Task' => 'งาน', + 'Percentage' => 'เปอร์เซ็นต์', + 'Number of tasks' => 'จำนวนงาน', + 'Task distribution' => 'การกระจายงาน', + 'Analytics' => 'การวิเคราะห์', + 'Subtask' => 'งานย่อย', + 'User repartition' => 'การแบ่งงานของผู้ใช้', + 'Clone this project' => 'สำเนาโปรเจคนี้', + 'Column removed successfully.' => 'ลบคอลัมน์สำเร็จ', + 'Not enough data to show the graph.' => 'ไม่มีข้อมูลเพียงพอสำหรับการแสดงกราฟ', + 'Previous' => 'ก่อนหน้า', + 'The id must be an integer' => 'ไอดีต้องเป็นตัวเลขจำนวนเต็ม', + 'The project id must be an integer' => 'ไอดีโปรเจคต้องเป็นตัวเลขเท่านั้น', + 'The status must be an integer' => 'สถานะต้องเป็นตัวเลขเท่านั้น', + 'The subtask id is required' => 'ต้องการงานย่อย', + 'The subtask id must be an integer' => 'ไอดีงานย่อยต้องเป็นตัวเลขเท่านั้น', + 'The task id is required' => 'ต้องการไอดีงาน', + 'The task id must be an integer' => 'ไอดีงานต้องเป็นตัวเลขเท่านั้น', + 'The user id must be an integer' => 'ไอดีผู้ใช้ต้องเป็นตัวเลขเท่านั้น', + 'This value is required' => 'ต้องการค่านี้', + 'This value must be numeric' => 'ค่านี้ต้องเป็นตัวเลข', + 'Unable to create this task.' => 'ไม่สามารถสร้างงานนี้', + 'Cumulative flow diagram' => 'แผนภาพงานสะสม', + 'Daily project summary' => 'สรุปโปรเจครายวัน', + 'Daily project summary export' => 'ส่งออกสรุปโปรเจครายวัน', + 'Exports' => 'ส่งออก', + 'This export contains the number of tasks per column grouped per day.' => 'การส่งออกนี้เป็นการนับจำนวนงานในแต่ละคอลัมน์ในแต่ละวัน', + 'Active swimlanes' => 'สวิมเลนพร้อมใช้งาน', + 'Add a new swimlane' => 'เพิ่มสวิมเลนใหม่', + 'Default swimlane' => 'สวิมเลนเริ่มต้น', + 'Do you really want to remove this swimlane: "%s"?' => 'คุณต้องการลบสวิมเลนนี้ : "%s"?', + 'Inactive swimlanes' => 'สวิมเลนไม่ทำงาน', + 'Remove a swimlane' => 'ลบสวิมเลน', + 'Swimlane modification for the project "%s"' => 'แก้ไขสวิมเลนสำหรับโปรเจค "%s"', + 'Swimlane removed successfully.' => 'ลบสวิมเลนเรียบร้อยแล้ว', + 'Swimlanes' => 'สวิมเลน', + 'Swimlane updated successfully.' => 'ปรับปรุงสวิมเลนเรียบร้อยแล้ว', + 'Unable to remove this swimlane.' => 'ไม่สามารถลบสวิมเลนนี้', + 'Unable to update this swimlane.' => 'ไม่สามารถปรับปรุงสวิมเลนนี้', + 'Your swimlane has been created successfully.' => 'สวิมเลนของคุณถูกสร้างเรียบร้อยแล้ว', + 'Example: "Bug, Feature Request, Improvement"' => 'ตัวอย่าง: "Bug, Feature Request, Improvement"', + 'Default categories for new projects (Comma-separated)' => 'ค่าเริ่มต้นหมวดสำหรับโปรเจคใหม่ (Comma-separated)', + 'Integrations' => 'การใช้ร่วมกัน', + 'Integration with third-party services' => 'การใช้งานร่วมกับบริการ third-party', + 'Subtask Id' => 'ไอดีของงานย่อย', + 'Subtasks' => 'งานย่อย', + 'Subtasks Export' => 'ส่งออก งานย่อย', + 'Task Title' => 'ชื่องาน', + 'Untitled' => 'ไม่มีชื่อ', + 'Application default' => 'แอพพลิเคชันเริ่มต้น', + 'Language:' => 'ภาษา:', + 'Timezone:' => 'เขตเวลา:', + 'All columns' => 'คอลัมน์ทั้งหมด', + 'Next' => 'ต่อไป', + '#%d' => '#%d', + 'All swimlanes' => 'สวิมเลนทั้งหมด', + 'All colors' => 'สีทั้งหมด', + 'Moved to column %s' => 'เคลื่อนไปคอลัมน์ %s', + 'User dashboard' => 'ผู้ใช้แดชบอร์ด', + 'Allow only one subtask in progress at the same time for a user' => 'อนุญาตให้ทำงานย่อยได้เพียงงานเดียวต่อหนึ่งคนในเวลาเดียวกัน', + 'Edit column "%s"' => 'แก้ไขคอลัมน์ "%s"', + '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?' => 'เลือกส่วนของโปรเจคที่คุณต้องการทำซ้ำ?', + 'Disallow login form' => 'ไม่อนุญาตให้ใช้แบบฟอร์มการเข้าสู่ระบบ', + 'Start' => 'เริ่ม', + 'End' => 'จบ', + 'Task age in days' => 'อายุงาน', + 'Days in this column' => 'วันในคอลัมน์นี้', + '%dd' => '%d วัน', + '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' => 'แก้ไขลิงค์', + 'Opposite label' => 'ป้ายชื่อตรงข้าม', + 'Remove a link' => 'ลบลิงค์', + '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' => '<1 ชม.', + '%dh' => '%d ชม.', + 'Expand tasks' => 'ขยายงาน', + 'Collapse tasks' => 'ย่องาน', + 'Expand/collapse tasks' => 'ขยาย/ย่อ งาน', + 'Close dialog box' => 'ปิดกล่องข้อความ', + 'Submit a form' => 'ยอมรับฟอร์ม', + 'Board view' => 'มุมมองบอร์ด', + 'Keyboard shortcuts' => 'คีย์ลัด', + 'Open board switcher' => 'เปิดการสลับบอร์ด', + 'Application' => 'แอพพลิเคชัน', + 'Compact view' => 'มุมมองพอดี', + 'Horizontal scrolling' => 'เลื่อนตามแนวนอน', + 'Compact/wide view' => 'พอดี/กว้าง มุมมอง', + 'Currency' => 'สกุลเงิน', + 'Personal project' => 'โปรเจคส่วนตัว', + 'AUD - Australian Dollar' => 'AUD - ดอลลาร์ออสเตรเลีย', + 'CAD - Canadian Dollar' => 'CAD - ดอลลาร์แคนาดา', + 'CHF - Swiss Francs' => 'CHF - ฟรังก์สวิส', + 'Custom Stylesheet' => 'สไตล์ที่กำหนดเอง', + 'EUR - Euro' => 'EUR - ยูโร', + 'GBP - British Pound' => 'GBP - ปอนด์อังกฤษ', + 'INR - Indian Rupee' => 'INR - รูปี', + 'JPY - Japanese Yen' => 'JPY - เยน', + 'NZD - New Zealand Dollar' => 'NZD - ดอลลาร์นิวซีแลนด์', + 'PEN - Peruvian Sol' => 'PEN - โซลเปรู', + 'RSD - Serbian dinar' => 'RSD - ดีนาร์เซอร์เบีย', + 'CNY - Chinese Yuan' => 'CNY - หยวนจีน', + 'USD - US Dollar' => 'USD - ดอลลาร์สหรัฐ', + 'VES - Venezuelan Bolívar' => 'VES - โบลีวาร์เวเนซุเอลา', + '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' => 'คอลัมน์ต้นทาง', + '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' => 'เปลี่ยนการอ้างถึงค่าเงิน', + 'Reference currency' => 'อ้างถึงค่าเงิน', + 'The currency rate has been added successfully.' => 'เพิ่มอัตราค่าเงินเรียบร้อย', + 'Unable to add this currency rate.' => 'ไม่สามารถเพิ่มค่าเงินนี้', + 'Webhook URL' => 'URL Webhook', + '%s removed the assignee of the task %s' => '%s เอาผู้รับผิดชอบออกจากงาน %s', + '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' => 'การยืนยันแบบสองขั้นตอน', + 'This QR code contains the key URI: ' => 'รหัส QR นี้มี URI คีย์:', + 'Check my code' => 'ตรวจสอบรหัสของฉัน', + 'Secret key: ' => 'กุญแจลับ', + 'Test your device' => 'ทดสอบอุปกรณ์ของคุณ', + 'Assign a color when the task is moved to a specific column' => 'กำหนดสีเมื่องานถูกย้ายไปคอลัมน์ที่กำหนดไว้', + '%s via Kanboard' => '%s ผ่าน Kanboard', + 'Burndown chart' => 'แผนภูมิงานกับเวลา', + 'This chart show the task complexity over the time (Work Remaining).' => 'แผนภูมิแสดงความซับซ้อนของงานตามเวลา (งานที่เหลือ)', + 'Screenshot taken %s' => 'จับภาพหน้าจอ %s', + 'Add a screenshot' => 'เพิ่ม screenshot', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'จับภาพหน้าจอ (screenshot) และกด CTRL+V หรือ ⌘+V เพื่อวางที่นี้', + 'Screenshot uploaded successfully.' => 'อัพโหลด screenshot เรียบร้อยแล้ว', + 'SEK - Swedish Krona' => 'SEK - โครนสวีเดน', + 'Identifier' => 'ตัวบ่งชี้', + 'Disable two factor authentication' => 'ปิดใช้งานการยืนยันแบบสองขั้นตอน', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'คุณต้องการยกเลิก the two factor authentication สำหรับผู้ใช้นีั: "%s"?', + 'Edit link' => 'แก้ไขลิงค์', + 'Start to type task title...' => 'พิมพ์ชื่องาน', + 'A task cannot be linked to itself' => 'งานไม่สามารถลิงค์ตัวเอง', + 'The exact same link already exists' => 'ลิงก์เดียวกันนี้มีอยู่แล้ว', + 'Recurrent task is scheduled to be generated' => 'งานแบบวนลูปถูกสร้างตามที่กำหนดไว้', + 'Score' => 'คะแนน', + 'The identifier must be unique' => 'ตัวบ่งชี้ต้องไม่ซ้ำ', + 'This linked task id doesn\'t exists' => 'รหัสงานที่เชื่อมโยงนี้ไม่มีอยู่', + 'This value must be alphanumeric' => 'ค่านี้ต้องเป็นตัวอักษร', + 'Edit recurrence' => 'แก้ไขการวนลูป', + 'Generate recurrent task' => 'สร้างงานที่เป็นวนลูป', + 'Trigger to generate recurrent task' => 'จะสร้างงานแบบวนลูป', + 'Factor to calculate new due date' => 'ปัจจัยการคำนวณวันครบกำหนดใหม่', + 'Timeframe to calculate new due date' => 'ระยะเวลาการคำนวณวันครบกำหนดใหม่', + 'Base date to calculate new due date' => 'ฐานวันที่การคำนวณวันครบกำหนดใหม่', + 'Action date' => 'วันที่ทำ', + 'Base date to calculate new due date: ' => 'ฐานวันที่การคำนวณวันครบกำหนดใหม่: ', + 'This task has created this child task: ' => 'งานนี้สร้างงานลูกคือ', + 'Day(s)' => 'วัน', + 'Existing due date' => 'วันครบกำหนดที่มีอยู่', + 'Factor to calculate new due date: ' => 'ปัจจัยการคำนวณวันครบกำหนดใหม่: ', + 'Month(s)' => 'เดือน', + 'This task has been created by: ' => 'งานนี้ถูกสร้างโดย', + 'Recurrent task has been generated:' => 'งานแบบวนลูปถูกสร้าง', + 'Timeframe to calculate new due date: ' => 'ระยะเวลาการคำนวณวันครบกำหนดใหม่: ', + 'Trigger to generate recurrent task: ' => 'จะสร้างงานแบบวนลูป', + 'When task is closed' => 'เมื่อปิดงาน', + 'When task is moved from first column' => 'เมื่องานถูกย้ายจากคอลัมน์แรก', + 'When task is moved to last column' => 'เมื่องานถูกย้ายไปคอลัมน์สุดท้าย', + 'Year(s)' => 'ปี', + 'Project settings' => 'ตั้งค่าโปรเจค', + 'Automatically update the start date' => 'ปรับปรุงวันที่เริ่มอัตโนมมัติ', + 'iCal feed' => 'ฟีด iCal', + 'Preferences' => 'การตั้งค่า', + 'Security' => 'ความปลอดภัย', + 'Two factor authentication disabled' => 'ปิดใช้งานการยืนยันแบบสองขั้นตอนแล้ว', + 'Two factor authentication enabled' => 'เปิดใช้งานการยืนยันแบบสองขั้นตอนแล้ว', + 'Unable to update this user.' => 'ไม่สามารถปรับปรุงผู้ใช้นี้', + 'There is no user management for personal projects.' => 'ไม่มีการจัดการผู้ใช้สำหรับโปรเจคส่วนตัว', + 'User that will receive the email' => 'ผู้ใช้จะได้รับอีเมล์', + 'Email subject' => 'หัวเรื่องอีเมล์', + 'Date' => 'วันที่', + 'Add a comment log when moving the task between columns' => 'เพิ่มล็อกความคิดเห็นเมื่อย้ายงานระหว่างคอลัมน์', + 'Move the task to another column when the category is changed' => 'ย้ายงานไปคอลัมน์อื่นเมื่อหมวดถูกเปลี่ยน', + 'Send a task by email to someone' => 'ส่งงานโดยถึงบางคน', + 'Reopen a task' => 'เปิดงานอีกครั้ง', + 'Notification' => 'แจ้งเตือน', + '%s moved the task #%d to the first swimlane' => '%s ย้ายงาน #%d ไปสวินเลนแรก', + 'Swimlane' => 'สวิมเลน', + '%s moved the task %s to the first swimlane' => '%s ย้ายงาน %s ไปสวินเลนแรก', + '%s moved the task %s to the swimlane "%s"' => '%s ย้ายงาน %s ไปสวินเลนไปสวินเลน "%s"', + 'This report contains all subtasks information for the given date range.' => 'รายงานนี้มีข้อมูลงานย่อยทั้งหมดในช่วงวันที่กำหนด', + 'This report contains all tasks information for the given date range.' => 'รายงานนี้มีข้อมูลงานทั้งหมดสำหรับช่วงวันที่ที่กำหนด', + 'Project activities for %s' => 'กิจกรรมโปรเจคสำหรับ %s', + 'view the board on Kanboard' => 'แสดงบอร์ดบนคังบอร์ด', + 'The task has been moved to the first swimlane' => 'งานถูกย้านไปสวิมเลนแรก', + 'The task has been moved to another swimlane:' => 'งานถูกย้านไปสวิมเลนอื่น:', + 'New title: %s' => 'ชื่อเรื่องใหม่: %s', + 'The task is not assigned anymore' => 'ไม่กำหนดผู้รับผิดชอบ', + 'New assignee: %s' => 'ผู้รับผิดชอบใหม่: %s', + 'There is no category now' => 'ปัจจุบันไม่มีหมวด', + 'New category: %s' => 'หมวดใหม่: %s', + 'New color: %s' => 'สีใหม่: %s', + 'New complexity: %d' => 'ความซับซ้อนใหม่: %d', + 'The due date has been removed' => 'วันครบกำหนดถูกลบ', + 'There is no description anymore' => 'ไม่มีคำอธิบาย', + 'Recurrence settings has been modified' => 'แก้ไขการตั้งค่าการวนลูป', + 'Time spent changed: %sh' => 'เวลาที่ใช้ในการเปลี่ยน: %s ชม.', + 'Time estimated changed: %sh' => 'เวลาโดยประมาณในการเปลี่ยน: %s ชม.', + 'The field "%s" has been updated' => 'ฟิลด์ "%s" ถูกปรับปรุง', + 'The description has been modified:' => 'คำอธิบายถูกแก้ไข', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'คุณต้องการปิดงาน "%s" เช่นเดียวกับงานย่อยทั้งหมด?', + 'I want to receive notifications for:' => 'ฉันต้องการรับการแจ้งเตือนสำหรับ:', + 'All tasks' => 'ทุกงาน', + 'Only for tasks assigned to me' => 'เฉพาะงานที่ฉันรับผิดชอบ', + 'Only for tasks created by me' => 'เฉพาะงานที่ฉันสร้าง', + 'Only for tasks created by me and tasks assigned to me' => 'เฉพาะงานที่ฉันสร้างและฉันรับผิดชอบ', + '%%Y-%%m-%%d' => '%%Y-%%m-%%d', + 'Total for all columns' => 'จำนวนคอลัมน์ทั้งหมด', + 'You need at least 2 days of data to show the chart.' => 'คุณต้องการอย่างน้อย 2 วันในการแสดงแผนภูมิ', + '<15m' => '<15นาที', + '<30m' => '<30นาที', + 'Stop timer' => 'หยุดจับเวลา', + 'Start timer' => 'เริ่มจับเวลา', + 'My activity stream' => 'กิจกรรมที่เกิดขึ้นของฉัน', + 'Search tasks' => 'ค้นหางาน', + 'Reset filters' => 'ล้างตัวกรอง', + 'My tasks due tomorrow' => 'งานถึงกำหนดของฉันวันพรุ่งนี้', + 'Tasks due today' => 'งานถึงกำหนดวันนี้', + 'Tasks due tomorrow' => 'งานถึงกำหนดพรุ่งนี้', + 'Tasks due yesterday' => 'งานถึงกำหนดเมื่อวาน', + 'Closed tasks' => 'งานปิด', + 'Open tasks' => 'งานเปิด', + 'Not assigned' => 'ไม่กำหนดใคร', + 'View advanced search syntax' => 'แสดงรูปแบบการค้นหาขั้นสูง', + 'Overview' => 'ภาพรวม', + 'Board/Calendar/List view' => 'มุมมอง บอร์ด/ปฎิทิน/ลิสต์', + 'Switch to the board view' => 'เปลี่ยนเป็นมุมมองบอร์ด', + 'Switch to the list view' => 'เปลี่ยนเป็นมุมมองลิสต์', + 'Go to the search/filter box' => 'ไปที่กล่องค้นหา/ตัวกรอง', + 'There is no activity yet.' => 'ตอนนี้ไม่มีกิจกรรม', + 'No tasks found.' => 'ไม่พบงาน', + 'Keyboard shortcut: "%s"' => 'คีย์ลัด: %s', + 'List' => 'ลิสต์', + 'Filter' => 'ตัวกรอง', + 'Advanced search' => 'ค้นหาขั้นสูง', + 'Example of query: ' => 'ตัวอย่างคิวรี: ', + 'Search by project: ' => 'ค้นหาตามโปรเจค: ', + 'Search by column: ' => 'ค้นหาตามคอลัมน์: ', + 'Search by assignee: ' => 'ค้นหาตามผู้รับผิดชอบ: ', + 'Search by color: ' => 'ค้นหาตามสี: ', + 'Search by category: ' => 'ค้นหาตามหมวด: ', + 'Search by description: ' => 'ค้นหาตามคำอธิบาย: ', + 'Search by due date: ' => 'ค้นหาตามวันครบกำหนด: ', + 'Average time spent in each column' => 'ค่าเฉลี่ยเวลาที่ใช้แต่ละคอลัมน์', + 'Average time spent' => 'ค่าเฉลี่ยเวลาที่ใช้', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'แผนภูมิแสดงค่าเฉลี่ยเวลาที่ใช้แต่ละคอลัมน์สำหรับ %d งานล่าสุด', + 'Average Lead and Cycle time' => 'ค่าเฉลี่ยเวลานำและรอบเวลา', + 'Average lead time: ' => 'ค่าเฉลี่ยเวลานำ: ', + 'Average cycle time: ' => 'ค่าเฉลี่ยรอบเวลา: ', + 'Cycle Time' => 'รอบเวลา', + 'Lead Time' => 'เวลานำ', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'แผนภูมิแสดงค่าเฉลี่ยเวลานำและรอบเวลาสำหรับ %d งานล่าสุดเมื่อเวลาผ่านไป', + 'Average time into each column' => 'ค่าเฉลี่ยเวลาแต่ละคอลัมน์', + 'Lead and cycle time' => 'เวลานำและรอบเวลา', + 'Lead time: ' => 'เวลานำ: ', + 'Cycle time: ' => 'รอบเวลา: ', + 'Time spent in each column' => 'เวลาที่ใช้แต่ละคอลัมน์', + 'The lead time is the duration between the task creation and the completion.' => 'เวลานำคือระหว่างสร้างงานและจบงาน', + 'The cycle time is the duration between the start date and the completion.' => 'รอบเวลาคือระหว่างวันที่เริ่มและจบงาน', + 'If the task is not closed the current time is used instead of the completion date.' => 'หากงานยังไม่ถูกปิด จะใช้เวลาปัจจุบันแทนวันที่เสร็จสมบูรณ์', + 'Set the start date automatically' => 'ตั้งค่าวันที่เริ่มต้นอัตโนมัติ', + 'Edit Authentication' => 'แก้ไขการตรวจสอบ', + 'Remote user' => 'ผู้ใช้รีโมท', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'ผู้ใช้ระยะไกลจะไม่เก็บรหัสผ่านไว้ในฐานข้อมูล Kanboard ตัวอย่าง: บัญชี LDAP, Google และ Github', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'หากคุณทำเครื่องหมายที่ช่อง "ไม่อนุญาตแบบฟอร์มเข้าสู่ระบบ" ข้อมูลรับรองที่ป้อนในแบบฟอร์มเข้าสู่ระบบจะถูกละเว้น', + 'Default task color' => 'สีเริ่มต้นของงาน', + 'This feature does not work with all browsers.' => 'คุณลักษณะนี้ไม่สามารถทำงานได้ทุกเบราเซอร์', + 'There is no destination project available.' => 'ไม่มีโครงการปลายทางที่ใช้งานได้', + 'Trigger automatically subtask time tracking' => 'เรียกโดยอัตโนมัติการติดตามเวลางานย่อย', + 'Include closed tasks in the cumulative flow diagram' => 'รวมงานที่ปิดแล้วในแผนภาพกระแสสะสม', + 'Current swimlane: %s' => 'สวิมเลนปัจจุบัน: %s', + 'Current column: %s' => 'คอลัมน์ปัจจุบัน: %s', + 'Current category: %s' => 'หมวดปัจจุบัน: %s', + 'no category' => 'ไม่มีหมวด', + 'Current assignee: %s' => 'ผู้รับผิดชอบปัจจุบัน: %s', + 'not assigned' => 'ไม่กำหนด', + 'Author:' => 'ผู้แต่ง:', + 'contributors' => 'ผู้ให้กำเนิด', + 'License:' => 'สัญญาอนุญาต:', + 'License' => 'สัญญาอนุญาต', + 'Enter the text below' => 'พิมพ์ข้อความด้านล่าง', + 'Start date:' => 'วันที่เริ่ม:', + 'Due date:' => 'วันครบกำหนด:', + 'People who are project managers' => 'คนที่เป็นผู้จัดการโปรเจค', + 'People who are project members' => 'คนที่เป็นสมาชิกโปรเจค', + 'NOK - Norwegian Krone' => 'NOK - โครนนอร์เวย์', + 'Show this column' => 'แสดงคอลัมนี้', + 'Hide this column' => 'ซ่อนคอลัมน์นี้', + 'End date' => 'วันจบ', + 'Users overview' => 'ภาพรวมผู้ใช้', + 'Members' => 'สมาชิก', + 'Shared project' => 'แชร์โปรเจค', + 'Project managers' => 'ผู้จัดการโปรเจค', + 'Projects list' => 'รายการโปรเจค', + 'End date:' => 'วันที่จบ:', + 'Change task color when using a specific task link' => 'เปลี่ยนสีงานเมื่อมีการใช้การเชื่อมโยงงาน', + 'Task link creation or modification' => 'การสร้างการเชื่อมโยงงานหรือการปรับเปลี่ยน', + 'Milestone' => 'ขั้น', + 'Reset the search/filter box' => 'รีเซตกล่องค้นหา/ตัวกรอง', + 'Documentation' => 'เอกสาร', + 'Author' => 'ผู้แต่ง', + 'Version' => 'เวอร์ชัน', + 'Plugins' => 'ปลั๊กอิน', + 'There is no plugin loaded.' => 'ไม่มีปลั๊กอินถูกโหลดไว้', + 'My notifications' => 'การแจ้งเตือนของฉัน', + 'Custom filters' => 'ตัวกรองกำหนดเอง', + 'Your custom filter has been created successfully.' => 'ตัวกรองกำหนดเองของคุณสร้างเรียบร้อย', + 'Unable to create your custom filter.' => 'ไม่สามารถสร้างตัวกรองกำหนดเอง', + 'Custom filter removed successfully.' => 'ลบตัวกรองกำหนดเองเรียบร้อย', + 'Unable to remove this custom filter.' => 'ไม่สามารถลบตัวกรองกำหนดเอง', + 'Edit custom filter' => 'แก้ไขตัวกรองกำหนดเอง', + 'Your custom filter has been updated successfully.' => 'ตัวกรองกำหนดเองของคุณแก้ไขเรียบร้อย', + 'Unable to update custom filter.' => 'ไม่สามารถแก้ไขตัวกรองกำหนดเอง', + 'Web' => 'เวบ', + 'New attachment on task #%d: %s' => 'แนบใหม่ของงาน #%d: %s', + 'New comment on task #%d' => 'ความคิดเห็นใหม่ของงาน #%d', + 'Comment updated on task #%d' => 'แก้ไขความคิดเห็นของงาน #%d', + 'New subtask on task #%d' => 'งานย่อยใหม่ของงาน #%d', + 'Subtask updated on task #%d' => 'แก้ไขงานย่อยของงาน #%d', + 'New task #%d: %s' => 'งานใหม่ #%d: %s', + 'Task updated #%d' => 'แก้ไขงาน #%d', + 'Task #%d closed' => 'ปิดงาน #%d', + 'Task #%d opened' => 'เปิดงาน #%d', + 'Column changed for task #%d' => 'เปลี่ยนคอลัมน์สำหรับงาน #%d', + 'New position for task #%d' => 'ตำแหน่งใหม่ของงาน #%d', + 'Swimlane changed for task #%d' => 'เปลี่ยนสวิมเลนสำหรับงาน #%d', + 'Assignee changed on task #%d' => 'เปลี่ยนผู้รับผิดชอบงาน #%d', + '%d overdue tasks' => '%d งานเกินกำหนด', + 'No notification.' => 'ไม่มีการแจ้งเตือนใหม่', + 'Mark all as read' => 'มาร์คทั้งหมดว่าอ่านแล้ว', + 'Mark as read' => 'มาร์คว่าอ่านแล้ว', + 'Total number of tasks in this column across all swimlanes' => 'จำนวนงานทั้งหมดในคอลัมน์นี้ในทุกเลน', + 'Collapse swimlane' => 'ย่อสวิมเลน', + 'Expand swimlane' => 'ขยายสวิมเลน', + 'Add a new filter' => 'เพิ่มตัวกรองใหม่', + 'Share with all project members' => 'แชร์ให้สมาชิกทุกคนของโปรเจค', + 'Shared' => 'แชร์', + 'Owner' => 'เจ้าของ', + 'Unread notifications' => 'การแจ้งเตือนยังไม่ได้อ่าน', + 'Notification methods:' => 'ลักษณะการแจ้งเตือน:', + 'Unable to read your file' => 'ไม่สามารถอ่านไฟล์ของคุณ', + '%d task(s) have been imported successfully.' => '%d งานนำเข้าเรียบร้อย', + 'Nothing has been imported!' => 'ไม่มีอะไรถูกนำเข้า', + 'Import users from CSV file' => 'นำเข้าผู้ใช้จากไฟล์ CSV', + '%d user(s) have been imported successfully.' => '%d ผู้ใช้นำเข้าเรียบร้อย', + 'Comma' => ', - Comma', + 'Semi-colon' => '; - Semi-colon', + 'Tab' => 'Tab - Tab', + 'Vertical bar' => '| - Vertical bar', + 'Double Quote' => '" " - Double Quote', + 'Single Quote' => '\' \' - Single Quote', + '%s attached a file to the task #%d' => '%s แนบไฟล์ในงาน #%d', + 'There is no column or swimlane activated in your project!' => 'ไม่มีคอลัมน์หรือสวิมเลนเปิดใช้งานในโปรเจคของคุณ!', + 'Append filter (instead of replacement)' => 'เพิ่มตัวกรอง (แทนที่การแทนที่)', + 'Append/Replace' => 'เพิ่มเติม/แทนที่', + 'Append' => 'เพิ่มเติม', + 'Replace' => 'แทนที่', + 'Import' => 'นำเข้า', + 'Change sorting' => 'เปลี่ยนการเรียง', + 'Tasks Importation' => 'การนำเข้างาน', + 'Delimiter' => 'คั่น', + 'Enclosure' => 'กำหนดข้อความ', + 'CSV File' => 'ไฟล์ CSV', + 'Instructions' => 'คำสั่ง', + 'Your file must use the predefined CSV format' => 'ไฟล์ของคุณจะต้องใช้รูปแบบ CSV ที่กำหนดไว้ล่วงหน้า', + 'Your file must be encoded in UTF-8' => 'ไฟล์ของคุณต้องเอนโค้ดด้วย UTF-8', + 'The first row must be the header' => 'แถวแรกต้องเป็นหัวข้อ', + 'Duplicates are not verified for you' => 'รายการที่ซ้ำกันจะไม่ได้รับการตรวจสอบสำหรับคุณ', + 'The due date must use the ISO format: YYYY-MM-DD' => 'วันที่ต้องอยู่ในรูปแบบ ISO: YYYY-MM-DD', + 'Download CSV template' => 'ดาวน์โหลด CSV ต้นฉบับ', + 'No external integration registered.' => 'ไม่มีการรวมภายนอกถูกลงทะเบียนไว้', + 'Duplicates are not imported' => 'ซ้ำกันไม่สามารถนำเข้าได้', + 'Usernames must be lowercase and unique' => 'ชื่อผู้ใช้ต้องเป็นตัวพิมพ์เล็กและไม่ซ้ำ', + 'Passwords will be encrypted if present' => 'รหัสผ่านจะถูกเข้ารหัสหากมีอยู่', + '%s attached a new file to the task %s' => '%s แนบไฟล์ใหม่ในงาน %s', + 'Link type' => 'ประเภทลิงค์', + 'Assign automatically a category based on a link' => 'กำหนดหมวดอัตโนมัติตามลิงค์', + 'BAM - Konvertible Mark' => 'BAM - มาร์คแปลงสภาพ', + 'Assignee Username' => 'กำหนดชื่อผู้ใช้', + 'Assignee Name' => 'กำหนดชื่อ', + 'Groups' => 'กลุ่ม', + 'Members of %s' => 'สมาชิกของ %s', + 'New group' => 'กลุ่มใหม่', + 'Group created successfully.' => 'สร้างกลุ่มสำเร็จ', + 'Unable to create your group.' => 'ไม่สามารถสร้างกลุ่มของคุณ', + 'Edit group' => 'แก้ไขกลุ่ม', + 'Group updated successfully.' => 'แก้ไขกลุ่มเรียบร้อย', + 'Unable to update your group.' => 'ไม่สามารถแก้ไขกลุ่มของคุณ', + 'Add group member to "%s"' => 'เพิ่มสมาชิกในกลุ่ม %s', + 'Group member added successfully.' => 'เพิ่มสมาชิกกลุ่มเรียบร้อย', + 'Unable to add group member.' => 'ไม่สามารถเพิ่มสมาชิกกลุ่ม', + 'Remove user from group "%s"' => 'เอาผู้ใช้ออกจากกลุ่ม %s', + 'User removed successfully from this group.' => 'เอาผู้ใช้ออกจากกลุ่มนี้เรียบร้อย', + 'Unable to remove this user from the group.' => 'ไม่สามารถลบผู้ใช้ออกจากกลุ่มนี้', + 'Remove group' => 'ลบกลุ่ม', + 'Group removed successfully.' => 'ลบกลุ่มเรียบร้อย', + 'Unable to remove this group.' => 'ไม่สามารถลบกลุ่มนี้', + 'Project Permissions' => 'การอนุญาตใช้งานโปรเจค', + 'Manager' => 'ผู้จัดการ', + 'Project Manager' => 'ผู้จัดการโปรเจค', + 'Project Member' => 'สมาชิกโปรเจค', + 'Project Viewer' => 'ผู้ดูโปรเจค', + 'Your account is locked for %d minutes' => 'บัญชีของคุณถูกล็อก %d นาที', + 'Invalid captcha' => 'captcha ไม่ถูกต้อง', + 'The name must be unique' => 'ชื่อต้องไม่ซ้ำ', + 'View all groups' => 'แสดงกลุ่มทั้งหมด', + 'There is no user available.' => 'ไม่มีผู้ใช้ที่ใช้งานได้', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'คุณต้องการลบผู้ใช้ "%s" ออกจากกลุ่ม "%s"?', + 'There is no group.' => 'ไม่มีกลุ่ม', + 'Add group member' => 'เพิ่มสมาชิกกลุ่ม', + 'Do you really want to remove this group: "%s"?' => 'คุณต้องการลบกลุ่มนี้: "%s"?', + 'There is no user in this group.' => 'ไม่มีผู้ใช้ในกลุ่มนี้', + 'Permissions' => 'การอนุญาตใช้งาน', + 'Allowed Users' => 'การอนุญาตผู้ใช้', + 'No specific user has been allowed.' => 'ไม่มีผู้ใช้ได้รับอนุญาตเป็นพิเศษ', + 'Role' => 'บทบาท', + 'Enter user name...' => 'พิมพ์ชื่อผู้ใช้...', + 'Allowed Groups' => 'อนุญาตกลุ่ม', + 'No group has been allowed.' => 'ไม่มีกลุ่มได้รับอนุญาตเป็นพิเศษ', + 'Group' => 'กลุ่ม', + 'Group Name' => 'ชื่อกลุ่ม', + 'Enter group name...' => 'พิมพ์ชื่อกลุ่ม...', + 'Role:' => 'บทบาท:', + 'Project members' => 'สมาชิกโปรเจค', + '%s mentioned you in the task #%d' => '%s กล่าวถึงคุณในงาน #%d', + '%s mentioned you in a comment on the task #%d' => '%s กล่าวถึงคุณในความคิดเห็นของงาน #%d', + 'You were mentioned in the task #%d' => 'คุณได้รับการกล่าวถึงในงาน #%d', + 'You were mentioned in a comment on the task #%d' => 'คุณได้รับการกล่าวถึงในความคิดเห็นของงาน #%d', + 'Estimated hours: ' => 'เวลาโดยประมาณ:', + 'Actual hours: ' => 'เวลาที่เกิดขึ้นจริง:', + 'Hours Spent' => 'เวลาที่ใช้', + 'Hours Estimated' => 'เวลาโดยประมาณ', + 'Estimated Time' => 'เวลาโดยประมาณ', + 'Actual Time' => 'เวลาที่ใช้', + 'Estimated vs actual time' => 'เวลาโดยประมาณกับเวลาจริง', + 'RUB - Russian Ruble' => 'RUB - รูเบิลรัสเซีย', + 'Assign the task to the person who does the action when the column is changed' => 'กำหนดผู้รับผิดชอบงานเมื่อเปลี่ยนคอลัมน์', + 'Close a task in a specific column' => 'ปิดงานในคอลัมน์ที่เฉพาะเจาะจง', + 'Time-based One-time Password Algorithm' => 'อัลกอริทึมรหัสผ่านแบบใช้ครั้งเดียวตามเวลา', + 'Two-Factor Provider: ' => 'ผู้ให้บริการการยืนยันแบบสองขั้นตอน:', + 'Disable two-factor authentication' => 'ปิดใช้งานการยืนยันแบบสองขั้นตอน', + 'Enable two-factor authentication' => 'เปิดใช้งานการยืนยันแบบสองขั้นตอน', + 'There is no integration registered at the moment.' => 'ขณะนี้ไม่มีการลงทะเบียนการรวมระบบ', + 'Password Reset for Kanboard' => 'รีเซตรหัสผ่านสำหรับคังบอร์ด', + 'Forgot password?' => 'ลืมรหัสผ่าน?', + 'Enable "Forget Password"' => 'เปิดการใช้งาน "ลืมรหัสผ่าน"', + 'Password Reset' => 'รีเซตรหัสผ่าน', + 'New password' => 'รหัสผ่านใหม่', + 'Change Password' => 'เปลี่ยนรหัสผ่าน', + 'To reset your password click on this link:' => 'ในการรีเซตรหัสผ่านของคุณคลิ๊กที่ลิงค์นี้:', + 'Last Password Reset' => 'รีเซตรหัสผ่านครั้งล่าสุด', + 'The password has never been reinitialized.' => 'รหัสผ่านไม่เคยเริ่มใหม่อีกครั้ง', + 'Creation' => 'สร้าง', + 'Expiration' => 'สิ้นสุด', + 'Password reset history' => 'ประวัติการรีเซตรหัสผ่าน', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'ทุกงานของคอลัมน์ "%s" และสวิมเลน "%s" ถูกปิดเรียบร้อย', + 'Do you really want to close all tasks of this column?' => 'คุณต้องการปิดทุกงานในคอลัมนี้ใช่หรือไม่?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d งานในคอลัมน์ "%s" และสวิมเลน "%s" จะปิด', + 'Close all tasks in this column and this swimlane' => 'ปิดทุกงานในคอลัมน์นี้', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'ปลั๊กอินไม่ได้ลงทะเบียนการแจ้งเตือนในโปรเจค คุณยังสามารถกำหนดค่าการแจ้งเตือนรายบุคคลในโปรไฟล์ผู้ใช้ของคุณ', + 'My dashboard' => 'แดชบอร์ดของฉํน', + 'My profile' => 'โปรเจคของฉัน', + 'Project owner: ' => 'เจ้าของโปรเจค: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'ตัวบ่งชี้โปรโจคเป็นตัวเลือกเสริมและต้องเป็นตัวอักษรหรือตัวเลข ตัวอย่าง: MYPROJECT', + 'Project owner' => 'เจ้าของโปรเจค', + 'Personal projects do not have users and groups management.' => 'โปรเจคส่วนตัวไม่มีการจัดการผู้ใช้และกลุ่ม', + 'There is no project member.' => 'ไม่มีสมาชิกโปรเจค', + 'Priority' => 'ความสำคัญ', + 'Task priority' => 'ความสำคัญของงาน', + 'General' => 'ทั่วไป', + 'Dates' => 'วันที่', + 'Default priority' => 'ความสำคัญเริ่มต้น', + 'Lowest priority' => 'ความสำคัญต่ำสุด', + 'Highest priority' => 'ความสำคัญสูงสุด', + 'Close a task when there is no activity' => 'ปิดงานเมื่อไม่มีกิจกกรมเกิดขึ้น', + 'Duration in days' => 'ระยะเวลาวันที่', + 'Send email when there is no activity on a task' => 'ส่งอีเมลเมื่อไม่มีกิจกรรมเกิดขึ้นในงาน', + 'Unable to fetch link information.' => 'ไม่สามารถดึงข้อมูลการเชื่อมโยง', + 'Daily background job for tasks' => 'งานเบื้องหลังรายวันสำหรับงาน', + 'Auto' => 'อัตโนมัติ', + 'Related' => 'ที่เกี่ยวข้อง', + 'Attachment' => 'แนบ', + 'Web Link' => 'เวบลิงค์', + 'External links' => 'เชื่อมโยงภายนอก', + 'Add external link' => 'เพิ่มการเชื่อมโยงภายนอก', + 'Type' => 'ประเภท', + 'Dependency' => 'ขึ้นอยู่กับ', + 'Add internal link' => 'เพิ่มการเชื่อมโยงภายใน', + 'Add a new external link' => 'เพิ่มการเชื่อมโยงภายนอกใหม่', + 'Edit external link' => 'แก้ไขการเชื่อมโยงภายนอก', + 'External link' => 'เชื่อมโยงภายนอก', + 'Copy and paste your link here...' => 'คัดลอกและวางลิงค์ของคุณที่นี้...', + 'URL' => 'URL', + 'Internal links' => 'เชื่อมโยงภายใน', + 'Assign to me' => 'ฉันรับผิดชอบ', + 'Me' => 'ฉัน', + 'Do not duplicate anything' => 'ไม่ต้องทำซ้ำอะไรเลย', + 'Projects management' => 'การจัดการโปรเจค', + 'Users management' => 'การจัดการผู้ใช้', + 'Groups management' => 'การจัดการกลุ่ม', + 'Create from another project' => 'สร้างโปรเจคอื่น', + 'open' => 'เปิด', + 'closed' => 'ปิด', + 'Priority:' => 'ความสำคัญ:', + 'Reference:' => 'อ้างถึง:', + 'Complexity:' => 'ความซับซ้อน:', + 'Swimlane:' => 'สวิมเลน:', + 'Column:' => 'คอลัมน์:', + 'Position:' => 'ตำแหน่ง:', + 'Creator:' => 'ผู้สร้าง:', + 'Time estimated:' => 'เวลาเฉลี่ย:', + '%s hours' => '%s ชั่วโมง', + 'Time spent:' => 'ใช้เวลา:', + 'Created:' => 'สร้าง:', + 'Modified:' => 'แก้ไข:', + 'Completed:' => 'เสร็จสิ้น:', + 'Started:' => 'เริ่ม:', + 'Moved:' => 'ย้าย:', + 'Task #%d' => 'งานที่ #%d', + 'Time format' => 'รูปแบบของเวลา', + 'Start date: ' => 'เริ่มวันที่:', + 'End date: ' => 'จบวันที่:', + 'New due date: ' => 'วันครบกำหนดใหม่', + 'Start date changed: ' => 'เปลี่ยนวันที่เริ่ม', + 'Disable personal projects' => 'ปิดใช้งานโครงการส่วนตัว', + 'Do you really want to remove this custom filter: "%s"?' => 'คุณต้องการลบตัวกรองที่กำหนดเองนี้จริง ๆ หรือไม่: "%s"?', + 'Remove a custom filter' => 'ลบตัวกรองที่กำหนดเอง', + 'User activated successfully.' => 'เปิดใช้งานผู้ใช้สำเร็จแล้ว', + 'Unable to enable this user.' => 'ไม่สามารถเปิดใช้งานผู้ใช้นี้ได้', + 'User disabled successfully.' => 'ปิดใช้งานผู้ใช้สำเร็จแล้ว', + 'Unable to disable this user.' => 'ไม่สามารถปิดใช้งานผู้ใช้นี้ได้', + 'All files have been uploaded successfully.' => 'อัปโหลดไฟล์ทั้งหมดสำเร็จแล้ว', + 'The maximum allowed file size is %sB.' => 'ขนาดไฟล์สูงสุดที่อนุญาตคือ %sB', + 'Drag and drop your files here' => 'ลากและวางไฟล์ของคุณที่นี่', + 'choose files' => 'เลือกไฟล์', + 'View profile' => 'ดูโปรไฟล์', + 'Two Factor' => 'สองปัจจัย', + 'Disable user' => 'ปิดใช้งานผู้ใช้', + 'Do you really want to disable this user: "%s"?' => 'คุณต้องการปิดใช้งานผู้ใช้นี้จริง ๆ หรือไม่: "%s"?', + 'Enable user' => 'เปิดใช้งานผู้ใช้', + 'Do you really want to enable this user: "%s"?' => 'คุณต้องการเปิดใช้งานผู้ใช้นี้จริง ๆ หรือไม่: "%s"?', + 'Download' => 'ดาวน์โหลด', + 'Uploaded: %s' => 'อัปโหลด: %s', + 'Size: %s' => 'ขนาด: %s', + 'Uploaded by %s' => 'อัปโหลดโดย %s', + 'Filename' => 'ชื่อไฟล์', + 'Size' => 'ขนาด', + 'Column created successfully.' => 'สร้างคอลัมน์สำเร็จแล้ว', + 'Another column with the same name exists in the project' => 'มีคอลัมน์อื่นที่มีชื่อเดียวกันอยู่ในโครงการ', + 'Default filters' => 'ตัวกรองเริ่มต้น', + 'Your board doesn\'t have any columns!' => 'บอร์ดของคุณไม่มีคอลัมน์!', + 'Change column position' => 'เปลี่ยนตำแหน่งคอลัมน์', + 'Switch to the project overview' => 'เปลี่ยนไปที่ภาพรวมโครงการ', + 'User filters' => 'ตัวกรองผู้ใช้', + 'Category filters' => 'ตัวกรองหมวดหมู่', + 'Upload a file' => 'อัปโหลดไฟล์', + 'View file' => 'ดูไฟล์', + 'Last activity' => 'กิจกรรมล่าสุด', + 'Change subtask position' => 'เปลี่ยนตำแหน่งของงานย่อย', + 'This value must be greater than %d' => 'ค่านี้นต้องมากกว่า %d', + 'Another swimlane with the same name exists in the project' => 'มีเลนอื่นที่มีชื่อเดียวกันอยู่ในโครงการ', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'ตัวอย่าง: https://example.kanboard.org/ (ใช้สร้าง URL แบบเต็ม)', + 'Actions duplicated successfully.' => 'ทำซ้ำการดำเนินการสำเร็จแล้ว', + 'Unable to duplicate actions.' => 'ไม่สามารถทำซ้ำการดำเนินการได้', + 'Add a new action' => 'เพิ่มการดำเนินการใหม่', + 'Import from another project' => 'นำเข้าจากโครงการอื่น', + 'There is no action at the moment.' => 'ขณะนี้ไม่มีการดำเนินการ', + 'Import actions from another project' => 'นำเข้าการดำเนินการจากโครงการอื่น', + 'There is no available project.' => 'ไม่มีโครงการที่ใช้งานได้', + 'Local File' => 'ไฟล์ภายในเครื่อง', + 'Configuration' => 'การกำหนดค่า', + 'PHP version:' => 'เวอร์ชัน PHP:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'เวอร์ชัน OS:', + 'Database version:' => 'เวอร์ชันฐานข้อมูล:', + 'Browser:' => 'เบราว์เซอร์:', + 'Task view' => 'มุมมองงาน', + 'Edit task' => 'แก้ไขงาน', + 'Edit description' => 'แก้ไขคำอธิบาย', + 'New internal link' => 'ลิงก์ภายในใหม่', + 'Display list of keyboard shortcuts' => 'แสดงรายการแป้นพิมพ์ลัด', + 'Avatar' => 'อวตาร', + 'Upload my avatar image' => 'อัปโหลดรูปอวตารของฉัน', + 'Remove my image' => 'ลบรูปภาพของฉัน', + 'The OAuth2 state parameter is invalid' => 'พารามิเตอร์สถานะ OAuth2 ไม่ถูกต้อง', + 'User not found.' => 'ไม่พบผู้ใช้', + 'Search in activity stream' => 'ค้นหาในสตรีมกิจกรรม', + 'My activities' => 'กิจกรรมของฉัน', + 'Activity until yesterday' => 'กิจกรรมจนถึงเมื่อวาน', + 'Activity until today' => 'กิจกรรมจนถึงวันนี้', + 'Search by creator: ' => 'ค้นหาตามผู้สร้าง:', + 'Search by creation date: ' => 'ค้นหาตามวันที่สร้าง:', + 'Search by task status: ' => 'ค้นหาตามสถานะงาน:', + 'Search by task title: ' => 'ค้นหาตามชื่อเรื่องงาน:', + 'Activity stream search' => 'ค้นหาสตรีมกิจกรรม', + 'Projects where "%s" is manager' => 'โครงการที่ "%s" เป็นผู้จัดการ', + 'Projects where "%s" is member' => 'โครงการที่ "%s" เป็นสมาชิก', + 'Open tasks assigned to "%s"' => 'งานที่เปิดซึ่งมอบหมายให้ "%s"', + 'Closed tasks assigned to "%s"' => 'งานที่ปิดซึ่งมอบหมายให้ "%s"', + 'Assign automatically a color based on a priority' => 'กำหนดสีโดยอัตโนมัติตามลำดับความสำคัญ', + 'Overdue tasks for the project(s) "%s"' => 'งานที่เลยกำหนดสำหรับโครงการ "%s"', + 'Upload files' => 'อัปโหลดไฟล์', + 'Installed Plugins' => 'ปลั๊กอินที่ติดตั้ง', + 'Plugin Directory' => 'ไดเรกทอรีปลั๊กอิน', + 'Plugin installed successfully.' => 'ติดตั้งปลั๊กอินสำเร็จแล้ว', + 'Plugin updated successfully.' => 'อัปเดตปลั๊กอินสำเร็จแล้ว', + 'Plugin removed successfully.' => 'ลบปลั๊กอินสำเร็จแล้ว', + 'Subtask converted to task successfully.' => 'แปลงงานย่อยเป็นงานสำเร็จแล้ว', + 'Unable to convert the subtask.' => 'ไม่สามารถแปลงงานย่อยได้', + 'Unable to extract plugin archive.' => 'ไม่สามารถแยกไฟล์เก็บถาวรปลั๊กอินได้', + 'Plugin not found.' => 'ไม่พบปลั๊กอิน', + 'You don\'t have the permission to remove this plugin.' => 'คุณไม่มีสิทธิ์ลบปลั๊กอินนี้', + 'Unable to download plugin archive.' => 'ไม่สามารถดาวน์โหลดไฟล์เก็บถาวรปลั๊กอินได้', + 'Unable to write temporary file for plugin.' => 'ไม่สามารถเขียนไฟล์ชั่วคราวสำหรับปลั๊กอินได้', + 'Unable to open plugin archive.' => 'ไม่สามารถเปิดไฟล์เก็บถาวรปลั๊กอินได้', + 'There is no file in the plugin archive.' => 'ไม่มีไฟล์ในไฟล์เก็บถาวรปลั๊กอิน', + 'Create tasks in bulk' => 'สร้างงานจำนวนมาก', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'อินสแตนซ์ Kanboard ของคุณไม่ได้กำหนดค่าให้ติดตั้งปลั๊กอินจากส่วนต่อประสานผู้ใช้', + 'There is no plugin available.' => 'ไม่มีปลั๊กอินที่ใช้งานได้', + 'Install' => 'ติดตั้ง', + 'Update' => 'อัปเดต', + 'Up to date' => 'ล่าสุดแล้ว', + 'Not available' => 'ไม่พร้อมใช้งาน', + 'Remove plugin' => 'ลบปลั๊กอิน', + 'Do you really want to remove this plugin: "%s"?' => 'คุณต้องการลบปลั๊กอินนี้จริง ๆ หรือไม่: "%s"?', + 'Uninstall' => 'ถอนการติดตั้ง', + 'Listing' => 'รายการ', + 'Metadata' => 'ข้อมูลเมตา', + 'Manage projects' => 'จัดการโครงการ', + 'Convert to task' => 'แปลงเป็นงาน', + 'Convert sub-task to task' => 'แปลงงานย่อยเป็นงาน', + 'Do you really want to convert this sub-task to a task?' => 'คุณต้องการแปลงงานย่อยนี้เป็นงานจริง ๆ หรือไม่?', + 'My task title' => 'ชื่อเรื่องงานของฉัน', + 'Enter one task by line.' => 'ป้อนหนึ่งงานต่อบรรทัด', + 'Number of failed login:' => 'จำนวนการเข้าสู่ระบบที่ล้มเหลว:', + 'Account locked until:' => 'บัญชีถูกล็อกจนถึง:', + 'Email settings' => 'การตั้งค่าอีเมล', + 'Email sender address' => 'ที่อยู่อีเมลผู้ส่ง', + 'Email transport' => 'การขนส่งอีเมล', + 'Webhook token' => 'โทเค็น Webhook', + 'Project tags management' => 'การจัดการแท็กโครงการ', + 'Tag created successfully.' => 'สร้างแท็กสำเร็จแล้ว', + 'Unable to create this tag.' => 'ไม่สามารถสร้างแท็กนี้ได้', + 'Tag updated successfully.' => 'อัปเดตแท็กสำเร็จแล้ว', + 'Unable to update this tag.' => 'ไม่สามารถอัปเดตแท็กนี้ได้', + 'Tag removed successfully.' => 'ลบแท็กสำเร็จแล้ว', + 'Unable to remove this tag.' => 'ไม่สามารถลบแท็กนี้ได้', + 'Global tags management' => 'การจัดการแท็กสากล', + 'Tags' => 'แท็ก', + 'Tags management' => 'การจัดการแท็ก', + 'Add new tag' => 'เพิ่มแท็กใหม่', + 'Edit a tag' => 'แก้ไขแท็ก', + 'Project tags' => 'แท็กโครงการ', + 'There is no specific tag for this project at the moment.' => 'ขณะนี้ไม่มีแท็กเฉพาะสำหรับโครงการนี้', + 'Tag' => 'แท็ก', + 'Remove a tag' => 'ลบแท็ก', + 'Do you really want to remove this tag: "%s"?' => 'คุณต้องการลบแท็กนี้จริง ๆ หรือไม่: "%s"?', + 'Global tags' => 'แท็กสากล', + 'There is no global tag at the moment.' => 'ขณะนี้ไม่มีแท็กสากล', + 'This field cannot be empty' => 'ช่องนี้ต้องไม่ว่างเปล่า', + 'Close a task when there is no activity in a specific column' => 'ปิดงานเมื่อไม่มีกิจกรรมในคอลัมน์ที่ระบุ', + '%s removed a subtask for the task #%d' => '%s ลบงานย่อยสำหรับงาน #%d', + '%s removed a comment on the task #%d' => '%s ลบความคิดเห็นในงาน #%d', + 'Comment removed on task #%d' => 'ลบความคิดเห็นในงาน #%d', + 'Subtask removed on task #%d' => 'ลบงานย่อยในงาน #%d', + 'Hide tasks in this column in the dashboard' => 'ซ่อนงานในคอลัมน์นี้ในแดชบอร์ด', + '%s removed a comment on the task %s' => '%s ลบความคิดเห็นในงาน %s', + '%s removed a subtask for the task %s' => '%s ลบงานย่อยสำหรับงาน %s', + 'Comment removed' => 'ลบความคิดเห็นแล้ว', + 'Subtask removed' => 'ลบงานย่อยแล้ว', + '%s set a new internal link for the task #%d' => '%s ตั้งค่าลิงก์ภายในใหม่สำหรับงาน #%d', + '%s removed an internal link for the task #%d' => '%s ลบลิงก์ภายในสำหรับงาน #%d', + 'A new internal link for the task #%d has been defined' => 'มีการกำหนดลิงก์ภายในใหม่สำหรับงาน #%d แล้ว', + 'Internal link removed for the task #%d' => 'ลบลิงก์ภายในสำหรับงาน #%d แล้ว', + '%s set a new internal link for the task %s' => '%s ตั้งค่าลิงก์ภายในใหม่สำหรับงาน %s', + '%s removed an internal link for the task %s' => '%s ลบลิงก์ภายในสำหรับงาน %s', + 'Automatically set the due date on task creation' => 'ตั้งค่าวันครบกำหนดโดยอัตโนมัติเมื่อสร้างงาน', + 'Move the task to another column when closed' => 'ย้ายงานไปยังคอลัมน์อื่นเมื่อปิด', + 'Move the task to another column when not moved during a given period' => 'ย้ายงานไปยังคอลัมน์อื่นเมื่อไม่มีการย้ายในช่วงเวลาที่กำหนด', + 'Dashboard for %s' => 'แดชบอร์ดสำหรับ %s', + 'Tasks overview for %s' => 'ภาพรวมงานสำหรับ %s', + 'Subtasks overview for %s' => 'ภาพรวมงานย่อยสำหรับ %s', + 'Projects overview for %s' => 'ภาพรวมโครงการสำหรับ %s', + 'Activity stream for %s' => 'สตรีมกิจกรรมสำหรับ %s', + 'Assign a color when the task is moved to a specific swimlane' => 'กำหนดสีเมื่อย้ายงานไปยังเลนเฉพาะ', + 'Assign a priority when the task is moved to a specific swimlane' => 'กำหนดลำดับความสำคัญเมื่อย้ายงานไปยังเลนเฉพาะ', + 'User unlocked successfully.' => 'ปลดล็อกผู้ใช้สำเร็จแล้ว', + 'Unable to unlock the user.' => 'ไม่สามารถปลดล็อกผู้ใช้ได้', + 'Move a task to another swimlane' => 'ย้ายงานไปยังเลนอื่น', + 'Creator Name' => 'ชื่อผู้สร้าง', + 'Time spent and estimated' => 'เวลาที่ใช้และประมาณ', + 'Move position' => 'ย้ายตำแหน่ง', + 'Move task to another position on the board' => 'ย้ายงานไปยังตำแหน่งอื่นบนบอร์ด', + 'Insert before this task' => 'แทรกก่อนงานนี้', + 'Insert after this task' => 'แทรกหลังงานนี้', + 'Unlock this user' => 'ปลดล็อกผู้ใช้นี้', + 'Custom Project Roles' => 'บทบาทโครงการที่กำหนดเอง', + 'Add a new custom role' => 'เพิ่มบทบาทที่กำหนดเองใหม่', + 'Restrictions for the role "%s"' => 'ข้อจำกัดสำหรับบทบาท "%s"', + 'Add a new project restriction' => 'เพิ่มข้อจำกัดโครงการใหม่', + 'Add a new drag and drop restriction' => 'เพิ่มข้อจำกัดการลากและวางใหม่', + 'Add a new column restriction' => 'เพิ่มข้อจำกัดคอลัมน์ใหม่', + 'Edit this role' => 'แก้ไขบทบาทนี้', + 'Remove this role' => 'ลบบทบาทนี้', + 'There is no restriction for this role.' => 'ไม่มีข้อจำกัดสำหรับบทบาทนี้', + 'Only moving task between those columns is permitted' => 'อนุญาตให้ย้ายงานระหว่างคอลัมน์เหล่านี้เท่านั้น', + 'Close a task in a specific column when not moved during a given period' => 'ปิดงานในคอลัมน์ที่ระบุเมื่อไม่มีการย้ายในช่วงเวลาที่กำหนด', + 'Edit columns' => 'แก้ไขคอลัมน์', + 'The column restriction has been created successfully.' => 'สร้างข้อจำกัดคอลัมน์สำเร็จแล้ว', + 'Unable to create this column restriction.' => 'ไม่สามารถสร้างข้อจำกัดคอลัมน์นี้ได้', + 'Column restriction removed successfully.' => 'ลบข้อจำกัดคอลัมน์สำเร็จแล้ว', + 'Unable to remove this restriction.' => 'ไม่สามารถลบข้อจำกัดนี้ได้', + 'Your custom project role has been created successfully.' => 'สร้างบทบาทโครงการที่กำหนดเองของคุณสำเร็จแล้ว', + 'Unable to create custom project role.' => 'ไม่สามารถสร้างบทบาทโครงการที่กำหนดเองได้', + 'Your custom project role has been updated successfully.' => 'อัปเดตบทบาทโครงการที่กำหนดเองของคุณสำเร็จแล้ว', + 'Unable to update custom project role.' => 'ไม่สามารถอัปเดตบทบาทโครงการที่กำหนดเองได้', + 'Custom project role removed successfully.' => 'ลบบทบาทโครงการที่กำหนดเองสำเร็จแล้ว', + 'Unable to remove this project role.' => 'ไม่สามารถลบบทบาทโครงการนี้ได้', + 'The project restriction has been created successfully.' => 'สร้างข้อจำกัดโครงการสำเร็จแล้ว', + 'Unable to create this project restriction.' => 'ไม่สามารถสร้างข้อจำกัดโครงการนี้ได้', + 'Project restriction removed successfully.' => 'ลบข้อจำกัดโครงการสำเร็จแล้ว', + 'You cannot create tasks in this column.' => 'คุณไม่สามารถสร้างงานในคอลัมน์นี้ได้', + 'Task creation is permitted for this column' => 'อนุญาตให้สร้างงานสำหรับคอลัมน์นี้', + 'Closing or opening a task is permitted for this column' => 'อนุญาตให้ปิดหรือเปิดงานสำหรับคอลัมน์นี้', + 'Task creation is blocked for this column' => 'บล็อกการสร้างงานสำหรับคอลัมน์นี้', + 'Closing or opening a task is blocked for this column' => 'บล็อกการปิดหรือเปิดงานสำหรับคอลัมน์นี้', + 'Task creation is not permitted' => 'ไม่อนุญาตให้สร้างงาน', + 'Closing or opening a task is not permitted' => 'ไม่อนุญาตให้ปิดหรือเปิดงาน', + 'New drag and drop restriction for the role "%s"' => 'ข้อจำกัดการลากและวางใหม่สำหรับบทบาท "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'บุคคลที่อยู่ในบทบาทนี้จะสามารถย้ายงานได้เฉพาะระหว่างคอลัมน์ต้นทางและคอลัมน์ปลายทางเท่านั้น', + 'Remove a column restriction' => 'ลบข้อจำกัดคอลัมน์', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'คุณต้องการลบข้อจำกัดคอลัมน์นี้จริง ๆ หรือไม่: "%s" ถึง "%s"?', + 'New column restriction for the role "%s"' => 'ข้อจำกัดคอลัมน์ใหม่สำหรับบทบาท "%s"', + 'Rule' => 'กฎ', + 'Do you really want to remove this column restriction?' => 'คุณต้องการลบข้อจำกัดคอลัมน์นี้จริง ๆ หรือไม่?', + 'Custom roles' => 'บทบาทที่กำหนดเอง', + 'New custom project role' => 'บทบาทโครงการที่กำหนดเองใหม่', + 'Edit custom project role' => 'แก้ไขบทบาทโครงการที่กำหนดเอง', + 'Remove a custom role' => 'ลบบทบาทที่กำหนดเอง', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'คุณต้องการลบบทบาทที่กำหนดเองนี้จริง ๆ หรือไม่: "%s"? บุคคลทั้งหมดที่ได้รับมอบหมายบทบาทนี้จะกลายเป็นสมาชิกโครงการ', + 'There is no custom role for this project.' => 'ไม่มีบทบาทที่กำหนดเองสำหรับโครงการนี้', + 'New project restriction for the role "%s"' => 'ข้อจำกัดโครงการใหม่สำหรับบทบาท "%s"', + 'Restriction' => 'ข้อจำกัด', + 'Remove a project restriction' => 'ลบข้อจำกัดโครงการ', + 'Do you really want to remove this project restriction: "%s"?' => 'คุณต้องการลบข้อจำกัดโครงการนี้จริง ๆ หรือไม่: "%s"?', + 'Duplicate to multiple projects' => 'ทำซ้ำไปยังหลายโครงการ', + 'This field is required' => 'ฟิลด์นี้จำเป็น', + 'Moving a task is not permitted' => 'ไม่อนุญาตให้ย้ายงาน', + 'This value must be in the range %d to %d' => 'ค่านี้นต้องอยู่ในช่วง %d ถึง %d', + 'You are not allowed to move this task.' => 'คุณไม่ได้รับอนุญาตให้ย้ายงานนี้', + 'API User Access' => 'การเข้าถึง API ของผู้ใช้', + 'Preview' => 'ตัวอย่าง', + 'Write' => 'เขียน', + 'Write your text in Markdown' => 'เขียนข้อความของคุณในรูปแบบ Markdown', + 'No personal API access token registered.' => 'ไม่มีโทเค็นการเข้าถึง API ส่วนบุคคลที่ลงทะเบียนไว้', + 'Your personal API access token is "%s"' => 'โทเค็นการเข้าถึง API ส่วนบุคคลของคุณคือ "%s"', + 'Remove your token' => 'ลบโทเค็นของคุณ', + 'Generate a new token' => 'สร้างโทเค็นใหม่', + 'Showing %d-%d of %d' => 'กำลังแสดง %d-%d จาก %d', + 'Outgoing Emails' => 'อีเมลขาออก', + 'Add or change currency rate' => 'เพิ่มหรือเปลี่ยนอัตราแลกเปลี่ยน', + 'Reference currency: %s' => 'สกุลเงินอ้างอิง: %s', + 'Add custom filters' => 'เพิ่มตัวกรองที่กำหนดเอง', + 'Export' => 'ส่งออก', + 'Add link label' => 'เพิ่มป้ายกำกับลิงก์', + 'Incompatible Plugins' => 'ปลั๊กอินที่ไม่เข้ากัน', + 'Compatibility' => 'ความเข้ากันได้', + 'Permissions and ownership' => 'สิทธิ์และความเป็นเจ้าของ', + 'Priorities' => 'ลำดับความสำคัญ', + 'Close this window' => 'ปิดหน้าต่างนี้', + 'Unable to upload this file.' => 'ไม่สามารถอัปโหลดไฟล์นี้ได้', + 'Import tasks' => 'นำเข้างาน', + 'Choose a project' => 'เลือกโครงการ', + 'Profile' => 'โปรไฟล์', + 'Application role' => 'บทบาทแอปพลิเคชัน', + '%d invitations were sent.' => 'ส่งคำเชิญ %d ฉบับแล้ว', + '%d invitation was sent.' => 'ส่งคำเชิญ %d ฉบับแล้ว', + 'Unable to create this user.' => 'ไม่สามารถสร้างผู้ใช้นี้ได้', + 'Kanboard Invitation' => 'คำเชิญ Kanboard', + 'Visible on dashboard' => 'มองเห็นได้บนแดชบอร์ด', + 'Created at:' => 'สร้างเมื่อ:', + 'Updated at:' => 'อัปเดตเมื่อ:', + 'There is no custom filter.' => 'ไม่มีตัวกรองที่กำหนดเอง', + 'New User' => 'ผู้ใช้ใหม่', + 'Authentication' => 'การรับรองความถูกต้อง', + 'If checked, this user will use a third-party system for authentication.' => 'หากทำเครื่องหมาย ผู้ใช้นี้จะใช้ระบบของบุคคลที่สามในการรับรองความถูกต้อง', + 'The password is necessary only for local users.' => 'รหัสผ่านจำเป็นสำหรับผู้ใช้ภายในเท่านั้น', + 'You have been invited to register on Kanboard.' => 'คุณได้รับเชิญให้ลงทะเบียนบน Kanboard', + 'Click here to join your team' => 'คลิกที่นี่เพื่อเข้าร่วมทีมของคุณ', + 'Invite people' => 'เชิญผู้คน', + 'Emails' => 'อีเมล', + 'Enter one email address by line.' => 'ป้อนที่อยู่อีเมลหนึ่งบรรทัดต่อหนึ่งรายการ', + 'Add these people to this project' => 'เพิ่มบุคคลเหล่านี้ในโครงการนี้', + 'Add this person to this project' => 'เพิ่มบุคคลนี้ในโครงการนี้', + 'Sign-up' => 'ลงทะเบียน', + 'Credentials' => 'ข้อมูลรับรอง', + 'New user' => 'ผู้ใช้ใหม่', + 'This username is already taken' => 'ชื่อผู้ใช้นี้ถูกใช้ไปแล้ว', + 'Your profile must have a valid email address.' => 'โปรไฟล์ของคุณต้องมีที่อยู่อีเมลที่ถูกต้อง', + 'TRL - Turkish Lira' => 'TRL - ลีราตุรกี', + 'The project email is optional and could be used by several plugins.' => 'อีเมลโครงการเป็นตัวเลือกและสามารถใช้โดยปลั๊กอินหลายตัว', + 'The project email must be unique across all projects' => 'อีเมลโครงการต้องไม่ซ้ำกันในทุกโครงการ', + 'The email configuration has been disabled by the administrator.' => 'ผู้ดูแลระบบได้ปิดใช้งานการกำหนดค่าอีเมลแล้ว', + 'Close this project' => 'ปิดโครงการนี้', + 'Open this project' => 'เปิดโครงการนี้', + 'Close a project' => 'ปิดโครงการ', + 'Do you really want to close this project: "%s"?' => 'คุณต้องการปิดโครงการนี้จริง ๆ หรือไม่: "%s"?', + 'Reopen a project' => 'เปิดโครงการอีกครั้ง', + 'Do you really want to reopen this project: "%s"?' => 'คุณต้องการเปิดโครงการนี้อีกครั้งจริง ๆ หรือไม่: "%s"?', + 'This project is open' => 'โครงการนี้เปิดอยู่', + 'This project is closed' => 'โครงการนี้ปิดอยู่', + 'Unable to upload files, check the permissions of your data folder.' => 'ไม่สามารถอัปโหลดไฟล์ได้ ตรวจสอบสิทธิ์ของโฟลเดอร์ข้อมูลของคุณ', + 'Another category with the same name exists in this project' => 'มีหมวดหมู่อื่นที่มีชื่อเดียวกันอยู่ในโครงการนี้', + 'Comment sent by email successfully.' => 'ส่งความคิดเห็นทางอีเมลสำเร็จแล้ว', + 'Sent by email to "%s" (%s)' => 'ส่งทางอีเมลถึง "%s" (%s)', + 'Unable to read uploaded file.' => 'ไม่สามารถอ่านไฟล์ที่อัปโหลดได้', + 'Database uploaded successfully.' => 'อัปโหลดฐานข้อมูลสำเร็จแล้ว', + 'Task sent by email successfully.' => 'ส่งงานทางอีเมลสำเร็จแล้ว', + 'There is no category in this project.' => 'ไม่มีหมวดหมู่ในโครงการนี้', + 'Send by email' => 'ส่งทางอีเมล', + 'Create and send a comment by email' => 'สร้างและส่งความคิดเห็นทางอีเมล', + 'Subject' => 'หัวข้อ', + 'Upload the database' => 'อัปโหลดฐานข้อมูล', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'คุณสามารถอัปโหลดฐานข้อมูล Sqlite ที่ดาวน์โหลดก่อนหน้านี้ (รูปแบบ Gzip) ได้', + 'Database file' => 'ไฟล์ฐานข้อมูล', + 'Upload' => 'อัปโหลด', + 'Your project must have at least one active swimlane.' => 'โครงการของคุณต้องมีอย่างน้อยหนึ่งเลนที่ใช้งานอยู่', + 'Project: %s' => 'โครงการ: %s', + 'Automatic action not found: "%s"' => 'ไม่พบการดำเนินการอัตโนมัติ: "%s"', + '%d projects' => '%d โครงการ', + '%d project' => '%d โครงการ', + 'There is no project.' => 'ไม่มีโครงการ', + 'Sort' => 'เรียงลำดับ', + 'Project ID' => 'รหัสโครงการ', + 'Project name' => 'ชื่อโครงการ', + 'Public' => 'สาธารณะ', + 'Personal' => 'ส่วนตัว', + '%d tasks' => '%d งาน', + '%d task' => '%d งาน', + 'Task ID' => 'รหัสงาน', + 'Assign automatically a color when due date is expired' => 'กำหนดสีโดยอัตโนมัติเมื่อครบกำหนด', + 'Total score in this column across all swimlanes' => 'คะแนนรวมในคอลัมน์นี้ในทุกเลน', + 'HRK - Kuna' => 'HRK - คูนาโครเอเชีย', + 'ARS - Argentine Peso' => 'ARS - เปโซอาร์เจนตินา', + 'COP - Colombian Peso' => 'COP - เปโซโคลอมเบีย', + '%d groups' => '%d กลุ่ม', + '%d group' => '%d กลุ่ม', + 'Group ID' => 'รหัสกลุ่ม', + 'External ID' => 'รหัสภายนอก', + '%d users' => '%d ผู้ใช้', + '%d user' => '%d ผู้ใช้', + 'Hide subtasks' => 'ซ่อนงานย่อย', + 'Show subtasks' => 'แสดงงานย่อย', + 'Authentication Parameters' => 'พารามิเตอร์การรับรองความถูกต้อง', + 'API Access' => 'การเข้าถึง API', + 'No users found.' => 'ไม่พบผู้ใช้', + 'User ID' => 'รหัสผู้ใช้', + 'Notifications are activated' => 'เปิดใช้งานการแจ้งเตือนแล้ว', + 'Notifications are disabled' => 'ปิดใช้งานการแจ้งเตือนแล้ว', + 'User disabled' => 'ปิดใช้งานผู้ใช้แล้ว', + '%d notifications' => '%d การแจ้งเตือน', + '%d notification' => '%d การแจ้งเตือน', + 'There is no external integration installed.' => 'ไม่ได้ติดตั้งการรวมระบบภายนอก', + 'You are not allowed to update tasks assigned to someone else.' => 'คุณไม่ได้รับอนุญาตให้อัปเดตงานที่มอบหมายให้ผู้อื่น', + 'You are not allowed to change the assignee.' => 'คุณไม่ได้รับอนุญาตให้เปลี่ยนผู้รับมอบหมาย', + 'Task suppression is not permitted' => 'ไม่อนุญาตให้ระงับงาน', + 'Changing assignee is not permitted' => 'ไม่อนุญาตให้เปลี่ยนผู้รับมอบหมาย', + 'Update only assigned tasks is permitted' => 'อนุญาตให้อัปเดตเฉพาะงานที่ได้รับมอบหมาย', + 'Only for tasks assigned to the current user' => 'สำหรับงานที่มอบหมายให้ผู้ใช้ปัจจุบันเท่านั้น', + 'My projects' => 'โครงการของฉัน', + 'You are not a member of any project.' => 'คุณไม่ใช่สมาชิกของโครงการใดๆ', + 'My subtasks' => 'งานย่อยของฉัน', + '%d subtasks' => '%d งานย่อย', + '%d subtask' => '%d งานย่อย', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'อนุญาตให้ย้ายงานระหว่างคอลัมน์เหล่านั้นเท่านั้นสำหรับงานที่มอบหมายให้ผู้ใช้ปัจจุบัน', + '[DUPLICATE]' => '[ทำซ้ำ]', + 'DKK - Danish Krona' => 'DKK - โครนเดนมาร์ก', + 'Remove user from group' => 'ลบผู้ใช้ออกจากกลุ่ม', + 'Assign the task to its creator' => 'มอบหมายงานให้ผู้สร้าง', + 'This task was sent by email to "%s" with subject "%s".' => 'งานนี้ถูกส่งทางอีเมลถึง "%s" พร้อมหัวข้อ "%s"', + 'Predefined Email Subjects' => 'หัวข้ออีเมลที่กำหนดไว้ล่วงหน้า', + 'Write one subject by line.' => 'เขียนหัวข้อหนึ่งบรรทัดต่อหนึ่งรายการ', + 'Create another link' => 'สร้างลิงก์อื่น', + 'BRL - Brazilian Real' => 'BRL - เรียลบราซิล', + 'Add a new Kanboard task' => 'เพิ่มงาน Kanboard ใหม่', + 'Subtask not started' => 'งานย่อยยังไม่เริ่ม', + 'Subtask currently in progress' => 'งานย่อยกำลังดำเนินการอยู่', + 'Subtask completed' => 'งานย่อยเสร็จสมบูรณ์แล้ว', + 'Subtask added successfully.' => 'เพิ่มงานย่อยสำเร็จแล้ว', + '%d subtasks added successfully.' => 'เพิ่มงานย่อย %d รายการสำเร็จแล้ว', + 'Enter one subtask by line.' => 'ป้อนงานย่อยหนึ่งบรรทัดต่อหนึ่งรายการ', + 'Predefined Contents' => 'เนื้อหาที่กำหนดไว้ล่วงหน้า', + 'Predefined contents' => 'เนื้อหาที่กำหนดไว้ล่วงหน้า', + 'Predefined Task Description' => 'คำอธิบายงานที่กำหนดไว้ล่วงหน้า', + 'Do you really want to remove this template? "%s"' => 'คุณต้องการลบแม่แบบนี้จริง ๆ หรือไม่: "%s"?', + 'Add predefined task description' => 'เพิ่มคำอธิบายงานที่กำหนดไว้ล่วงหน้า', + 'Predefined Task Descriptions' => 'คำอธิบายงานที่กำหนดไว้ล่วงหน้า', + 'Template created successfully.' => 'สร้างแม่แบบสำเร็จแล้ว', + 'Unable to create this template.' => 'ไม่สามารถสร้างแม่แบบนี้ได้', + 'Template updated successfully.' => 'อัปเดตแม่แบบสำเร็จแล้ว', + 'Unable to update this template.' => 'ไม่สามารถอัปเดตแม่แบบนี้ได้', + 'Template removed successfully.' => 'ลบแม่แบบสำเร็จแล้ว', + 'Unable to remove this template.' => 'ไม่สามารถลบแม่แบบนี้ได้', + 'Template for the task description' => 'แม่แบบสำหรับคำอธิบายงาน', + 'The start date is greater than the end date' => 'วันที่เริ่มต้นมากกว่าวันที่สิ้นสุด', + 'Tags must be separated by a comma' => 'ต้องใช้เครื่องหมายจุลภาคคั่นแท็ก', + 'Only the task title is required' => 'จำเป็นต้องมีเพียงชื่อเรื่องงานเท่านั้น', + 'Creator Username' => 'ชื่อผู้ใช้ผู้สร้าง', + 'Color Name' => 'ชื่อสี', + 'Column Name' => 'ชื่อคอลัมน์', + 'Swimlane Name' => 'ชื่อเลน', + 'Time Estimated' => 'เวลาที่ประมาณ', + 'Time Spent' => 'เวลาที่ใช้ไป', + 'External Link' => 'ลิงก์ภายนอก', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'คุณลักษณะนี้เปิดใช้งานฟีด iCal, ฟีด RSS และมุมมองบอร์ดสาธารณะ', + 'Stop the timer of all subtasks when moving a task to another column' => 'หยุดตัวจับเวลาของงานย่อยทั้งหมดเมื่อย้ายงานไปยังคอลัมน์อื่น', + 'Subtask Title' => 'ชื่อเรื่องงานย่อย', + 'Add a subtask and activate the timer when moving a task to another column' => 'เพิ่มงานย่อยและเปิดใช้งานตัวจับเวลาเมื่อย้ายงานไปยังคอลัมน์อื่น', + 'days' => 'วัน', + 'minutes' => 'นาที', + 'seconds' => 'วินาที', + 'Assign automatically a color when preset start date is reached' => 'กำหนดสีโดยอัตโนมัติเมื่อถึงวันที่เริ่มต้นที่ตั้งไว้ล่วงหน้า', + 'Move the task to another column once a predefined start date is reached' => 'ย้ายงานไปยังคอลัมน์อื่นเมื่อถึงวันที่เริ่มต้นที่กำหนดไว้ล่วงหน้า', + 'This task is now linked to the task %s with the relation "%s"' => 'งานนี้เชื่อมโยงกับงาน %s โดยใช้ความสัมพันธ์ "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'ลบลิงก์ที่มีความสัมพันธ์ "%s" ไปยังงาน %s แล้ว', + 'Custom Filter:' => 'ตัวกรองที่กำหนดเอง:', + 'Unable to find this group.' => 'ไม่สามารถหากลุ่มนี้ได้', + '%s moved the task #%d to the column "%s"' => '%s ย้ายงาน #%d ไปยังคอลัมน์ "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => '%s ย้ายงาน #%d ไปยังตำแหน่งที่ %d ในคอลัมน์ "%s"', + '%s moved the task #%d to the swimlane "%s"' => '%s ย้ายงาน #%d ไปยังเลน "%s"', + '%sh spent' => 'ใช้ไป %sh', + '%sh estimated' => 'ประมาณ %sh', + 'Select All' => 'เลือกทั้งหมด', + 'Unselect All' => 'ยกเลิกการเลือกทั้งหมด', + 'Apply action' => 'ใช้การดำเนินการ', + 'Move selected tasks to another column or swimlane' => 'ย้ายงานที่เลือกไปยังคอลัมน์หรือเลนอื่น', + 'Edit tasks in bulk' => 'แก้ไขงานจำนวนมาก', + 'Choose the properties that you would like to change for the selected tasks.' => 'เลือกคุณสมบัติที่คุณต้องการเปลี่ยนสำหรับงานที่เลือก', + 'Configure this project' => 'กำหนดค่าโครงการนี้', + 'Start now' => 'เริ่มตอนนี้', + '%s removed a file from the task #%d' => '%s ลบไฟล์ออกจากงาน #%d', + 'Attachment removed from task #%d: %s' => 'ลบไฟล์แนบจากงาน #%d: %s', + 'No color' => 'ไม่มีสี', + 'Attachment removed "%s"' => 'ลบไฟล์แนบ "%s" แล้ว', + '%s removed a file from the task %s' => '%s ลบไฟล์ออกจากงาน %s', + 'Move the task to another swimlane when assigned to a user' => 'ย้ายงานไปยังเลนอื่นเมื่อมอบหมายให้ผู้ใช้', + 'Destination swimlane' => 'เลนปลายทาง', + 'Assign a category when the task is moved to a specific swimlane' => 'กำหนดหมวดหมู่เมื่อย้ายงานไปยังเลนเฉพาะ', + 'Move the task to another swimlane when the category is changed' => 'ย้ายงานไปยังเลนอื่นเมื่อมีการเปลี่ยนหมวดหมู่', + 'Reorder this column by priority (ASC)' => 'จัดเรียงคอลัมน์นี้ใหม่ตามลำดับความสำคัญ (จากน้อยไปมาก)', + 'Reorder this column by priority (DESC)' => 'จัดเรียงคอลัมน์นี้ใหม่ตามลำดับความสำคัญ (จากมากไปน้อย)', + 'Reorder this column by assignee and priority (ASC)' => 'จัดเรียงคอลัมน์นี้ใหม่ตามผู้รับมอบหมายและลำดับความสำคัญ (จากน้อยไปมาก)', + 'Reorder this column by assignee and priority (DESC)' => 'จัดเรียงคอลัมน์นี้ใหม่ตามผู้รับมอบหมายและลำดับความสำคัญ (จากมากไปน้อย)', + 'Reorder this column by assignee (A-Z)' => 'จัดเรียงคอลัมน์นี้ใหม่ตามผู้รับมอบหมาย (A-Z)', + 'Reorder this column by assignee (Z-A)' => 'จัดเรียงคอลัมน์นี้ใหม่ตามผู้รับมอบหมาย (Z-A)', + 'Reorder this column by due date (ASC)' => 'จัดเรียงคอลัมน์นี้ใหม่ตามวันครบกำหนด (จากน้อยไปมาก)', + 'Reorder this column by due date (DESC)' => 'จัดเรียงคอลัมน์นี้ใหม่ตามวันครบกำหนด (จากมากไปน้อย)', + 'Reorder this column by id (ASC)' => 'จัดเรียงคอลัมน์นี้ใหม่ตาม ID (จากน้อยไปมาก)', + 'Reorder this column by id (DESC)' => 'จัดเรียงคอลัมน์นี้ใหม่ตาม ID (จากมากไปน้อย)', + '%s moved the task #%d "%s" to the project "%s"' => '%s ย้ายงาน #%d "%s" ไปยังโครงการ "%s"', + 'Task #%d "%s" has been moved to the project "%s"' => 'งาน #%d "%s" ถูกย้ายไปยังโครงการ "%s"', + 'Move the task to another column when the due date is less than a certain number of days' => 'ย้ายงานไปยังคอลัมน์อื่นเมื่อวันครบกำหนดน้อยกว่าจำนวนวันที่กำหนด', + 'Automatically update the start date when the task is moved away from a specific column' => 'อัปเดตวันที่เริ่มต้นโดยอัตโนมัติเมื่อย้ายงานออกจากคอลัมน์ที่ระบุ', + 'HTTP Client:' => 'ไคลเอนต์ HTTP:', + 'Assigned' => 'ได้รับมอบหมาย', + 'Task limits apply to each swimlane individually' => 'ขีดจำกัดงานจะใช้กับแต่ละเลนแยกกัน', + 'Column task limits apply to each swimlane individually' => 'ขีดจำกัดงานคอลัมน์จะใช้กับแต่ละเลนแยกกัน', + 'Column task limits are applied to each swimlane individually' => 'ขีดจำกัดงานคอลัมน์จะใช้กับแต่ละเลนแยกกัน', + 'Column task limits are applied across swimlanes' => 'ขีดจำกัดงานคอลัมน์จะใช้กับทุกเลน', + 'Task limit: ' => 'ขีดจำกัดงาน:', + 'Change to global tag' => 'เปลี่ยนเป็นแท็กสากล', + 'Do you really want to make the tag "%s" global?' => 'คุณต้องการทำให้แท็ก "%s" เป็นสากลจริง ๆ หรือไม่?', + 'Enable global tags for this project' => 'เปิดใช้งานแท็กสากลสำหรับโครงการนี้', + 'Group membership(s):' => 'สมาชิกกลุ่ม:', + '%s is a member of the following group(s): %s' => '%s เป็นสมาชิกของกลุ่มต่อไปนี้: %s', + '%d/%d group(s) shown' => 'แสดงกลุ่ม %d/%d', + 'Subtask creation or modification' => 'การสร้างหรือแก้ไขงานย่อย', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'มอบหมายงานให้ผู้ใช้ที่ระบุเมื่อย้ายงานไปยังเลนที่ระบุ', + 'Comment' => 'ความคิดเห็น', + 'Collapse vertically' => 'ยุบแนวตั้ง', + 'Expand vertically' => 'ขยายแนวตั้ง', + 'MXN - Mexican Peso' => 'MXN - เปโซเม็กซิโก', + 'Estimated vs actual time per column' => 'เวลาที่ประมาณ vs เวลาจริงต่อคอลัมน์', + 'HUF - Hungarian Forint' => 'HUF - โฟรินต์ฮังการี', + 'XBT - Bitcoin' => 'XBT - บิตคอยน์', + 'You must select a file to upload as your avatar!' => 'คุณต้องเลือกไฟล์เพื่ออัปโหลดเป็นอวตารของคุณ!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'ไฟล์ที่คุณอัปโหลดไม่ใช่รูปภาพที่ถูกต้อง! (อนุญาตเฉพาะ *.gif, *.jpg, *.jpeg และ *.png เท่านั้น!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'ตั้งค่าวันครบกำหนดโดยอัตโนมัติเมื่อย้ายงานออกจากคอลัมน์ที่ระบุ', + 'No other projects found.' => 'ไม่พบโครงการอื่น', + 'Tasks copied successfully.' => 'คัดลอกงานสำเร็จแล้ว', + 'Unable to copy tasks.' => 'ไม่สามารถคัดลอกงานได้', + 'Theme' => 'ธีม', + 'Theme:' => 'ธีม:', + 'Light theme' => 'ธีมสว่าง', + 'Dark theme' => 'ธีมมืด', + 'Automatic theme - Sync with system' => 'ธีมอัตโนมัติ - ซิงค์กับระบบ', + 'Application managers or more' => 'ผู้จัดการแอปพลิเคชันหรือมากกว่า', + 'Administrators' => 'ผู้ดูแลระบบ', + 'Visibility:' => 'การมองเห็น:', + 'Standard users' => 'ผู้ใช้มาตรฐาน', + 'Visibility is required' => 'จำเป็นต้องมีการมองเห็น', + 'The visibility should be an app role' => 'การมองเห็นควรเป็นบทบาทแอปพลิเคชัน', + 'Reply' => 'ตอบกลับ', + '%s wrote: ' => '%s เขียน:', + 'Number of visible tasks in this column and swimlane' => 'จำนวนงานที่มองเห็นได้ในคอลัมน์และเลนนี้', + 'Number of tasks in this swimlane' => 'จำนวนงานในเลนนี้', + 'Unable to find another subtask in progress, you can close this window.' => 'ไม่สามารถหางานย่อยอื่นที่กำลังดำเนินการอยู่ได้ คุณสามารถปิดหน้าต่างนี้ได้', + 'This theme is invalid' => 'ธีมนี้ไม่ถูกต้อง', + 'This role is invalid' => 'บทบาทนี้ไม่ถูกต้อง', + 'This timezone is invalid' => 'เขตเวลานี้ไม่ถูกต้อง', + 'This language is invalid' => 'ภาษานี้ไม่ถูกต้อง', + 'This URL is invalid' => 'URL นี้ไม่ถูกต้อง', + 'Date format invalid' => 'รูปแบบวันที่ไม่ถูกต้อง', + 'Time format invalid' => 'รูปแบบเวลาไม่ถูกต้อง', + 'Invalid Mail transport' => 'การขนส่งอีเมลไม่ถูกต้อง', + 'Color invalid' => 'สีไม่ถูกต้อง', + 'This value must be greater or equal to %d' => 'ค่านี้นต้องมากกว่าหรือเท่ากับ %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'เพิ่ม BOM ที่ต้นไฟล์ (จำเป็นสำหรับ Microsoft Excel)', + 'Just add these tag(s)' => 'เพียงเพิ่มแท็กเหล่านี้', + 'Remove internal link(s)' => 'ลบลิงก์ภายใน', + 'Import tasks from another project' => 'นำเข้างานจากโครงการอื่น', + 'Select the project to copy tasks from' => 'เลือกโครงการที่จะคัดลอกงานจาก', + 'The total maximum allowed attachments size is %sB.' => 'ขนาดไฟล์แนบสูงสุดที่อนุญาตทั้งหมดคือ %sB', + 'Add attachments' => 'เพิ่มไฟล์แนบ', + 'Task #%d "%s" is overdue' => 'งาน #%d "%s" เกินกำหนด', + 'Enable notifications by default for all new users' => 'เปิดใช้งานการแจ้งเตือนตามค่าเริ่มต้นสำหรับผู้ใช้ใหม่ทั้งหมด', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'กำหนดงานให้ผู้สร้างสำหรับคอลัมน์ที่ระบุ หากไม่มีผู้รับมอบหมายที่ตั้งค่าเอง', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'กำหนดงานให้ผู้ใช้ที่ล็อกอินเมื่อย้ายคอลัมน์ไปยังคอลัมน์ที่ระบุ หากยังไม่มีผู้ใช้ที่ถูกกำหนด', +]; diff --git a/app/Locale/tr_TR/translations.php b/app/Locale/tr_TR/translations.php new file mode 100644 index 0000000..c876638 --- /dev/null +++ b/app/Locale/tr_TR/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => '.', + 'number.thousands_separator' => ',', + 'None' => 'Hiçbiri', + 'Edit' => 'Düzenle', + 'Remove' => 'Sil', + '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', + 'Brown' => 'K.rengi', + 'Deep Orange' => 'Kavuniçi', + 'Dark Grey' => 'Koyu Gri', + 'Pink' => 'Pembe', + 'Teal' => 'Turkuaz', + 'Cyan' => 'Cam Göbeği', + 'Lime' => 'Limoni', + 'Light Green' => 'Açık Yeşil', + 'Amber' => 'Koyu Sarı', + '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"?', + 'All users' => 'Tüm kullanıcılar', + 'Username' => 'Kullanıcı adı', + 'Password' => 'Şifre', + 'Administrator' => 'Yönetici', + 'Sign in' => 'Giriş yap', + 'Users' => 'Kullanıcılar', + 'Forbidden' => 'Yasak', + 'Access Forbidden' => 'Erişim yasaklandı', + 'Edit user' => 'Kullanıcıyı düzenle', + 'Logout' => 'Çıkış yap', + 'Bad username or password' => 'Hatalı kullanıcı adı veya şifre', + 'Edit project' => 'Projeyi düzenle', + 'Name' => 'İsim', + 'Projects' => 'Projeler', + 'No project' => 'Proje yok', + 'Project' => 'Proje', + 'Status' => 'Durum', + 'Tasks' => 'Görevler', + 'Board' => 'Pano', + 'Actions' => 'İşlemler', + 'Inactive' => 'Aktif değil', + 'Active' => 'Aktif', + 'Unable to update this board.' => 'Bu pano güncellenemiyor.', + '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', + 'Edit the board for "%s"' => 'Panoyu "%s" için güncelle', + 'Add a new column' => 'Yeni sütun ekle', + 'Title' => 'Başlık', + 'Assigned to %s' => '%s kullanıcısına atanmış', + 'Remove a column' => '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"?', + 'Settings' => 'Ayarlar', + 'Application settings' => 'Uygulama ayarları', + 'Language' => 'Dil', + 'Webhook token:' => 'Web kancası anahtarı:', + 'API token:' => 'API anahtarı:', + '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ı)', + 'Close a task' => 'Bir görevi kapat', + 'Column' => 'Sütun', + 'Color' => 'Renk', + 'Assignee' => 'Atanan', + 'Create another task' => 'Başka bir görev oluştur', + 'New task' => 'Yeni görev', + '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' => 'Panoya dön', + 'There is nobody assigned' => 'Kimse atanmamış', + 'Column on the board:' => 'Panodaki sütun:', + '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 user id is required' => 'Kullanıcı kodu gerekli', + 'Passwords don\'t match' => 'Şifreler uyuşmuyor', + 'The confirmation is required' => 'Onay gerekli', + 'The project is required' => 'Proje gerekli', + 'The id is required' => 'Kod gerekli', + 'The project id is required' => 'Proje kodu gerekli', + 'The project name is required' => 'Proje adı gerekli', + 'The title is required' => 'Başlık gerekli', + '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 has 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 aktif edildi.', + 'Unable to activate this project.' => 'Bu proje aktif edilemedi.', + 'Project disabled successfully.' => 'Proje başarıyla 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şturulamadı.', + 'User updated successfully.' => 'Kullanıcı başarıyla güncellendi.', + 'User removed successfully.' => 'Kullanıcı başarıyla silindi.', + 'Unable to remove this user.' => 'Bu kullanıcı silinemiyor.', + 'Board updated successfully.' => 'Pano başarıyla güncellendi.', + 'Ready' => 'Hazır', + 'Backlog' => 'Bekleme listesi', + 'Work in progress' => 'İşlemde', + 'Done' => 'Tamamlandı', + 'Application version:' => 'Uygulama versiyonu:', + 'Id' => 'Kod', + 'Public link' => 'Dışa açık link', + 'Timezone' => 'Saat dilimi', + 'Sorry, I didn\'t find this information in my database!' => 'Üzgünüm, bu bilgiyi veri tabanımda bulamadım.', + 'Page not found' => 'Sayfa bulunamadı', + 'Complexity' => 'Zorluk seviyesi', + 'Task limit' => 'Görev limiti', + 'Task count' => 'Görev sayısı', + 'User' => 'Kullanıcı', + 'Comments' => 'Yorumlar', + 'Comment is required' => 'Yorum gerekli', + 'Comment added successfully.' => 'Yorum eklendi', + 'Unable to create your comment.' => 'Yorumunuz oluşturulamadı', + 'Due Date' => 'Bitiş Tarihi', + 'Invalid date' => 'Geçersiz tarihi', + 'Automatic actions' => 'Otomatik işlemler', + 'Your automatic action has 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', + 'Add an action' => 'İşlem ekle', + 'Event name' => 'Durum adı', + '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', + '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', + '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şı', + 'Task modification' => 'Görev düzenleme', + 'Task creation' => 'Görev oluşturma', + 'Closing a task' => 'Bir görev kapatılıyor', + 'Assign a color to a specific user' => 'Bir kullanıcıya renk tanımla', + 'Position' => 'Pozisyon', + 'Duplicate to project' => 'Başka bir projeye kopyala', + 'Duplicate' => 'Kopya oluştur', + 'Link' => 'Bağlantı', + '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?', + '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', + '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ı Arayüzü', + 'Persistent connections' => 'Kalıcı bağlantılar', + 'No session.' => 'Oturum yok.', + 'Expiration date' => 'Geçerlilik sonu', + 'Remember Me' => 'Beni hatırla', + 'Creation date' => 'Oluşturulma tarihi', + 'Everybody' => 'Herkes', + 'Open' => 'Açık', + 'Closed' => 'Kapalı', + 'Search' => 'Ara', + 'Nothing found.' => 'Hiçbir şey bulunamadı.', + 'Due date' => 'Bitiş tarihi', + 'Description' => 'Açıklama', + '%d comments' => '%d yorum', + '%d comment' => '%d yorum', + 'Email address invalid' => 'E-posta adresi geçersiz', + 'Your external account is not linked anymore to your profile.' => 'Harici hesabınız artık profilinize bağlı değil', + 'Unable to unlink your external account.' => 'Harici hesabınızla bağlantı koparılamadı', + 'External authentication failed' => 'Harici hesap doğrulaması başarısız', + 'Your external account is linked to your profile successfully.' => 'Harici hesabınız profilinizle başarıyla bağlandı.', + 'Email' => 'E-posta', + 'Task removed successfully.' => 'Görev başarıyla 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 düzenleme', + 'Category' => 'Kategori', + 'Category:' => 'Kategori:', + 'Categories' => 'Kategoriler', + 'Your category has been created successfully.' => 'Kategori başarıyla oluşturuldu.', + 'This category has been updated successfully.' => 'Kategori başarıyla güncellendi.', + 'Unable to update this category.' => 'Kategori güncellenemedi.', + 'Remove a category' => 'Bir kategoriyi sil', + 'Category removed successfully.' => 'Kategori başarıyla silindi.', + 'Unable to remove this category.' => 'Bu kategori silinemedi.', + 'Category modification for the project "%s"' => '"%s" projesi için kategori düzenleme', + 'Category Name' => 'Kategori adı', + 'Add a new category' => 'Yeni kategori ekle', + 'Do you really want to remove this category: "%s"?' => 'Bu kategoriyi silmek istediğinize emin misiniz: "%s"?', + '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"?', + 'Attachments' => 'Ekler', + 'Edit the task' => 'Görevi değiştir', + 'Add a comment' => 'Yorum ekle', + 'Edit a comment' => 'Yorum değiştir', + 'Summary' => 'Özet', + 'Time tracking' => 'Süre Çizelgesi', + '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', + 'estimated' => 'tahmini', + 'Sub-Tasks' => 'Alt Görev', + 'Add a sub-task' => 'Alt görev ekle', + 'Original estimate' => 'Orjinal tahmin', + 'Create another sub-task' => 'Başka bir alt görev daha oluştur', + 'Time spent' => 'Geçen süre', + '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 alfanümerik bir değer olmalı', + 'Todo' => 'Yapılacaklar', + 'In progress' => 'Devam etmekte', + 'Sub-task removed successfully.' => 'Alt görev başarıyla 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ı.', + 'Maximum size: ' => 'Maksimum boyutu', + 'Display another project' => 'Başka bir proje göster', + 'Created by %s' => '%s tarafından oluşturuldu', + 'Tasks Export' => 'Görevleri dışa aktar', + 'Start Date' => 'Başlangıç tarihi', + 'Execute' => 'Gerçekleştir', + 'Task Id' => 'Görev Kimliği', + 'Creator' => 'Oluşturan', + 'Modification date' => 'Değişiklik tarihi', + 'Completion date' => 'Tamamlanma tarihi', + 'Clone' => 'Kopya oluştur', + 'Project cloned successfully.' => 'Proje kopyası başarıyla oluşturuldu.', + 'Unable to clone this project.' => 'Proje kopyası oluşturulamıyor.', + 'Enable email notifications' => 'E-posta bilgilendirmesini aç', + 'Task position:' => 'Görev pozisyonu', + 'The task #%d has been opened.' => '#%d numaralı görev açıldı.', + 'The task #%d has 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.', + 'New comment posted by %s' => '%s tarafından yeni yorum eklendi', + 'New comment' => 'Yeni yorum', + 'Comment updated' => 'Yorum güncellendi', + 'New subtask' => 'Yeni alt görev', + 'I only want to receive notifications for these projects:' => 'Yalnızca bu projelerle ilgili bildirim almak istiyorum:', + 'view the task on Kanboard' => 'bu görevi Kanboard üzerinde göster', + 'Public access' => 'Dışa açık erişim', + 'Disable public access' => 'Dışa açık erişimi kapat', + 'Enable public access' => 'Dışa açık erişimi aç', + 'Public access disabled' => 'Dışa açık erişim kapatıldı', + 'Move the task to another project' => 'Görevi başka projeye taşı', + 'Move to 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ı', + 'Login:' => 'Kullanıcı adı', + 'Full Name:' => 'Ad', + 'Email:' => 'E-posta', + 'Notifications:' => 'Bildirimler:', + 'Notifications' => 'Bildirimler', + '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ı', + 'Never connected.' => 'Hiç bağlanmamış.', + '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ştirilemiyor.', + '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', + '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 opened the task #%d' => '%s kullanıcısı #%d nolu görevi açtı', + '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 changed 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', + 'New password for the user "%s"' => '"%s" kullanıcısı için yeni şifre', + 'Choose an event' => 'Bir durum seçin', + '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', + 'Label' => 'Etiket', + 'Database' => 'Veritabanı', + 'About' => 'Hakkında', + 'Database driver:' => 'Veritabanı sürücüsü:', + 'Board settings' => 'Pano ayarları', + 'Webhook settings' => 'Webhook ayarları', + 'Reset token' => 'Anahtarı sıfırla', + 'API endpoint:' => 'API bitiş noktası:', + 'Refresh interval for personal board' => 'Özel panolar için yenileme sıklığı', + 'Refresh interval for public board' => 'Dışa açık panolar 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', + 'Token regenerated.' => 'Anahtar 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 personal project' => 'Yeni özel proje', + 'This project is personal' => 'Bu proje özel', + 'Add' => 'Ekle', + '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' => 'Pano', + 'Confirmation' => 'Onay', + 'Webhooks' => 'Web Kancaları', + 'API' => 'API', + 'Create a comment from an external provider' => 'Dış sağlayıcı ile bir yorum oluştur', + 'Project management' => 'Proje yönetimi', + 'Columns' => 'Sütunlar', + 'Task' => 'Görev', + 'Percentage' => 'Yüzde', + 'Number of tasks' => 'Görev sayısı', + 'Task distribution' => 'Görev dağılımı', + 'Analytics' => 'Analiz', + 'Subtask' => 'Alt görev', + 'User repartition' => 'Kullanıcı dağılımı', + 'Clone this project' => 'Projenin kopyasını oluştur', + 'Column removed successfully.' => 'Sütun başarıyla kaldırıldı.', + '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ı', + 'Daily project summary' => 'Günlük proje özeti', + 'Daily project summary export' => 'Günlük proje özetini dışa aktar', + '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.', + 'Active swimlanes' => 'Aktif Kulvar', + 'Add a new swimlane' => 'Yeni bir Kulvar ekle', + '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', + 'Remove a swimlane' => 'Kulvarı sil', + 'Swimlane modification for the project "%s"' => '"%s" Projesi için Kulvar değişikliği', + 'Swimlane removed successfully.' => 'Kulvar başarıyla kaldırıldı.', + 'Swimlanes' => 'Kulvarlar', + 'Swimlane updated successfully.' => 'Kulvar başarıyla güncellendi.', + '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 has 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ış)', + 'Integrations' => 'Entegrasyon', + 'Integration with third-party services' => 'Dış servislerle entegrasyon', + 'Subtask Id' => 'Alt görev No:', + 'Subtasks' => 'Alt görevler', + 'Subtasks Export' => 'Alt görevleri dışa aktar', + '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', + 'Next' => 'Sonraki', + '#%d' => '#%d', + 'All swimlanes' => 'Tüm Kulvarlar', + 'All colors' => 'Tüm Renkler', + 'Moved to column %s' => '%s Sütununa taşındı', + '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', + '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 süre tablosu', + 'There is nothing to show.' => 'Gösterilecek hiçbir şey yok.', + 'Time Tracking' => 'Süre Çizelgesi', + '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?', + 'Disallow login form' => 'Giriş formu erişimini engelle', + 'Start' => 'Başlangıç', + 'End' => 'Bitiş', + '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 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', + 'Opposite label' => 'Zıt etiket', + 'Remove a link' => 'Bir link silmek', + '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', + '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' => 'Pano görünümü', + 'Keyboard shortcuts' => 'Klavye kısayolları', + 'Open board switcher' => 'Pano seçim listesini aç', + 'Application' => 'Uygulama', + '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', + 'Currency' => 'Para birimi', + 'Personal project' => 'Özel proje', + 'AUD - Australian Dollar' => 'AUD - Avustralya Doları', + 'CAD - Canadian Dollar' => 'CAD - Kanada Doları', + 'CHF - Swiss Francs' => 'CHF - İsviçre Frangı', + 'Custom Stylesheet' => 'Özel Sitil Css', + 'EUR - Euro' => 'EUR - Euro', + 'GBP - British Pound' => 'GBP - İngiliz Paund', + 'INR - Indian Rupee' => 'INR - Hint Rupesi', + 'JPY - Japanese Yen' => 'JPY - Japon Yeni', + 'NZD - New Zealand Dollar' => 'NZD - Yeni Zelanda Doları', + 'PEN - Peruvian Sol' => 'PEN - Peru Solü', + 'RSD - Serbian dinar' => 'Sırp Dinarı', + 'CNY - Chinese Yuan' => 'CNY - Çin Yuanı', + 'USD - US Dollar' => 'USD Amerikan Doları', + 'VES - Venezuelan Bolívar' => 'VES - Venezuela Bolivarı', + 'Destination column' => 'Hedef Sütun', + 'Move the task to another column when assigned to a user' => 'Bir kullanıcıya atandığında görevi başka bir sütuna taşı', + 'Move the task to another column when assignee is cleared' => 'Atanmış kullanıcı kaldırıldığında görevi başka bir sütuna taşı', + 'Source column' => 'Kaynak sütun', + 'Transitions' => 'Geçişler', + 'Executer' => 'Uygulayıcı', + 'Time spent in the column' => 'Sütunda geçen süre', + 'Task transitions' => 'Görev geçişleri', + 'Task transitions export' => 'Görev geçişlerini dışa aktar', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Bu rapor her bir görevin sütunlar arası geçişlerini tarih, kullanıcı ve sütunda harcanan zaman detaylarıyla içerir.', + 'Currency rates' => 'Döviz kurları', + 'Rate' => 'Kurlar', + 'Change reference currency' => 'Referans kur değiştir', + 'Reference currency' => 'Referans kur', + 'The currency rate has been added successfully.' => 'Kur başarıyla eklendi', + 'Unable to add this currency rate.' => 'Bu kur eklenemedi', + 'Webhook URL' => 'Web kancası URL', + '%s removed the assignee of the task %s' => '%s, %s görevinin atanan bilgisini kaldırdı', + 'Information' => 'Bilgi', + 'Check two factor authentication code' => 'Çift-Kademeli doğrulama kodunu kontrol et', + 'The two factor authentication code is not valid.' => 'Çift-Kademeli doğrulama kodu geçersiz', + 'The two factor authentication code is valid.' => 'Çift-Kademeli doğrulama kodu onaylandı', + 'Code' => 'Kod', + 'Two factor authentication' => 'Çift-Kademeli doğrulama', + 'This QR code contains the key URI: ' => 'Bu QR kodu anahtar URI içerir', + 'Check my code' => 'Kodu kontrol et', + 'Secret key: ' => 'Gizli anahtar', + 'Test your device' => 'Cihazınızı test edin', + 'Assign a color when the task is moved to a specific column' => 'Görev belirli bir sütuna taşındığında rengini değiştir', + '%s via Kanboard' => '%s Kanboard ile', + 'Burndown chart' => 'Kalan iş grafiği', + 'This chart show the task complexity over the time (Work Remaining).' => 'Bu grafik zorluk seviyesini zamana göre gösterir (kalan iş)', + 'Screenshot taken %s' => 'Ekran görüntüsü alındı %s', + 'Add a screenshot' => 'Bir ekran görüntüsü ekle', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Bir ekran görüntüsü alın ve buraya yapıştırmak için CTRL+V veya ⌘+V tuşlarına basın.', + 'Screenshot uploaded successfully.' => 'Ekran görüntüsü başarıyla yüklendi', + 'SEK - Swedish Krona' => ' SEK - İsveç Kronu', + 'Identifier' => 'Kimlik', + 'Disable two factor authentication' => 'Çift-Kademeli doğrulamayı iptal et', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Bu kullanıcı için Çift-Kademeli doğrulamayı iptal etmek istediğinize emin misiniz: "%s"?', + 'Edit link' => 'Linki düzenle', + 'Start to type task title...' => 'Görev başlığını yazmaya başlayın...', + 'A task cannot be linked to itself' => 'Bir görevden kendine link atanamaz', + 'The exact same link already exists' => 'Birebir aynı link zaten var', + 'Recurrent task is scheduled to be generated' => 'Tekrarlanan görev oluşturulması zamanlandı', + 'Score' => 'Skor', + 'The identifier must be unique' => 'Kimlik özgün olmalı', + 'This linked task id doesn\'t exists' => 'Link oluşturulan görec kodu mevcut değil', + 'This value must be alphanumeric' => 'Bu değer alfanumerik olmalı', + 'Edit recurrence' => 'Tekrarlanmayı düzenle', + 'Generate recurrent task' => 'Tekrarlanan görev oluştur', + 'Trigger to generate recurrent task' => 'Tekrarlanan görev oluşturmak için tetikleyici', + 'Factor to calculate new due date' => 'Yeni tamamlanma tarihi için hesaplama faktörü', + 'Timeframe to calculate new due date' => 'Yeni tamamlanma tarihinin hesaplanması için zaman dilimi', + 'Base date to calculate new due date' => 'Yeni tamamlanma tarihinin hesaplanması için baz alınacak tarih', + 'Action date' => 'Hareket tarihi', + 'Base date to calculate new due date: ' => 'Yeni tamamlanma tarihinin hesaplanması için baz alınacak tarih', + 'This task has created this child task: ' => 'Bu görev şu alt görevi oluşturdu:', + 'Day(s)' => 'Gün(ler)', + 'Existing due date' => 'Mevcut tamamlanma tarihi', + 'Factor to calculate new due date: ' => 'Yeni tamamlanma tarihi için hesaplama faktörü', + 'Month(s)' => 'Ay(lar)', + 'This task has been created by: ' => 'Bu görev şunun tarafından oluşturuldu:', + 'Recurrent task has been generated:' => 'Tekrarlanan görev oluşturuldu:', + 'Timeframe to calculate new due date: ' => 'Yeni tamamlanma tarihinin hesaplanması için zaman dilimi', + 'Trigger to generate recurrent task: ' => 'Tekrarlanan görev oluşturmak için tetikleyici', + 'When task is closed' => 'Görev kapatıldığı zaman', + 'When task is moved from first column' => 'Görev ilk sütundan taşındığı zaman', + 'When task is moved to last column' => 'Görev son sütuna taşındığı zaman', + 'Year(s)' => 'Yıl(lar)', + 'Project settings' => 'Proje ayarları', + 'Automatically update the start date' => 'Başlangıç tarihini otomatik olarak güncelle', + 'iCal feed' => 'iCal akışı', + 'Preferences' => 'Ayarlar', + 'Security' => 'Güvenlik', + 'Two factor authentication disabled' => 'Çift-Kademeli doğrulamayı devre dışı bırak', + 'Two factor authentication enabled' => 'Çift-Kademeli doğrulamayı etkinleştir', + 'Unable to update this user.' => 'Bu kullanıcı güncellenemiyor', + 'There is no user management for personal projects.' => 'Özel projeler için kullanıcı yönetimi yoktur.', + 'User that will receive the email' => 'Email alacak kullanıcı', + 'Email subject' => 'Email başlığı', + 'Date' => 'Tarih', + 'Add a comment log when moving the task between columns' => 'Görevi sütunlar arasında taşırken yorum kaydı ekle', + 'Move the task to another column when the category is changed' => 'Kategori değiştirildiğinde görevi başka sütuna taşı', + 'Send a task by email to someone' => 'Bir görevi email ile birine gönder', + 'Reopen a task' => 'Bir görevi tekrar aç', + 'Notification' => 'Uyarılar', + '%s moved the task #%d to the first swimlane' => '%s, #%d görevini birinci kulvara taşıdı', + 'Swimlane' => 'Kulvar', + '%s moved the task %s to the first swimlane' => '%s, %s görevini ilk kulvara taşıdı', + '%s moved the task %s to the swimlane "%s"' => '%s, %s görevini "%s" kulvarına taşıdı', + 'This report contains all subtasks information for the given date range.' => 'Bu rapor belirtilen tarih aralığında tüm alt görev bilgilerini içerir.', + 'This report contains all tasks information for the given date range.' => 'Bu rapor belirtilen tarih aralığında tüm görev bilgilerini içerir.', + 'Project activities for %s' => '%s için proje aktiviteleri', + 'view the board on Kanboard' => 'Pano yu Kanboard\'da görüntüle', + 'The task has been moved to the first swimlane' => 'Görev birinci kulvara taşındı', + 'The task has been moved to another swimlane:' => 'Görev başka bir kulvara taşındı:', + 'New title: %s' => 'Yeni başlık: %s', + 'The task is not assigned anymore' => 'Görev artık atanmamış', + 'New assignee: %s' => 'Yeni atanan: %s', + 'There is no category now' => 'Şu anda kategori yok', + 'New category: %s' => 'Yeni kategori: %s', + 'New color: %s' => 'Yeni renk: %s', + 'New complexity: %d' => 'Yeni zorluk seviyesi: %d', + 'The due date has been removed' => 'Tamamlanma tarihi silindi', + 'There is no description anymore' => 'Artık açıklama yok', + 'Recurrence settings has been modified' => 'Tekrarlanma ayarları değiştirildi', + 'Time spent changed: %sh' => 'Geçen süre değiştirildi: %sh', + 'Time estimated changed: %sh' => 'Tahmini süre değiştirildi: %sh', + 'The field "%s" has been updated' => '"%s" hanesi değiştirildi', + 'The description has been modified:' => 'Açıklama değiştirildi', + 'Do you really want to close the task "%s" as well as all subtasks?' => '"%s" görevini ve tüm alt görevlerini kapatmak istediğinize emin misiniz?', + 'I want to receive notifications for:' => 'Bununla ilgili bildirimler almak istiyorum:', + 'All tasks' => 'Tüm görevler', + 'Only for tasks assigned to me' => 'Yalnızca bana atanmış görevler için', + 'Only for tasks created by me' => 'Yalnızca benim oluşturduğum görevler için', + 'Only for tasks created by me and tasks assigned to me' => 'Yalnızca benim oluşturduğum ve bana atanmış görevler için', + '%%Y-%%m-%%d' => '%%Y-%%m-%%d', + 'Total for all columns' => 'Tüm sütunlar için toplam', + 'You need at least 2 days of data to show the chart.' => 'Grafiği göstermek için en az iki günlük veriye ihtiyaç var.', + '<15m' => '<15dk', + '<30m' => '<30dk', + 'Stop timer' => 'Zamanlayıcıyı durdur', + 'Start timer' => 'Zamanlayıcıyı başlat', + 'My activity stream' => 'Olay akışım', + 'Search tasks' => 'Görevleri ara', + 'Reset filters' => 'Filtreleri sıfırla', + 'My tasks due tomorrow' => 'Yarına tamamlanması gereken görevlerim', + 'Tasks due today' => 'Bugün tamamlanması gereken görevler', + 'Tasks due tomorrow' => 'Yarına tamamlanması gereken görevler', + 'Tasks due yesterday' => 'Dün tamamlanmış olması gereken görevler', + 'Closed tasks' => 'Kapatılmış görevler', + 'Open tasks' => 'Açık görevler', + 'Not assigned' => 'Atanmamış', + 'View advanced search syntax' => 'Gelişmiş arama kodlarını göster', + 'Overview' => 'Özet Görünüm', + 'Board/Calendar/List view' => 'Pano/Takvim/Liste görünümü', + 'Switch to the board view' => 'Pano görünümüne geç', + 'Switch to the list view' => 'Liste görünümüne geç', + 'Go to the search/filter box' => 'Arama/Filtreleme kutusuna git', + 'There is no activity yet.' => 'Henüz bir aktivite yok.', + 'No tasks found.' => 'Hiç görev bulunamadı.', + 'Keyboard shortcut: "%s"' => 'Klavye kısayolu: "%s"', + 'List' => 'Liste', + 'Filter' => 'Filtre', + 'Advanced search' => 'Gelişmiş arama', + 'Example of query: ' => 'Sorgu örneği', + 'Search by project: ' => 'Projeye göre ara', + 'Search by column: ' => 'Sütuna göre ara', + 'Search by assignee: ' => 'Atanana göre ara', + 'Search by color: ' => 'Renge göre ara', + 'Search by category: ' => 'Kategoriye göre ara', + 'Search by description: ' => 'Açıklamaya göre ara', + 'Search by due date: ' => 'Tamamlanma tarihine göre ara', + 'Average time spent in each column' => 'Her bir sütunda geçirilen ortalama süre', + 'Average time spent' => 'Geçen ortalama süre', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Bu grafik son %d görev için her bir sütunda geçirilen ortalama süreyi gösterir.', + 'Average Lead and Cycle time' => 'Ortalama teslim ve çevrim süresi', + 'Average lead time: ' => 'Ortalama teslim süresi', + 'Average cycle time: ' => 'Ortalama çevrim süresi', + 'Cycle Time' => 'Çevrim süresi', + 'Lead Time' => 'Teslim süresi', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Bu grafik son %d görev için zaman içinde gerçekleşen ortalama teslim ve çevrim sürelerini gösterir.', + 'Average time into each column' => 'Her bir sütunda ortalama zaman', + 'Lead and cycle time' => 'Teslim ve çevrim süresi', + 'Lead time: ' => 'Teslim süresi: ', + 'Cycle time: ' => 'Çevrim süresi: ', + 'Time spent in each column' => 'Her sütunda geçen süre', + 'The lead time is the duration between the task creation and the completion.' => 'Teslim süresi, görevin oluşturulması ile tamamlanması arasında geçen süredir.', + 'The cycle time is the duration between the start date and the completion.' => 'Çevrim süresi, görevin başlangıç tarihi ile tamamlanması arasında geçen süredir.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Eğer görev henüz kapatılmamışsa, tamamlanma tarihi yerine şu anki tarih kullanılır.', + 'Set the start date automatically' => 'Başlangıç tarihini otomatik olarak belirle', + 'Edit Authentication' => 'Doğrulamayı düzenle', + 'Remote user' => 'Uzak kullanıcı', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Uzak kullanıcıların şifreleri Kanboard veritabanında saklanmaz, örnek: LDAP, Google ve Github hesapları', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Eğer giriş formuna erişimi engelleyi seçerseniz, giriş formuna girilen bilgiler gözardı edilir.', + 'Default task color' => 'Varsayılan görev rengi', + 'This feature does not work with all browsers.' => 'Bu özellik tüm tarayıcılarla çalışmaz', + 'There is no destination project available.' => 'Seçilebilecek hedef proje yok.', + 'Trigger automatically subtask time tracking' => 'Altgörev zamanlayıcıyı otomatik olarak başlat', + 'Include closed tasks in the cumulative flow diagram' => 'Kümülatif akış diyagramında kapatılmış görevleri de dahil et', + 'Current swimlane: %s' => 'Geçerli kulvar: %s', + 'Current column: %s' => 'Geçerli sütun: %s', + 'Current category: %s' => 'Geçerli kategori: %s', + 'no category' => 'kategori yok', + 'Current assignee: %s' => 'Geçerli atanan: %s', + 'not assigned' => 'atanmamış', + 'Author:' => 'Yazar', + 'contributors' => 'Katkı sağlayanlar', + 'License:' => 'Lisans:', + 'License' => 'Lisans', + 'Enter the text below' => 'Aşağıdaki metni girin', + 'Start date:' => 'Başlangıç tarihi:', + 'Due date:' => 'Tamamlanması gereken tarih:', + 'People who are project managers' => 'Proje müdürü olan kişiler', + 'People who are project members' => 'Proje üyesi olan kişiler', + 'NOK - Norwegian Krone' => ' NOK - Norveç Kronu', + 'Show this column' => 'Bu sütunu göster', + 'Hide this column' => 'Bu sütunu gizle', + 'End date' => 'Bitiş tarihi', + 'Users overview' => 'Kullanıcılara genel bakış', + 'Members' => 'Üyeler', + 'Shared project' => 'Paylaşılan proje', + 'Project managers' => 'Proje müdürleri', + 'Projects list' => 'Proje listesi', + 'End date:' => 'Bitiş tarihi:', + 'Change task color when using a specific task link' => 'Belirli bir görev bağlantısı kullanıldığında görevin rengini değiştir', + 'Task link creation or modification' => 'Görev bağlantısı oluşturulması veya değiştirilmesi', + 'Milestone' => 'Kilometre taşı', + 'Reset the search/filter box' => 'Arama/Filtre kutusunu sıfırla', + 'Documentation' => 'Dokümantasyon', + 'Author' => 'Yazar', + 'Version' => 'Versiyon', + 'Plugins' => 'Eklentiler', + 'There is no plugin loaded.' => 'Yüklenmiş bir eklendi yok', + 'My notifications' => 'Bildirimlerim', + 'Custom filters' => 'Özel filtreler', + 'Your custom filter has been created successfully.' => 'Özel filtreleriniz başarıyla oluşturuldu.', + 'Unable to create your custom filter.' => 'Özel filtreniz oluşturulamadı.', + 'Custom filter removed successfully.' => 'Özel filtreniz başarıyla silindi.', + 'Unable to remove this custom filter.' => 'Bu özel filtre silinemiyor.', + 'Edit custom filter' => 'Özel filtreyi düzenle', + 'Your custom filter has been updated successfully.' => 'Özel filtreleriniz başarıyla güncellendi.', + 'Unable to update custom filter.' => 'Özel filtre güncellenemiyor.', + 'Web' => 'İnternet', + 'New attachment on task #%d: %s' => '#%d görevinde yeni dosya: %s', + 'New comment on task #%d' => '#%d görevinde yeni yorum', + 'Comment updated on task #%d' => '#%d görevinde yorum güncellendi', + 'New subtask on task #%d' => '#%d görevinde yeni alt görev', + 'Subtask updated on task #%d' => '#%d görevinde alt görev güncellendi', + 'New task #%d: %s' => 'Yeni #%d görevi: %s', + 'Task updated #%d' => '#%d görevi güncellendi', + 'Task #%d closed' => '#%d görevi kapatıldı', + 'Task #%d opened' => '#%d görevi oluşturuldu', + 'Column changed for task #%d' => '#%d görevinin sütunu değişti', + 'New position for task #%d' => '#%d görevinin konumu değişti', + 'Swimlane changed for task #%d' => '#%d görevinin kulvarı değişti', + 'Assignee changed on task #%d' => '#%d görevine atanan değişti', + '%d overdue tasks' => '%d gecikmiş görev', + 'No notification.' => 'Yeni bildirim yok.', + 'Mark all as read' => 'Tümünü okunmuş olarak işaretle', + 'Mark as read' => 'Okunmuş olarak işaretle', + 'Total number of tasks in this column across all swimlanes' => 'Bu sutündaki görev sayısının tüm kulvarlardaki toplamı', + 'Collapse swimlane' => 'Kulvarı daralt', + 'Expand swimlane' => 'Kulvarı genişlet', + 'Add a new filter' => 'Yeni bir filtre ekle', + 'Share with all project members' => 'Tüm proje üyeleriyle paylaş', + 'Shared' => 'Paylaşılan', + 'Owner' => 'Sahibi', + 'Unread notifications' => 'Okunmamış bildirimler', + 'Notification methods:' => 'Bildirim yöntemleri:', + 'Unable to read your file' => 'Dosya okunamıyor', + '%d task(s) have been imported successfully.' => '%d görev başarıyla içeri aktarıldı.', + 'Nothing has been imported!' => 'Hiçbir şey içeri aktarılamadı!', + 'Import users from CSV file' => 'CSV dosyasından kullanıcıları içeri aktar', + '%d user(s) have been imported successfully.' => '%d kullanıcı başarıyla içeri aktarıldı.', + 'Comma' => 'Virgül', + 'Semi-colon' => 'Noktalı virgül', + 'Tab' => 'Tab', + 'Vertical bar' => 'Dikey çizgi', + 'Double Quote' => 'Tırnak işareti', + 'Single Quote' => 'Kesme işareti', + '%s attached a file to the task #%d' => '%s, #%d görevine bir dosya ekledi.', + 'There is no column or swimlane activated in your project!' => 'Projenizde etkinleştirilmiş hiç bir sütun veya kulvar yok!', + 'Append filter (instead of replacement)' => 'Fıltreye ekle (üzerine yazmak yerine)', + 'Append/Replace' => 'Ekle/Üzerine yaz', + 'Append' => 'Ekle', + 'Replace' => 'Üzerine yaz', + 'Import' => 'İçeri aktar', + 'Change sorting' => 'sıralamayı değiştir', + 'Tasks Importation' => 'Görevleri içeri aktar', + 'Delimiter' => 'Ayırıcı', + 'Enclosure' => 'Enclosure', + 'CSV File' => 'CSV Dosyası', + 'Instructions' => 'Yönergeler', + 'Your file must use the predefined CSV format' => 'Dosyanız önceden belirlenmiş CSV formatını kullanmalı', + 'Your file must be encoded in UTF-8' => 'Dosyanız UTF-8 kodlamasında olmalı', + 'The first row must be the header' => 'İlk satır başlık olmalı', + 'Duplicates are not verified for you' => 'Çift girişler sizin için onaylanmamış', + 'The due date must use the ISO format: YYYY-MM-DD' => 'Tamamlanma tarihi ISO formatını kullanmalı: YYYY-MM-DD', + 'Download CSV template' => 'CSV taslağını indir', + 'No external integration registered.' => 'Hiç dış entegrasyon kaydedilmemiş.', + 'Duplicates are not imported' => 'Çift girişler içeri aktarılmaz', + 'Usernames must be lowercase and unique' => 'Kullanıcı adları küçük harf ve tekil olmalı', + 'Passwords will be encrypted if present' => 'Şifreler (eğer varsa) kriptolanır', + '%s attached a new file to the task %s' => '%s, %s görevine yeni dosya ekledi', + 'Link type' => 'Bağlantı türü', + 'Assign automatically a category based on a link' => 'Bir bağlantıya göre otomatik olarak kategori ata', + 'BAM - Konvertible Mark' => 'BAM - Konvertible Mark', + 'Assignee Username' => 'Atanan kullanıcı adı', + 'Assignee Name' => 'Atanan İsmi', + 'Groups' => 'Gruplar', + 'Members of %s' => '%s in üyeleri', + 'New group' => 'Yeni grup', + 'Group created successfully.' => 'Grup başarıyla oluşturuldu.', + 'Unable to create your group.' => 'Grup oluşturulamadı.', + 'Edit group' => 'Grubu düzenle', + 'Group updated successfully.' => 'Grup başarıyla güncellendi.', + 'Unable to update your group.' => 'Grup güncellenemedi.', + 'Add group member to "%s"' => '"%s" e grup üyesi ekle', + 'Group member added successfully.' => 'Grup üyesi başarıyla eklendi.', + 'Unable to add group member.' => 'Grup üyesi eklenemedi.', + 'Remove user from group "%s"' => '"%s" grubundan kullanıcı çıkar', + 'User removed successfully from this group.' => 'Kullanıcı bu gruptan başarıyla çıkarıldı.', + 'Unable to remove this user from the group.' => 'Bu kullanıcı bu grubtan çıkarılamadı', + 'Remove group' => 'Grubu sil', + 'Group removed successfully.' => 'Grup başarıyla silindi.', + 'Unable to remove this group.' => 'Grup silinemedi.', + 'Project Permissions' => 'Proje izimleri', + 'Manager' => 'Müdür', + 'Project Manager' => 'Proje müdürü', + 'Project Member' => 'Proje üyesi', + 'Project Viewer' => 'Proje izleyicisi', + 'Your account is locked for %d minutes' => 'Hesabınız %d dakika boyunca kilitlendi', + 'Invalid captcha' => 'Geçersiz captcha', + 'The name must be unique' => 'İsim tekil olmalı', + 'View all groups' => 'Tüm grupları görüntüle', + 'There is no user available.' => 'Uygun üye yok', + 'Do you really want to remove the user "%s" from the group "%s"?' => '"%s" kullanıcısını "%s" grubundan çıkarmak istediğinize emin misiniz?', + 'There is no group.' => 'Hiç grup yok.', + 'Add group member' => 'Grup üyesi ekle', + 'Do you really want to remove this group: "%s"?' => '"%s" grubunu silmek istediğinize emin misiniz?', + 'There is no user in this group.' => 'Bu grupta hiç kullanıcı yok.', + 'Permissions' => 'İzinler', + 'Allowed Users' => 'İzin verilen kullanıcı', + 'No specific user has been allowed.' => 'Hiç bir kullanıcıya özel olarak izin verilmemiş.', + 'Role' => 'Rol', + 'Enter user name...' => 'Kullanıcı adını girin...', + 'Allowed Groups' => 'İzinli gruplar', + 'No group has been allowed.' => 'Hiç bir gruba özel olarak izin verilmemiş', + 'Group' => 'Grup', + 'Group Name' => 'Grup adı', + 'Enter group name...' => 'Grup adını girin...', + 'Role:' => 'Rol:', + 'Project members' => 'Proje üyeleri', + '%s mentioned you in the task #%d' => '%s sizden #%d görevinde bahsetti', + '%s mentioned you in a comment on the task #%d' => '%s sizden #%d görevindeki bir yorumda bahsetti', + 'You were mentioned in the task #%d' => '#%d görevinde sizden bahsedildi', + 'You were mentioned in a comment on the task #%d' => '#%d görevindeki bir yorumda sizden bahsedildi', + 'Estimated hours: ' => 'Tahmini saat:', + 'Actual hours: ' => 'Gerçekleşen saat:', + 'Hours Spent' => 'Harcanan saat', + 'Hours Estimated' => 'Tahmini saat', + 'Estimated Time' => 'Tahmini süre', + 'Actual Time' => 'Gerçekleşen süre', + 'Estimated vs actual time' => 'Tahmini vs gerçekleşen süre', + 'RUB - Russian Ruble' => ' RUB - Rus Rublesi', + 'Assign the task to the person who does the action when the column is changed' => 'Sütun değiştirildiği zaman görevi eylemi gerçekleştiren kişiye ata', + 'Close a task in a specific column' => 'Belirli bir sütundaki görevi kapat', + 'Time-based One-time Password Algorithm' => 'Zamana bağlı Tek-Kullanımlık şifre algoritması', + 'Two-Factor Provider: ' => 'Çift-Kademeli doğrulama sağlayıcısı: ', + 'Disable two-factor authentication' => 'Çift-Kademeli doğrulamayı devre dışı bırak', + 'Enable two-factor authentication' => 'Çift-Kademeli doğrulamayı etkinleştir', + 'There is no integration registered at the moment.' => 'Şu anda kayıtlı bir entegrasyon bulunmuyor.', + 'Password Reset for Kanboard' => 'Kanboard için şifre sıfırlama', + 'Forgot password?' => 'Şifrenizi mi unuttunuz?', + 'Enable "Forget Password"' => '"Şifremi unuttum" komutunu etkinleştir', + 'Password Reset' => 'Şifre sıfırlama', + 'New password' => 'Yeni şifre', + 'Change Password' => 'Şifreyi değiştir', + 'To reset your password click on this link:' => 'Şifrenizi sıfırlamak için bu linke tıklayın:', + 'Last Password Reset' => 'Son şifre sıfırlama', + 'The password has never been reinitialized.' => 'Şifre hiç bir zaman tekrar başlatılmamış.', + 'Creation' => 'Oluşturulma', + 'Expiration' => 'Sona erme', + 'Password reset history' => 'Şifre sıfırlama geçmişi', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => '"%s" sütunu ve "%s" kulvarındaki tüm görevler başarıyla kapatıldı.', + 'Do you really want to close all tasks of this column?' => 'Bu sütundaki tüm görevleri kapatmak istediğinize emin misiniz?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '"%s" sütunu ve "%s" kulvarındaki %d görev kapatılacak.', + 'Close all tasks in this column and this swimlane' => 'Bu sütundaki tüm görevleri kapat', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Proje bildirimleri için hiçbir plugin kaydedilmedi. Yine de profil sayfanızdan bildirim ayarları yapabilirsiniz.', + 'My dashboard' => 'Panom', + 'My profile' => 'Profilim', + 'Project owner: ' => 'Proje sahibi: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'Proje kodu opsiyoneldir ve alfanümerik olmalıdır, örneğin: PROJE1', + 'Project owner' => 'Proje sahibi', + 'Personal projects do not have users and groups management.' => '(Kişiye) Özel projelerde kullanıcı ve grup yönetimi yoktur.', + 'There is no project member.' => 'Proje ekibi yok.', + 'Priority' => 'Öncelik', + 'Task priority' => 'Görev önceliği', + 'General' => 'Genel', + 'Dates' => 'Tarihler', + 'Default priority' => 'Varsayılan öncelik', + 'Lowest priority' => 'En düşük öncelik', + 'Highest priority' => 'En yüksek öncelik', + 'Close a task when there is no activity' => 'Bir aktivite olmadığında görevi kapat', + 'Duration in days' => 'Süre (gün olarak)', + 'Send email when there is no activity on a task' => 'Bir görevde aktivite olmadığında e-posta gönder', + 'Unable to fetch link information.' => 'Link eşleştirilemiyor.', + 'Daily background job for tasks' => 'Görevler için günlük otomatik arkaplan işleri', + 'Auto' => 'Otomatik', + 'Related' => 'İlişkili', + 'Attachment' => 'Ek', + 'Web Link' => 'Web Linki', + 'External links' => 'Harici linkler', + 'Add external link' => 'Harici link ekle', + 'Type' => 'Tip', + 'Dependency' => 'Bağımlılık', + 'Add internal link' => 'Dahili link ekle', + 'Add a new external link' => 'Yeni bir harici link ekle', + 'Edit external link' => 'Harici linki düzenle', + 'External link' => 'Harici link', + 'Copy and paste your link here...' => 'Linkinizi kopyalayıp buraya yapıştırın...', + 'URL' => 'URL', + 'Internal links' => 'Dahili linkler', + 'Assign to me' => 'Bana ata', + 'Me' => 'Ben', + 'Do not duplicate anything' => 'Hiçbir projeyi örnek alma', + 'Projects management' => 'Proje yönetimi', + 'Users management' => 'Kullanıcı yönetimi', + 'Groups management' => 'Grup yönetimi', + 'Create from another project' => 'Başka bir projeden oluştur', + 'open' => 'açık', + 'closed' => 'kapalı', + 'Priority:' => 'Öncelik', + 'Reference:' => 'Referans', + 'Complexity:' => 'Zorluk', + 'Swimlane:' => 'Kulvar', + 'Column:' => 'Kolon', + 'Position:' => 'Pozisyon', + 'Creator:' => 'Oluşturan', + 'Time estimated:' => 'Tahmini zaman', + '%s hours' => '%s saat', + 'Time spent:' => 'Geçen Süre:', + 'Created:' => 'Oluşturuldu', + 'Modified:' => 'Güncellendi', + 'Completed:' => 'Tamamlandı', + 'Started:' => 'Başlatıldı', + 'Moved:' => 'Taşındı', + 'Task #%d' => 'Görev #%d', + 'Time format' => 'Saat formatı', + 'Start date: ' => 'Başlangıç tarihi', + 'End date: ' => 'Bitiş tarihi', + 'New due date: ' => 'Yeni hedef tarih', + 'Start date changed: ' => 'Başlangıç tarihi değişti:', + 'Disable personal projects' => 'Özel projeleri engelle', + 'Do you really want to remove this custom filter: "%s"?' => 'Bu özel filtreyi gerçekten kaldırmak istiyor musunuz: "%s"?', + 'Remove a custom filter' => 'Özel filtre kaldır', + 'User activated successfully.' => 'Kullanıcı başarıyla aktifleştirildi', + 'Unable to enable this user.' => 'Bu kullanıcı etkinleştirilemiyor.', + 'User disabled successfully.' => 'Kullanıcı başarıyla engellendi.', + 'Unable to disable this user.' => 'Bu kullanıcı engellenemez.', + 'All files have been uploaded successfully.' => 'Tüm dosyalar başarıyla yüklendi.', + 'The maximum allowed file size is %sB.' => 'Maksimum dosya büyüklüğü %s B.', + 'Drag and drop your files here' => 'Dosyalarınızı buraya sürükleyip bırakın', + 'choose files' => 'dosyaları seç', + 'View profile' => 'Profili göster', + 'Two Factor' => 'İki faktör', + 'Disable user' => 'Kullanıcıyı engelle', + 'Do you really want to disable this user: "%s"?' => '%s kullanıcısını gerçekten engellemek istiyor musunuz?', + 'Enable user' => 'Kullanıcıyı etkinleştir', + 'Do you really want to enable this user: "%s"?' => '%s kullanıcısını gerçekten etkinleştirmek istiyor musunuz?', + 'Download' => 'İndir', + 'Uploaded: %s' => 'Yükle: %s', + 'Size: %s' => 'Boyut: %s', + 'Uploaded by %s' => '%s tarafından yüklendi', + 'Filename' => 'Dosya adı', + 'Size' => 'Boyutu', + 'Column created successfully.' => 'Kolon başarıyla oluşturuldu.', + 'Another column with the same name exists in the project' => 'Projede aynı isimli başka bir kolon var', + 'Default filters' => 'Varsayılan filtreler', + 'Your board doesn\'t have any columns!' => 'Pano nuzda kolon bulunmuyor!', + 'Change column position' => 'Kolon sıralamasını değiştir', + 'Switch to the project overview' => 'Proje özetine geç', + 'User filters' => 'Kullanıcı filtreleri', + 'Category filters' => 'Kategori filtreleri', + 'Upload a file' => 'Bir dosya yükle', + 'View file' => 'Dosyayı göster', + 'Last activity' => 'Son aktivite', + 'Change subtask position' => 'Alt görev sırasını değiştir', + 'This value must be greater than %d' => 'Bu değer %d den büyük olmalı', + 'Another swimlane with the same name exists in the project' => 'Projede aynı isimli başka bir kulvar var', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Örneğin: https://ornek.kanboard.org/ (sabit URLler oluşturmak için)', + 'Actions duplicated successfully.' => 'İşlemler başarıyla çoklandı.', + 'Unable to duplicate actions.' => 'İşlemler çoklanamıyor.', + 'Add a new action' => 'Yeni bir işlem ekle', + 'Import from another project' => 'Başka bir projeden aktar', + 'There is no action at the moment.' => 'Şu anda bir işlem yok', + 'Import actions from another project' => 'Başka bir projeden işlemleri aktar', + 'There is no available project.' => 'Uygun bir proje yok.', + 'Local File' => 'Yerel dosya', + 'Configuration' => 'Konfigürasyon', + 'PHP version:' => 'PHP versiyonu:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'OS versiyonu:', + 'Database version:' => 'Veritabanı versiyonu:', + 'Browser:' => 'Tarayıcı:', + 'Task view' => 'Görev görünümü', + 'Edit task' => 'Görev güncelle', + 'Edit description' => 'Açıklamayı güncelle', + 'New internal link' => 'Yeni iç link', + 'Display list of keyboard shortcuts' => 'Kısayol tuşları listesini göster', + 'Avatar' => 'Simge', + 'Upload my avatar image' => 'Avatar resmimi yükle', + 'Remove my image' => 'Resmimi kaldır', + 'The OAuth2 state parameter is invalid' => 'OAuth2 durum parametresi geçersiz', + 'User not found.' => 'Kullanıcı bulunamadı', + 'Search in activity stream' => 'Aktivite akışında ara', + 'My activities' => 'Aktivitelerim', + 'Activity until yesterday' => 'Düne kadar olan aktiviteler', + 'Activity until today' => 'Bugüne kadar olan aktiviteler', + 'Search by creator: ' => 'Oluşturan ile ara', + 'Search by creation date: ' => 'Oluşturma tarihi ile ara', + 'Search by task status: ' => 'Görev durumu ile ara', + 'Search by task title: ' => 'Görev başlığı ile ara', + 'Activity stream search' => 'Aktivite akışı araması', + 'Projects where "%s" is manager' => '%s in müdürü olduğu projeler', + 'Projects where "%s" is member' => '%s in ekip üyesi olduğu projeler', + 'Open tasks assigned to "%s"' => '%s e atanan görevleri aç', + 'Closed tasks assigned to "%s"' => '%s e atanan görevleri kapat', + 'Assign automatically a color based on a priority' => 'Önceliğe bağlı olarak bir renk belirle', + 'Overdue tasks for the project(s) "%s"' => '%s proje(leri) için süresi geçen görevler', + 'Upload files' => 'Dosyaları yükle', + 'Installed Plugins' => 'Yüklenmiş Eklentiler', + 'Plugin Directory' => 'Eklenti Klasörü', + 'Plugin installed successfully.' => 'Eklenti başarıyla kuruldu.', + 'Plugin updated successfully.' => 'Eklenti başarıyla güncellendi.', + 'Plugin removed successfully.' => 'Eklenti başarıyla kaldırıldı.', + 'Subtask converted to task successfully.' => 'Alt görev başarıyla göreve dönüştürüldü.', + 'Unable to convert the subtask.' => 'Alt görev dönüştürülemedi', + 'Unable to extract plugin archive.' => 'Plugin (sıkıştırılmış) dosyası açılamadı.', + 'Plugin not found.' => 'Eklenti bulunamadı', + 'You don\'t have the permission to remove this plugin.' => 'Bu plugin\'i kaldırmak için yetkiniz yok.', + 'Unable to download plugin archive.' => 'Eklenti dosyası indirilemedi.', + 'Unable to write temporary file for plugin.' => 'Eklenti için geçici dosya oluşturulamadı.', + 'Unable to open plugin archive.' => 'Eklenti dosyası açılamadı.', + 'There is no file in the plugin archive.' => 'Eklenti (sıkıştırılmış) dosyasında, dosya bulunmuyor.', + 'Create tasks in bulk' => 'Toplu olarak görev oluştur', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Kanbord sisteminiz arayüzden eklenti kurulacak şekilde ayarlanmamış.', + 'There is no plugin available.' => 'Uygun eklenti yok.', + 'Install' => 'Kur', + 'Update' => 'Güncelle', + 'Up to date' => 'Güncel', + 'Not available' => 'Uygun değil', + 'Remove plugin' => 'Eklentiyi kaldır', + 'Do you really want to remove this plugin: "%s"?' => '"%s" eklentiyi gerçekten kaldırmak istiyor musunuz?', + 'Uninstall' => 'Kaldır', + 'Listing' => 'Listeliyor', + 'Metadata' => 'Meta veri', + 'Manage projects' => 'Projeler', + 'Convert to task' => 'Göreve dönüştür', + 'Convert sub-task to task' => 'Alt görevi göreve dönüştür', + 'Do you really want to convert this sub-task to a task?' => 'Bu alt görevi, göreve dönüştüreceğinize emin misiniz?', + 'My task title' => 'Görev başlığım', + 'Enter one task by line.' => 'Görevleri satır satır girin', + 'Number of failed login:' => 'Başarısız login denemesi sayısı', + 'Account locked until:' => 'Hesap şu zamana kadar kilitlendi:', + 'Email settings' => 'E-posta ayarları', + 'Email sender address' => 'E-posta gönderen adresi', + 'Email transport' => 'E-posta taşıma', + 'Webhook token' => 'Web kancası anahtarı', + 'Project tags management' => 'Proje etiket yönetimi', + 'Tag created successfully.' => 'Etiket başarıyla oluturuldu.', + 'Unable to create this tag.' => 'Etiket oluşturulamıyor.', + 'Tag updated successfully.' => 'Etiket başarıyla güncellendi.', + 'Unable to update this tag.' => 'Etiket güncellenemedi.', + 'Tag removed successfully.' => 'Etiket başarıyla kaldırıldı.', + 'Unable to remove this tag.' => 'Etiket kaldırılamadı.', + 'Global tags management' => 'Genel etiket yönetimi', + 'Tags' => 'Etiketler', + 'Tags management' => 'Etiket yönetimi', + 'Add new tag' => 'Yeni etiket ekle', + 'Edit a tag' => 'Etiket güncelle', + 'Project tags' => 'Proje etiketleri', + 'There is no specific tag for this project at the moment.' => 'Proje için şu anda bir etiket bulunmuyor.', + 'Tag' => 'Etiket', + 'Remove a tag' => 'Etiket kaldır', + 'Do you really want to remove this tag: "%s"?' => '"%s" etiketini kaldıracağınıza emin misiniz?', + 'Global tags' => 'Genel etiketler', + 'There is no global tag at the moment.' => 'Şu anda genel bir etiket bulunmuyor.', + 'This field cannot be empty' => 'Bu alan boş bırakılamaz', + 'Close a task when there is no activity in a specific column' => 'Bir kolonda hareket olmadığında bir görevi kapat', + '%s removed a subtask for the task #%d' => '%d görevi için %s bir alt görevi kaldırdı', + '%s removed a comment on the task #%d' => '%s %d görevi için bir yorumu kaldırdı', + 'Comment removed on task #%d' => '%d görevindeki yorum kaldırıldı', + 'Subtask removed on task #%d' => '%d görevindeki alt görev kaldırıldı', + 'Hide tasks in this column in the dashboard' => 'Panoda bu kolondaki görevleri gizle', + '%s removed a comment on the task %s' => '%s %s görevinde bir yorumu kaldırdı', + '%s removed a subtask for the task %s' => '%s %s alt görevini kaldırdı', + 'Comment removed' => 'Yorum kaldırıldı', + 'Subtask removed' => 'Alt görev kaldırıldı', + '%s set a new internal link for the task #%d' => '%d görevi için %s bir iç link oluşturdu', + '%s removed an internal link for the task #%d' => '%d görevi için %s bir iç linki kaldırdı', + 'A new internal link for the task #%d has been defined' => '%d görevi için yeni bir iç link tanımlandı', + 'Internal link removed for the task #%d' => '%d görevi için iç link kaldırıldı', + '%s set a new internal link for the task %s' => '%s görevi için %s yeni bir iç link oluşturdu', + '%s removed an internal link for the task %s' => '%s %s görevi için bir iç linki kaldırdı', + 'Automatically set the due date on task creation' => 'Görev oluştururken hedef tarihi otomatik ata', + 'Move the task to another column when closed' => 'Görev kapandığında başka bir kolona taşı', + 'Move the task to another column when not moved during a given period' => 'Bir kolonda belirli bir süre hareketsiz kalırsa görevi başka bir kolona taşı', + 'Dashboard for %s' => '%s için Pano', + 'Tasks overview for %s' => '%s için görev özeti', + 'Subtasks overview for %s' => '%s için alt görev özeti', + 'Projects overview for %s' => '%s için proje özeti', + 'Activity stream for %s' => '%s için akış', + 'Assign a color when the task is moved to a specific swimlane' => 'Görev bir kulvara taşındığında rengini değiştir', + 'Assign a priority when the task is moved to a specific swimlane' => 'Görev bir kulvara taşındığında önceliğini değiştir', + 'User unlocked successfully.' => 'Kullanıcı kilidi başarıyla kaldırıldı.', + 'Unable to unlock the user.' => 'Kullanıcı kilitlenemiyor.', + 'Move a task to another swimlane' => 'Görevi başka bir kulvara taşı', + 'Creator Name' => 'Oluşturan Adı', + 'Time spent and estimated' => 'Tahmini ve geçen süre', + 'Move position' => 'Taşıma sırası', + 'Move task to another position on the board' => 'Görevin sırasını değiştir', + 'Insert before this task' => 'Bu görevin öncesine oluştur', + 'Insert after this task' => 'Bu görevin ardına oluştur', + 'Unlock this user' => 'Bu kullanıcının kilidini kaldır', + 'Custom Project Roles' => 'Ek Proje Rolleri', + 'Add a new custom role' => 'Proje rolü ekle', + 'Restrictions for the role "%s"' => '"%s" rolü için kısıtlamalar', + 'Add a new project restriction' => 'Yeni kısıtlama ekle', + 'Add a new drag and drop restriction' => 'Sürükle bırak kısıtlaması ekle', + 'Add a new column restriction' => 'Kolon kısıtlaması ekle', + 'Edit this role' => 'Rolü güncelle', + 'Remove this role' => 'Rolü kaldır', + 'There is no restriction for this role.' => 'Bu rol için bir kısıtlama yok', + 'Only moving task between those columns is permitted' => 'Sadece belirlenen kolonlar arasında taşıma mümkündür', + 'Close a task in a specific column when not moved during a given period' => 'Bir kolonda belirli bir süre hareketsiz kalan görev kapatılsın', + 'Edit columns' => 'Kolonları güncelle', + 'The column restriction has been created successfully.' => 'Kolon kısıtlaması başarıyla oluşturuldu.', + 'Unable to create this column restriction.' => 'Kolon kısıtlaması oluşturulamadı.', + 'Column restriction removed successfully.' => 'Kolon kısıtlaması başarıyla kaldırıldı.', + 'Unable to remove this restriction.' => 'Bu kısıtlama kaldırılamıyor.', + 'Your custom project role has been created successfully.' => 'Ek proje rolü başarıyla oluşturuldu.', + 'Unable to create custom project role.' => 'Ek proje rolü oluşturulamadı', + 'Your custom project role has been updated successfully.' => 'Ek proje rolü başarıyla güncellendi.', + 'Unable to update custom project role.' => 'Ek proje rolü güncellenemiyor.', + 'Custom project role removed successfully.' => 'Ek proje rolü başarıyla kaldırıldı.', + 'Unable to remove this project role.' => 'Proje rolü kaldırılamıyor.', + 'The project restriction has been created successfully.' => 'Proje kısıtlaması başarıyla oluşturuldu.', + 'Unable to create this project restriction.' => 'Proje kısıtlaması oluşturulamadı', + 'Project restriction removed successfully.' => 'Proje kısıtlaması başarıyla kaldırıldı.', + 'You cannot create tasks in this column.' => 'Bu kolonda görev oluşturamazsınız.', + 'Task creation is permitted for this column' => 'Bu kolonda görev oluşturulabilir.', + 'Closing or opening a task is permitted for this column' => 'Bu kolonda görev açma yahut kapatma yapılabilir.', + 'Task creation is blocked for this column' => 'Bu kolonda görev oluşturma bloke edilmiştir.', + 'Closing or opening a task is blocked for this column' => 'Bu kolonda görev açma yahut kapatma bloke edilmiştir.', + 'Task creation is not permitted' => 'Görev oluşturmaya izin verilmemiştir', + 'Closing or opening a task is not permitted' => 'Görev açmaya yahut kapatmaya izin verilmemiştir', + 'New drag and drop restriction for the role "%s"' => '"%s" rolü için yeni sürükle bırak kısıtlaması', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Bu roldeki kullanıcılar sadece kaynak ve hedef kolonlar arasında görevi hareket ettirebilir.', + 'Remove a column restriction' => 'Kolon kısıtlamasını kaldır', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Kolon kısıtlamasının "%s" den "%s" e değiştireceğinize emin misiniz?', + 'New column restriction for the role "%s"' => '"%s" rolü için yeni kolon kısıtlaması', + 'Rule' => 'Kural', + 'Do you really want to remove this column restriction?' => 'Kolon kısıtlamasını kaldırmak istediğinize emin misiniz?', + 'Custom roles' => 'Ek roller', + 'New custom project role' => 'Yeni ek proje rolü', + 'Edit custom project role' => 'Ek proje rolünü güncelle', + 'Remove a custom role' => 'Ek rolü kaldır', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => '"%s" ek rolünü kaldıracağınıza emin misiniz? Bu role ait tüm kullanıcılar proje kullanıcısı olacak.', + 'There is no custom role for this project.' => 'Bu proje için ek rol bulunmuyor.', + 'New project restriction for the role "%s"' => '"%s" rolü için yeni proje kısıtlaması', + 'Restriction' => 'Kısıtlama', + 'Remove a project restriction' => 'Proje kısıtlaması kaldır', + 'Do you really want to remove this project restriction: "%s"?' => '"%s" proje kısıtlamasını kaldırmak istediğinize emin misiniz?', + 'Duplicate to multiple projects' => 'Birden çok projeye çokla', + 'This field is required' => 'Bu alan gerekli', + 'Moving a task is not permitted' => 'Görev taşımaya izin verilmemiş', + 'This value must be in the range %d to %d' => 'Bu değer şu aralıkta olmalı: "%d" "%d"', + 'You are not allowed to move this task.' => 'Bu görevi taşımaya izniniz yok.', + 'API User Access' => 'API Kullanıcı Erişimi', + 'Preview' => 'Öngörünüm', + 'Write' => 'Yaz', + 'Write your text in Markdown' => 'Metninizi Markdown a yazın', + 'No personal API access token registered.' => 'Kişisel API erişim anahtarınız kaydedilmedi.', + 'Your personal API access token is "%s"' => 'Kişisel API erişim anahtarınız "%s"', + 'Remove your token' => 'Anahtarı kaldır', + 'Generate a new token' => 'Yeni bir anahtar oluştur', + 'Showing %d-%d of %d' => 'Gösterilen %d-%d / %d', + 'Outgoing Emails' => 'Gönderilen ePostalar', + 'Add or change currency rate' => 'Kur Oranını ekle veya değiştir', + 'Reference currency: %s' => 'Referans parabirimi: %s', + 'Add custom filters' => 'Özel filtre ekle', + 'Export' => 'Dışa aktar', + 'Add link label' => 'Bağlantı etiketi ekle', + 'Incompatible Plugins' => 'Uyumsuz Eklentiler', + 'Compatibility' => 'Uyumluluk', + 'Permissions and ownership' => 'İzinler ve sahiplik', + 'Priorities' => 'Öncelikler', + 'Close this window' => 'Bu pencereyi kapat', + 'Unable to upload this file.' => 'Bu dosya yüklenemiyor', + 'Import tasks' => 'Görevleri içeri aktar', + 'Choose a project' => 'Proje seçin', + 'Profile' => 'Profil', + 'Application role' => 'Uygulama rolü', + '%d invitations were sent.' => '%d Davetiye gönderildi', + '%d invitation was sent.' => '%d Davetiye gönderildi', + 'Unable to create this user.' => 'Bu kullanıcı oluşturulamadı.', + 'Kanboard Invitation' => 'Kanboard Daveti', + 'Visible on dashboard' => 'Panoda görünür', + 'Created at:' => 'Oluşturulma: ', + 'Updated at:' => 'Güncellenme: ', + 'There is no custom filter.' => 'Uygulanmış özel filtre yok', + 'New User' => 'Yeni Kullanıcı', + 'Authentication' => 'Yetkilendirme', + 'If checked, this user will use a third-party system for authentication.' => 'İşaretlenirse, bu kullanıcı kimlik doğrulama için üçüncü parti bir sistem kullanacaktır.', + 'The password is necessary only for local users.' => 'Parola yalnızca yerel kullanıcılar için gereklidir', + 'You have been invited to register on Kanboard.' => 'Kanboarda kayıt yaptırmak için davet edildiniz.', + 'Click here to join your team' => 'Takımınıza katılmak için buraya tıklayın', + 'Invite people' => 'İnsanları davet et', + 'Emails' => 'ePostalar', + 'Enter one email address by line.' => 'Her satıra bir adet email girin.', + 'Add these people to this project' => 'Kişileri bu projeye ekle', + 'Add this person to this project' => 'Kişiyi bu projeye ekle', + 'Sign-up' => 'Kayıt', + 'Credentials' => 'Kimlik bilgileri', + 'New user' => 'Yeni kullanıcı', + 'This username is already taken' => 'Bu kullanıcı adı zaten alınmış', + 'Your profile must have a valid email address.' => 'Profiliniz için geçerli bir ePosta adresi gerekiyor.', + 'TRL - Turkish Lira' => 'TRL - Türk Lirası', + 'The project email is optional and could be used by several plugins.' => 'Proje ePostası isteğe bağlıdır ve eklentiler tarafından kullanılabilir', + 'The project email must be unique across all projects' => 'Proje e-postası tüm projelerde benzersiz olmalıdır.', + 'The email configuration has been disabled by the administrator.' => 'ePosta ayarları yönetici tarafından devre dışı bırakılmış.', + 'Close this project' => 'Bu projeyi kapat', + 'Open this project' => 'Bu projeyi aç', + 'Close a project' => 'Proje kapat', + 'Do you really want to close this project: "%s"?' => 'Bu projeyi kapatmak istediğinize emin misiniz? : "%s"', + 'Reopen a project' => 'Projeyi tekrar aç', + 'Do you really want to reopen this project: "%s"?' => 'Bu projeyi tekrar açmak istediğinizi onaylıyor musunuz? : "%s"', + 'This project is open' => 'Bu proje açıktır', + 'This project is closed' => 'Bu proje kapatılmış', + 'Unable to upload files, check the permissions of your data folder.' => 'Dosya yüklenemedi. Lütfen yükleme dizine yazma izninizi kontrol ediniz.', + 'Another category with the same name exists in this project' => 'Bu projede aynı ada sahip başka bir kategori var', + 'Comment sent by email successfully.' => 'Yorum ePosta ile başarıyla gönderildi.', + 'Sent by email to "%s" (%s)' => 'ePostayı gönder "%s" (%s)', + 'Unable to read uploaded file.' => 'Yüklenen dosya okunamadı.', + 'Database uploaded successfully.' => 'Veritabanı başarıyla yüklendi.', + 'Task sent by email successfully.' => 'Görev ePosta ile başarıyla gönderildi.', + 'There is no category in this project.' => 'Bu projenin kategorisi yok', + 'Send by email' => 'ePosta ile gönder', + 'Create and send a comment by email' => 'Yorum oluştur ve ePosta ile gönder', + 'Subject' => 'Konu', + 'Upload the database' => 'Veritabanına yükle', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Daha önce indirdiğiniz Sqlite Veritabanı dosyasını yükleyebilirsiniz (Gzip formatında)', + 'Database file' => 'Veritabanı dosyası', + 'Upload' => 'Yükle', + 'Your project must have at least one active swimlane.' => 'Projenizin en az bir aktif kulvara sahip olması gerekir', + 'Project: %s' => 'Proje: %s', + 'Automatic action not found: "%s"' => 'Otomatik işlem bulunamadı: "%s"', + '%d projects' => '%d proje', + '%d project' => '%d proje', + 'There is no project.' => 'Proje yok.', + 'Sort' => 'Sırala', + 'Project ID' => 'Proje No', + 'Project name' => 'Proje Adı', + 'Public' => 'Herkese açık', + 'Personal' => 'Gizli', + '%d tasks' => '%d görev', + '%d task' => '%d görev', + 'Task ID' => 'Görev No', + 'Assign automatically a color when due date is expired' => 'Son günü geçtiğinde otomatik olarak bir renk ata', + 'Total score in this column across all swimlanes' => 'Tüm kulvarlara göre bu kulvarın toplam puanı', + 'HRK - Kuna' => 'HRK - Kuna', + 'ARS - Argentine Peso' => 'ARS - Arjantin Pezosu', + 'COP - Colombian Peso' => 'COP - Kolombiya Pezosu', + '%d groups' => '%d grup', + '%d group' => '%d grup', + 'Group ID' => 'Grup No', + 'External ID' => 'Harici No', + '%d users' => '%d kullanıcı', + '%d user' => '%d kullanıcı', + 'Hide subtasks' => 'Alt görevleri gizler', + 'Show subtasks' => 'Alt görevleri göster', + 'Authentication Parameters' => 'Yetkilendirme Parametreleri', + 'API Access' => 'API Erişimi', + 'No users found.' => 'Kullanıcı yok', + 'User ID' => 'Kullanıcı No', + 'Notifications are activated' => 'Bildirimler etkinleştirildi', + 'Notifications are disabled' => 'Bildirimler devre dışı', + 'User disabled' => 'Kullanıcı devre dışı', + '%d notifications' => '%d bildirim', + '%d notification' => '%d bildirim', + 'There is no external integration installed.' => 'Yüklenmiş harici entegrasyon yok.', + 'You are not allowed to update tasks assigned to someone else.' => 'Başkasına atanmış görevi güncelleme izniniz yok.', + 'You are not allowed to change the assignee.' => 'Atanmış kişiyi değiştirme yetkiniz yok.', + 'Task suppression is not permitted' => 'Görev silmeye izniniz yok', + 'Changing assignee is not permitted' => 'Atanmışı değiştirme izniniz yok', + 'Update only assigned tasks is permitted' => 'Yalnızca atandığınız görevleri güncelleyebilirsiniz', + 'Only for tasks assigned to the current user' => 'Yalnızca geçerli kullanıcıya atanan görevler için', + 'My projects' => 'Projelerim', + 'You are not a member of any project.' => 'Herhangi bir projenin üyesi değilsiniz.', + 'My subtasks' => 'Altgörevlerim', + '%d subtasks' => '%d alt görev', + '%d subtask' => '%d alt görev', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Geçerli kullanıcıya atanan görevler için yalnızca o sütunlar arasında hareket eden bir göreve izin verilir.', + '[DUPLICATE]' => '[ÇİFT]', + 'DKK - Danish Krona' => 'DKK - Danimarka Kronu', + 'Remove user from group' => 'Kullanıcıyı gruptan çıkar', + 'Assign the task to its creator' => 'Oluşturucusuna görev ata', + 'This task was sent by email to "%s" with subject "%s".' => 'Bu görev "%s" konusuyla "%s" ePosta adresine gönderildi.', + 'Predefined Email Subjects' => 'Öntanımlı ePosta konuları', + 'Write one subject by line.' => 'Her satıra bir konu yazınız', + 'Create another link' => 'Başka bağlantı oluştur', + 'BRL - Brazilian Real' => 'BRL - Brezilya Reali', + 'Add a new Kanboard task' => 'Yeni bir Kanboard Görevi ekle', + 'Subtask not started' => 'Alt Görev başlatılmamış', + 'Subtask currently in progress' => 'Alt Görev işlemde', + 'Subtask completed' => 'Alt Görev tamamlandı', + 'Subtask added successfully.' => 'Alt Görev başarıyla eklendi.', + '%d subtasks added successfully.' => '%d Alt Görev başarıyla eklendi.', + 'Enter one subtask by line.' => 'Her satıra bir alt görev giriniz.', + 'Predefined Contents' => 'Ön tanımlı İçerik', + 'Predefined contents' => 'Ön tanımlı içerik', + 'Predefined Task Description' => 'Ön tanımlı Görev Açıklaması', + 'Do you really want to remove this template? "%s"' => 'Bu şablonu kaldırmayı gerçekten istiyor musunuz? "%s"', + 'Add predefined task description' => 'Ön tanımlı görev açıklaması ekle', + 'Predefined Task Descriptions' => 'Ön Tanımlı Görev Açıklamaları', + 'Template created successfully.' => 'Şablon başarıyla oluşturuldu.', + 'Unable to create this template.' => 'Şablon oluşturulamadı.', + 'Template updated successfully.' => 'Şablon başarıyla güncellendi.', + 'Unable to update this template.' => 'Şablon güncellenemedi.', + 'Template removed successfully.' => 'Şablon başarıyla kaldırıldı.', + 'Unable to remove this template.' => 'Şablon kaldırılamıyor.', + 'Template for the task description' => 'Görev Açıklaması için Şablon', + 'The start date is greater than the end date' => 'Başlama Tarihi Bitiş Tarihinden büyük', + 'Tags must be separated by a comma' => 'Etiketler virgül ile ayrılmalı', + 'Only the task title is required' => 'Sadece görev başlığı gereklidir', + 'Creator Username' => 'Oluşturan Kullanıcı', + 'Color Name' => 'Renk Adı', + 'Column Name' => 'Kolon Adı', + 'Swimlane Name' => 'Kulvar Adı', + 'Time Estimated' => 'Tahmini Zaman', + 'Time Spent' => 'Harcanan Zaman', + 'External Link' => 'Harici Bağlantı', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Bu özellik iCal beslemesini, RSS beslemesini ve genel pano görünümünü aktifleştirir.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Görev başka bir kolona taşındığında, tüm alt görevlerin zaman sayaçlarını durdur.', + 'Subtask Title' => 'Alt Görev Başlığı', + 'Add a subtask and activate the timer when moving a task to another column' => 'Görev başka bir kolona taşındığında, bir alt görev ekle ve zaman sayacını başlat', + 'days' => 'gün', + 'minutes' => 'dakika', + 'seconds' => 'saniye', + 'Assign automatically a color when preset start date is reached' => 'Başlangıç Tarihi geldiğinde, otomatik olarak bir renk ata', + 'Move the task to another column once a predefined start date is reached' => 'Başlangıç Tarihi geldiğinde, görevi başka bir kolona taşı', + 'This task is now linked to the task %s with the relation "%s"' => 'Bu görev %s görevine bağlandı, ilişki %s', + 'The link with the relation "%s" to the task %s has been removed' => '%s görevindeki %s bağlantısı kaldırıldı', + 'Custom Filter:' => 'Özel Filtre', + 'Unable to find this group.' => 'Bu grup bulunamıyor', + '%s moved the task #%d to the column "%s"' => '%s , #%d görevini "%s" kolonuna taşıdı', + '%s moved the task #%d to the position %d in the column "%s"' => '%s , #%d görevini "%s" kolonundaki %d pozisyonuna taşıdı', + '%s moved the task #%d to the swimlane "%s"' => '%s , #%d görevini "%s" kulvarına taşıdı', + '%sh spent' => '%sh harcandı', + '%sh estimated' => '%sh tahmin edildi', + 'Select All' => 'Tümünü Seç', + 'Unselect All' => 'Tüm Seçimi Kaldır', + 'Apply action' => 'Komutu Uygula', + 'Move selected tasks to another column or swimlane' => 'Seçili görevleri başka kolona taşı', + 'Edit tasks in bulk' => 'Görevleri toplu olarak güncelle', + 'Choose the properties that you would like to change for the selected tasks.' => 'Seçili görevler için değiştirilecek özellikleri seç', + 'Configure this project' => 'Proje Ayarları', + 'Start now' => 'Şimdi başla', + '%s removed a file from the task #%d' => '%s %d görevinden bir dosya çıkardı', + 'Attachment removed from task #%d: %s' => '%s görevinden bir ek dosya çıkarıldı: %d', + 'No color' => 'Renksiz', + 'Attachment removed "%s"' => 'Ek dosya kaldırıldız"%s"', + '%s removed a file from the task %s' => '%s %s görevinden bir dosya çıkardı', + 'Move the task to another swimlane when assigned to a user' => 'Görev birine atandığında bir kulvara taşı', + 'Destination swimlane' => 'Hedef Kulvar', + 'Assign a category when the task is moved to a specific swimlane' => 'Görev bir kulvara taşındığında bir kategori ata', + 'Move the task to another swimlane when the category is changed' => 'Katgorisi değiştiğinde görevi başka bir kulvara taşı', + 'Reorder this column by priority (ASC)' => 'Kolonu artan önceliğe göre sırala', + 'Reorder this column by priority (DESC)' => 'Kolonu azalan önceliğe göre sırala', + 'Reorder this column by assignee and priority (ASC)' => 'Kolonu atanan ve önceliğe göre artan sırala', + 'Reorder this column by assignee and priority (DESC)' => 'Kolonu atanan ve önceliğe göre azalan sırala', + 'Reorder this column by assignee (A-Z)' => 'Kolonu atanana göre artan sırala (A-Z)', + 'Reorder this column by assignee (Z-A)' => 'Kolonu atanana göre azalan sırala (Z-A)', + 'Reorder this column by due date (ASC)' => 'Kolonu hedef tarihe göre artan sırala', + 'Reorder this column by due date (DESC)' => 'Kolonu hedef tarihe göre azalan sırala', + 'Reorder this column by id (ASC)' => 'Kolonu id ye göre artan sırala', + 'Reorder this column by id (DESC)' => 'Kolonu id ye göre azalan sırala', + '%s moved the task #%d "%s" to the project "%s"' => '%s görevi taşıdı #%d "%s" "%s" projesine', + 'Task #%d "%s" has been moved to the project "%s"' => 'Görev #%d "%s" tarafından "%s" projesine taşındı', + 'Move the task to another column when the due date is less than a certain number of days' => 'Hedef tarihe belirli bir gün sayısından az kaldığında görevi başka kolona taşı', + 'Automatically update the start date when the task is moved away from a specific column' => 'Görev bir kolondan taşındığında başlangıç tarihini otomatik olarak güncelle', + 'HTTP Client:' => 'HTTP İstemci', + 'Assigned' => 'Atanmış', + 'Task limits apply to each swimlane individually' => 'Görev limitini her kulvara ayrı uygula', + 'Column task limits apply to each swimlane individually' => 'Kolon görev limitleri her kulvara ayrı uygulansın', + 'Column task limits are applied to each swimlane individually' => 'Kolon görev limitleri her kulvara ayrı uygulandı', + 'Column task limits are applied across swimlanes' => 'Kolon görev limitleri tüm kulvarlara uygulandı', + 'Task limit: ' => 'Görev limiti', + 'Change to global tag' => 'Global etikete değiştir', + 'Do you really want to make the tag "%s" global?' => '"%s" etiketini global yapmak istediğinize emin misiniz?', + 'Enable global tags for this project' => 'Bu iş grubu için global etiketleri etkinleştir', + 'Group membership(s):' => 'Grup üyelikleri', + '%s is a member of the following group(s): %s' => '"%s" şu grupların üyesidir:"%s"', + '%d/%d group(s) shown' => '%d/%dgrupları göster', + 'Subtask creation or modification' => 'Alt görev oluşturma veya değiştirme', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Görev belirli bir kulvara taşındığında belirli bir kullanıcıya ata', + 'Comment' => 'Yorum', + 'Collapse vertically' => 'Dikey olarak daralt', + 'Expand vertically' => 'Dikey olarak genişlet', + 'MXN - Mexican Peso' => 'MXN - Meksika Pesosu', + 'Estimated vs actual time per column' => 'Sütun başına tahmini ve gerçek zaman', + 'HUF - Hungarian Forint' => 'HUF - Macar Forinti', + 'XBT - Bitcoin' => 'XBT - Bitcoin', + 'You must select a file to upload as your avatar!' => 'Avatarınız olarak yüklemek için bir dosya seçmelisiniz!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'Yüklediğiniz dosya geçerli bir resim değil! (Yalnızca *.gif, *.jpg, *.jpeg ve *.png dosyalarına izin verilir!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Görev belirli bir sütundan taşındığında son tarihi otomatik olarak ayarla', + 'No other projects found.' => 'Başka proje bulunamadı.', + 'Tasks copied successfully.' => 'Görevler başarıyla kopyalandı.', + 'Unable to copy tasks.' => 'Görevler kopyalanamıyor.', + 'Theme' => 'Tema', + 'Theme:' => 'Tema:', + 'Light theme' => 'Açık tema', + 'Dark theme' => 'Koyu tema', + 'Automatic theme - Sync with system' => 'Otomatik tema - Sistemle senkronize et', + 'Application managers or more' => 'Uygulama yöneticileri veya daha fazlası', + 'Administrators' => 'Yöneticiler', + 'Visibility:' => 'Görünürlük:', + 'Standard users' => 'Standart kullanıcılar', + 'Visibility is required' => 'Görünürlük gereklidir', + 'The visibility should be an app role' => 'Görünürlük bir uygulama rolü olmalıdır', + 'Reply' => 'Yanıtla', + '%s wrote: ' => '%s yazdı: ', + 'Number of visible tasks in this column and swimlane' => 'Bu sütun ve yüzme yolunda görünen görev sayısı', + 'Number of tasks in this swimlane' => 'Bu yüzme yolundaki görev sayısı', + 'Unable to find another subtask in progress, you can close this window.' => 'Devam eden başka bir alt görev bulunamadı, bu pencereyi kapatabilirsiniz.', + 'This theme is invalid' => 'Bu tema geçersiz', + 'This role is invalid' => 'Bu rol geçersiz', + 'This timezone is invalid' => 'Bu saat dilimi geçersiz', + 'This language is invalid' => 'Bu dil geçersiz', + 'This URL is invalid' => 'Bu URL geçersiz', + 'Date format invalid' => 'Tarih biçimi geçersiz', + 'Time format invalid' => 'Saat biçimi geçersiz', + 'Invalid Mail transport' => 'Geçersiz posta aktarımı', + 'Color invalid' => 'Geçersiz renk', + 'This value must be greater or equal to %d' => 'Bu değer %d veya daha büyük olmalıdır', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Dosyanın başına BOM ekle (Microsoft Excel için gereklidir)', + 'Just add these tag(s)' => 'Sadece bu etiketleri ekle', + 'Remove internal link(s)' => 'Dahili bağlantıları kaldır', + 'Import tasks from another project' => 'Başka bir projeden görevleri içe aktar', + 'Select the project to copy tasks from' => 'Görevleri kopyalamak için projeyi seçin', + 'The total maximum allowed attachments size is %sB.' => 'Ekler için izin verilen toplam maksimum boyut %sB.', + 'Add attachments' => 'Ekleri ekle', + 'Task #%d "%s" is overdue' => 'Görev #%d "%s" son tarihi geçti', + 'Enable notifications by default for all new users' => 'Tüm yeni kullanıcılar için bildirimleri varsayılan olarak etkinleştir', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Sorumlu elle ayarlanmadıysa, belirli sütunlar için görevi oluşturan kişiye ata', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Hiç kullanıcı atanmadıysa, sütun değişikliğinde görev belirtilen sütuna taşındığında görevi oturum açmış kullanıcıya ata', +]; diff --git a/app/Locale/uk_UA/translations.php b/app/Locale/uk_UA/translations.php new file mode 100644 index 0000000..c0c691e --- /dev/null +++ b/app/Locale/uk_UA/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => ' ', + 'None' => 'Відсутній', + 'Edit' => 'Редагувати', + 'Remove' => 'Видалити', + 'Yes' => 'Так', + 'No' => 'Ні', + 'cancel' => 'скасувати', + 'or' => 'або', + 'Yellow' => 'Жовтий', + 'Blue' => 'Синій', + 'Green' => 'Зелений', + 'Purple' => 'Фіолетовий', + 'Red' => 'Червоний', + 'Orange' => 'Помаранчевий', + 'Grey' => 'Сірий', + 'Brown' => 'Коричневий', + 'Deep Orange' => 'Темно-помаранчевий', + 'Dark Grey' => 'Темно-сірий', + 'Pink' => 'Рожевий', + 'Teal' => 'Бірюзовий', + 'Cyan' => 'Синьо-зелений', + 'Lime' => 'Лайм', + 'Light Green' => 'Світло-зелений', + 'Amber' => 'Бурштиновий', + 'Save' => 'Зберегти', + 'Login' => 'Увійти', + 'Official website:' => 'Офіційний веб-сайт:', + 'Unassigned' => 'Непризначена', + 'View this task' => 'Переглянути задачу', + 'Remove user' => 'Видалити користувача', + 'Do you really want to remove this user: "%s"?' => 'Дійсно видалити користувача "%s"?', + 'All users' => 'Всі користувачі', + 'Username' => 'Логін', + 'Password' => 'Пароль', + 'Administrator' => 'Адміністратор', + 'Sign in' => 'Увійти', + 'Users' => 'Користувачі', + 'Forbidden' => 'Заборонено', + 'Access Forbidden' => 'Відмовлено у доступі', + 'Edit user' => 'Редагувати користувача', + 'Logout' => 'Вийти', + 'Bad username or password' => 'Неправильний логін або пароль', + 'Edit project' => 'Редагувати проєкт', + 'Name' => 'Ім\'я', + 'Projects' => 'Проєкти', + 'No project' => 'Проєкти відсутні', + 'Project' => 'Проєкт', + 'Status' => 'Статус', + 'Tasks' => 'Задачі', + 'Board' => 'Дошка', + 'Actions' => 'Дії', + 'Inactive' => 'Неактивний', + 'Active' => 'Активний', + 'Unable to update this board.' => 'Не вдалося оновити дошку.', + 'Disable' => 'Заблокувати', + 'Enable' => 'Розблокувати', + 'New project' => 'Новий проєкт', + 'Do you really want to remove this project: "%s"?' => 'Дійсно видалити проєкт "%s"?', + 'Remove project' => 'Видалити проєкт', + 'Edit the board for "%s"' => 'Редагувати дошку проєкту "%s"', + 'Add a new column' => 'Додати нову колонку', + 'Title' => 'Заголовок', + 'Assigned to %s' => 'Доручена %s', + 'Remove a column' => 'Видалити колонку', + 'Unable to remove this column.' => 'Не вдалося видалити колонку.', + 'Do you really want to remove this column: "%s"?' => 'Дійсно видалити колонку "%s"?', + 'Settings' => 'Налаштування', + 'Application settings' => 'Параметри додатку', + 'Language' => 'Мова', + 'Webhook token:' => 'Ключ доступу webhook:', + 'API token:' => 'Ключ доступу API:', + 'Database size:' => 'Розмір бази даних:', + 'Download the database' => 'Завантажити базу даних', + 'Optimize the database' => 'Оптимізувати базу даних', + '(VACUUM command)' => '(Команда VACUUM)', + '(Gzip compressed Sqlite file)' => '(Файл SQLite стиснутий Gzip)', + 'Close a task' => 'Закрити задачу', + 'Column' => 'Колонка', + 'Color' => 'Колір', + 'Assignee' => 'Доручено', + 'Create another task' => 'Створити ще задачу', + 'New task' => 'Нова задача', + 'Open a task' => 'Відкрити задачу', + 'Do you really want to open this task: "%s"?' => 'Дійсно відкрити "%s"?', + 'Back to the board' => 'Назад до дошки', + 'There is nobody assigned' => 'Не доручена нікому', + 'Column on the board:' => 'Колонка на дошці:', + 'Close this task' => 'Закрити цю задачу', + 'Open this task' => 'Відкрити цю задачу', + 'There is no description.' => 'Опис відсутній.', + 'Add a new task' => 'Додати нову задачу', + 'The username is required' => 'Логін є обов\'язковим', + 'The maximum length is %d characters' => 'Максимальна довжина – %d символів', + 'The minimum length is %d characters' => 'Мінімальна довжина – %d символів', + 'The password is required' => 'Пароль є обов\'язковим', + 'This value must be an integer' => 'Значення має бути цілим числом', + 'The username must be unique' => 'Логін має бути унікальним', + 'The user id is required' => 'Ідентифікатор користувача є обов\'язковим', + 'Passwords don\'t match' => 'Паролі не співпадають', + 'The confirmation is required' => 'Необхідно підтвердити', + 'The project is required' => 'Слід вказати проєкт', + 'The id is required' => 'Слід вказати ідентифікатор', + 'The project id is required' => 'Слід вказати ідентифікатор проєкту', + 'The project name is required' => 'Слід вказати ім\'я проєкту', + 'The title is required' => 'Слід вказати заголовок', + 'Settings saved successfully.' => 'Налаштування успішно збережено.', + 'Unable to save your settings.' => 'Не вдалося зберегти налаштування.', + 'Database optimization done.' => 'База даних оптимізована.', + 'Your project has been created successfully.' => 'Ваш проєкт успішно створено.', + 'Unable to create your project.' => 'Не вдалося створити проєкт.', + 'Project updated successfully.' => 'Проєкт успішно оновлено.', + 'Unable to update this project.' => 'Не вдалося оновити проєкт.', + 'Unable to remove this project.' => 'Не вдалося видалити проєкт.', + 'Project removed successfully.' => 'Проєкт успішно видалено.', + 'Project activated successfully.' => 'Проєкт успішно активовано.', + 'Unable to activate 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.' => 'Не вдалося оновити задачу.', + 'Task updated successfully.' => 'Задачу успішно оновлено.', + 'Unable to create your task.' => 'Не вдалося створити задачу.', + 'Task created successfully.' => 'Задачу успішно створено.', + 'User created successfully.' => 'Користувача успішно створено.', + 'Unable to create your user.' => 'Не вдалося створити користувача.', + 'User updated successfully.' => 'Користувача успішно оновлено.', + 'User removed successfully.' => 'Користувача успішно видалено.', + 'Unable to remove this user.' => 'Не вдалося видалити користувача.', + 'Board updated successfully.' => 'Дошку успішно оновлено.', + 'Ready' => 'Відібрані', + 'Backlog' => 'Заплановані', + 'Work in progress' => 'В роботі', + 'Done' => 'Завершені', + 'Application version:' => 'Версія додатку:', + 'Id' => 'ID', + 'Public link' => 'Публічне посилання', + 'Timezone' => 'Часовий пояс', + 'Sorry, I didn\'t find this information in my database!' => 'Не вдалося знайти цю інформацію в базі даних :(', + 'Page not found' => 'Сторінку не знайдено', + 'Complexity' => 'Складність', + 'Task limit' => 'Ліміт задач', + 'Task count' => 'Число задач', + 'User' => 'Користувач', + 'Comments' => 'Коментарі', + 'Comment is required' => 'Текст коментаря є обов\'язковим', + 'Comment added successfully.' => 'Коментар успішно додано.', + 'Unable to create your comment.' => 'Не вдалося створити коментар.', + 'Due Date' => 'Термін виконання', + 'Invalid date' => 'Неправильна дата', + 'Automatic actions' => 'Автоматичні дії', + 'Your automatic action has been created successfully.' => 'Автоматичну дію успішно створено.', + 'Unable to create your automatic action.' => 'Не вдалося створити автоматичну дію.', + 'Remove an action' => 'Видалити дію', + 'Unable to remove this action.' => 'Не вдалося видалити дію.', + 'Action removed successfully.' => 'Дію успішно видалено.', + 'Automatic actions for the project "%s"' => 'Автоматичні дії для проєкту "%s"', + 'Add an action' => 'Додати дію', + 'Event name' => 'Назва події', + 'Action' => 'Дія', + 'Event' => 'Подія', + 'When the selected event occurs execute the corresponding action.' => 'Виконати вказану дію при настанні обнаної події.', + 'Next step' => 'Наступний крок', + 'Define action parameters' => 'Вкажіть параметри події', + 'Do you really want to remove this action: "%s"?' => 'Дійсно видалити дію "%s"?', + 'Remove an automatic action' => 'Видалення автоматичної дії', + 'Assign the task to a specific user' => 'Призначити виконавцем задачі певного користувача', + 'Assign the task to the person who does the action' => 'Призначити виконавцем задачі користувача, що виконав дію', + 'Duplicate the task to another project' => 'Додати дублікат задачі до іншого проєкту', + 'Move a task to another column' => 'Перемістити задачу до іншої колонки', + 'Task modification' => 'Внесення змін до задачі', + 'Task creation' => 'Створення задачі', + 'Closing a task' => 'Закриття задачі', + 'Assign a color to a specific user' => 'Призначити колір певному користувачу', + 'Position' => 'Позиція', + 'Duplicate to project' => 'Дублювати до іншого проєкту', + 'Duplicate' => 'Дублювати', + 'Link' => 'Посилання', + 'Comment updated successfully.' => 'Комент успішно оновлено.', + 'Unable to update your comment.' => 'Не вдалося оновити коментар.', + 'Remove a comment' => 'Видалити коментар', + 'Comment removed successfully.' => 'Коментар успішно видалено.', + 'Unable to remove this comment.' => 'Не вдалося видалити коментар.', + 'Do you really want to remove this comment?' => 'Дійсно видалити цей коментар?', + 'Current password for the user "%s"' => 'Поточний пароль користувача "%s"', + 'The current password is required' => 'Слід вказати поточний пароль', + 'Wrong password' => 'Пароль неправильний', + 'Unknown' => 'Невідомо', + 'Last logins' => 'Останні підключення', + 'Login date' => 'Дата входу', + 'Authentication method' => 'Спосіб автентифікації', + 'IP address' => 'IP-адреса', + 'User agent' => 'Клієнт', + 'Persistent connections' => 'Постійні з\'єднання', + 'No session.' => 'З\'єднання відсутні.', + 'Expiration date' => 'Дійсне до', + 'Remember Me' => 'Запам\'ятати мене', + 'Creation date' => 'Дата створення', + 'Everybody' => 'Всі', + 'Open' => 'Відкритий', + 'Closed' => 'Закритий', + 'Search' => 'Пошук', + 'Nothing found.' => 'Нічого не знайдено.', + 'Due date' => 'Термін виконання', + 'Description' => 'Опис', + '%d comments' => '%d коментарів', + '%d comment' => '%d коментар', + 'Email address invalid' => 'Неправильна адреса e-mail', + 'Your external account is not linked anymore to your profile.' => 'Зовнішній обліковий запис більше не пов\'язаний з вашим профілем.', + 'Unable to unlink your external account.' => 'Не вдалося вилучити зв\'язок із зовнішнім обліковим записом.', + 'External authentication failed' => 'Помилка зовнішньої аутентифікації', + 'Your external account is linked to your profile successfully.' => 'Профіль успішно зв\'язано із зовнішнім обліковим записом.', + 'Email' => 'E-mail', + 'Task removed successfully.' => 'Задачу успішно видалено.', + 'Unable to remove this task.' => 'Не вдалося видалити задачу.', + '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' => 'Автоматично призначити категорію за кольором', + 'Task creation or modification' => 'Створення або зміна задачі', + 'Category' => 'Категорія', + 'Category:' => 'Категорія:', + 'Categories' => 'Категорії', + 'Your category has been created successfully.' => 'Категорію успішно створено.', + 'This category has been updated successfully.' => 'Категорію успішно оновлено.', + 'Unable to update this category.' => 'Не вдалося оновити категорію.', + 'Remove a category' => 'Видалити категорію', + 'Category removed successfully.' => 'Категорію успішно видалено.', + 'Unable to remove this category.' => 'Не вдалося видалити категорію.', + 'Category modification for the project "%s"' => 'Редагування категорії з проєкту "%s"', + 'Category Name' => 'Назва категорії', + 'Add a new category' => 'Додати категорію', + 'Do you really want to remove this category: "%s"?' => 'Дійсно видалити категорію "%s"?', + 'All categories' => 'Всі категорії', + 'No category' => 'Без категорії', + 'The name is required' => 'Слід вказати назву', + 'Remove a file' => 'Видалити файл', + 'Unable to remove this file.' => 'Не вдалося видалити файл.', + 'File removed successfully.' => 'Файл успішно видалено.', + 'Attach a document' => 'Прикріпити файл', + 'Do you really want to remove this file: "%s"?' => 'Дійсно видалити файл "%s"?', + 'Attachments' => 'Прикріплені файли', + 'Edit the task' => 'Редагувати задачу', + 'Add a comment' => 'Додати коментар', + 'Edit a comment' => 'Редагувати коментар', + 'Summary' => 'Відомості', + 'Time tracking' => 'Облік часу', + 'Estimate:' => 'Оцінка:', + 'Spent:' => 'Витрачено:', + 'Do you really want to remove this sub-task?' => 'Дійсно видалити цю підзадачу?', + 'Remaining:' => 'Залишилося:', + 'hours' => 'годин', + 'estimated' => 'оцінено', + 'Sub-Tasks' => 'Підзадачі', + 'Add a sub-task' => 'Додати підзадачу', + 'Original estimate' => 'Початкова оцінка', + 'Create another sub-task' => 'Створити ще одну підзадачу', + 'Time spent' => 'Витрачений час', + 'Edit a sub-task' => 'Редагувати відзадачу', + 'Remove a sub-task' => 'Видалити підзадачу', + 'The time must be a numeric value' => 'Час має бути числовим значенням', + 'Todo' => 'Зробити', + 'In progress' => 'Виконується', + 'Sub-task removed successfully.' => 'Підзадачу успішно видалено.', + 'Unable to remove this sub-task.' => 'Не вдалося видалити підзадачу.', + 'Sub-task updated successfully.' => 'Підзадачу успішно оновлено.', + 'Unable to update your sub-task.' => 'Не вдалося оновити підзадачу.', + 'Unable to create your sub-task.' => 'Не вдалося створити підзадачу.', + 'Maximum size: ' => 'Максимальний розмір: ', + 'Display another project' => 'Переглянути інший проєкт', + 'Created by %s' => 'Створена %s', + 'Tasks Export' => 'Експорт задач', + 'Start Date' => 'Дата початку', + 'Execute' => 'Виконати', + 'Task Id' => 'ID задачі', + 'Creator' => 'Автор', + 'Modification date' => 'Дата зміни', + 'Completion date' => 'Дата завершення', + 'Clone' => 'Копія', + 'Project cloned successfully.' => 'Проєкт успішно склоновано.', + 'Unable to clone this project.' => 'Не вдалося склонувати проєкт.', + 'Enable email notifications' => 'Увімкнути сповіщення по e-mail', + 'Task position:' => 'Позиція задачі:', + 'The task #%d has been opened.' => 'Відкрито задачу #%d.', + 'The task #%d has been closed.' => 'Закрито задачу #%d.', + 'Sub-task updated' => 'Підзадачу оновлено', + 'Title:' => 'Заголовок:', + 'Status:' => 'Статус:', + 'Assignee:' => 'Відповідальний:', + 'Time tracking:' => 'Облік часу:', + 'New sub-task' => 'Нова підзадача', + 'New attachment added "%s"' => 'Прикріплено "%s"', + 'New comment posted by %s' => 'Новий коментар від "%s"', + 'New comment' => 'Новий коментар', + 'Comment updated' => 'Коментар оновлено', + 'New subtask' => 'Нова підзадача', + 'I only want to receive notifications for these projects:' => 'Отримувати повідомлення лише для проєктів:', + 'view the task on Kanboard' => 'переглянути задачу в Kanboard', + 'Public access' => 'Публічний доступ', + 'Disable public access' => 'Вимкнути публічний доступ', + 'Enable public access' => 'Увімкнути публічний доступ', + 'Public access disabled' => 'Публічний доступ вимкнуто', + 'Move the task to another project' => 'Перемістити задачу до іншого проєкту', + 'Move to project' => 'Перемістити до іншого проєкту', + 'Do you really want to duplicate this task?' => 'Ви дійсно бажаєте створити копію цієї задачі?', + 'Duplicate a task' => 'Дублювати задачу', + 'External accounts' => 'Зовнішні облікові записи', + 'Account type' => 'Тип облікового запису', + 'Local' => 'Локальний', + 'Remote' => 'Віддалений', + 'Enabled' => 'Увімкнені', + 'Disabled' => 'Вимкнуті', + 'Login:' => 'Логін:', + 'Full Name:' => 'Повне ім\'я:', + 'Email:' => 'E-mail:', + 'Notifications:' => 'Сповіщення:', + 'Notifications' => 'Сповіщення', + 'Account type:' => 'Тип облікового запису:', + 'Edit profile' => 'Редагувати профіль', + 'Change password' => 'Змінити пароль', + 'Password modification' => 'Зміна пароля', + 'External authentications' => 'Зовнішня автентифікація', + 'Never connected.' => 'Історія підключень відсутня.', + 'No external authentication enabled.' => 'Зовнішня автентифікація відсутня.', + 'Password modified successfully.' => 'Пароль успішно змінено.', + 'Unable to change the password.' => 'Не вдалося змінити пароль.', + 'Change category' => 'Змінити категорію', + '%s updated the task %s' => '%s оновив задачу %s', + '%s opened the task %s' => '%s відкрив задачу %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s перемістив задачу %s на позицію %d в колонці "%s"', + '%s moved the task %s to the column "%s"' => '%s перемістив задачу %s в колонку "%s"', + '%s created the task %s' => '%s створив задачу %s', + '%s closed the task %s' => '%s закрив задачу %s', + '%s created a subtask for the task %s' => '%s створив підзадачу задачі %s', + '%s updated a subtask for the task %s' => '%s оновив підзадачу задачі %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Призначена %s з оцінкою часу %s/%s год', + 'Not assigned, estimate of %sh' => 'Не призначена нікому з оцінкою часу %s год', + '%s updated a comment on the task %s' => '%s оновив коментар до задачі %s', + '%s commented the task %s' => '%s додав коментар до задачі %s', + '%s\'s activity' => 'Діяльність в проєкті %s', + 'RSS feed' => 'Стрічка RSS', + '%s updated a comment 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', + '%s created the task #%d' => '%s створив задачу #%d', + '%s closed the task #%d' => '%s закрив задачу #%d', + '%s opened the task #%d' => '%s відкрив задачу #%d', + 'Activity' => 'Діяльність', + 'Default values are "%s"' => 'Стандартними значеннями є "%s"', + 'Default columns for new projects (Comma-separated)' => 'Стандартні колонки для нових проєктів (розділені комами)', + 'Task assignee change' => 'Зміна відповідального за задачу', + '%s changed the assignee of the task #%d to %s' => '%s змінив відповідального за задачу #%d на %s', + '%s changed the assignee of the task %s to %s' => '%s змінив відповідального за задачу %s на %s', + 'New password for the user "%s"' => 'Новий пароль користувача "%s"', + 'Choose an event' => 'Оберіть подію', + 'Create a task from an external provider' => 'Створити задачу із зовнішнього джерела', + 'Change the assignee based on an external username' => 'Змінити відповідального виходячи з зовнішнього логіна', + 'Change the category based on an external label' => 'Змінити категорію на основі мітки зв\'язку', + 'Reference' => 'Зовнішній ID', + 'Label' => 'Мітка', + 'Database' => 'База даних', + 'About' => 'Про додаток', + 'Database driver:' => 'Драйвер бази даних:', + 'Board settings' => 'Налаштування дошки', + 'Webhook settings' => 'Параметри webhook', + 'Reset token' => 'Скинути ключ доступу', + 'API endpoint:' => 'URL API:', + 'Refresh interval for personal board' => 'Інтервал оновлення персональної дошки', + 'Refresh interval for public board' => 'Інтервал оновлення публічної дошки', + 'Task highlight period' => 'Час підсвічування задачі', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Тривалість в секундах, протягом якої задача вважається зміненою нещодавно (0 – вимкнути, стандартно – 2 дні)', + 'Frequency in second (60 seconds by default)' => 'Частота в секундах (стандартно – 60 секунд)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Частота в секундах (0 – вимкнути цю функцію, стандартно – 10 секунд)', + 'Application URL' => 'URL додатку', + 'Token regenerated.' => 'Згенеровано новий ключ доступу.', + 'Date format' => 'Формат дати', + 'ISO format is always accepted, example: "%s" and "%s"' => 'Також підтримується формат ISO: "%s" та "%s"', + 'New personal project' => 'Новий персональний проєкт', + 'This project is personal' => 'Цей проєкт персональний', + 'Add' => 'Додати', + 'Start date' => 'Дата початку', + 'Time estimated' => 'Оцінка часу', + 'There is nothing assigned to you.' => 'Немає призначених вам задач.', + 'My tasks' => 'Мої задачі', + 'Activity stream' => 'Активність', + 'Dashboard' => 'Панель', + 'Confirmation' => 'Підтвердження', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Create a comment from an external provider' => 'Створити коментар із зовнішнього джерела', + 'Project management' => 'Керування проєктами', + 'Columns' => 'Колонки', + 'Task' => 'Задача', + 'Percentage' => 'Процент', + 'Number of tasks' => 'Число задач', + 'Task distribution' => 'Колонки', + 'Analytics' => 'Аналітика', + 'Subtask' => 'Підзадача', + 'User repartition' => 'Відповідальні', + 'Clone this project' => 'Створити клон проєкту', + 'Column removed successfully.' => 'Колонку успішно видалено.', + 'Not enough data to show the graph.' => 'Недостатньо даних для показу графіка.', + 'Previous' => 'Назад', + 'The id must be an integer' => 'ID має бути цілим числом', + 'The project id must be an integer' => 'ID проєкту має бути цілим числом', + 'The status must be an integer' => 'Статус має бути цілим числом', + 'The subtask id is required' => 'ID підзадачі є обов\'язковим', + 'The subtask id must be an integer' => 'ID підзадачі має бути цілим числом', + 'The task id is required' => 'ID задачі є обов\'язковим', + 'The task id must be an integer' => 'ID задачі має бути цілим числом', + 'The user id must be an integer' => 'ID користувача має бути цілим числом', + 'This value is required' => 'Це значення є обов\'язковим', + 'This value must be numeric' => 'Це значення має бути числовим', + 'Unable to create this task.' => 'Не вдалося створити задачу', + 'Cumulative flow diagram' => 'Накопичувальна діаграма', + 'Daily project summary' => 'Щоденний звіт проєкту', + 'Daily project summary export' => 'Експорт щоденного звіту проєкту', + 'Exports' => 'Експорт', + 'This export contains the number of tasks per column grouped per day.' => 'Звіт містить згруповані по дням кількості задач в кожній з колонок.', + 'Active swimlanes' => 'Активні доріжки', + 'Add a new swimlane' => 'Додати доріжку', + 'Default swimlane' => 'Стандартна доріжка', + 'Do you really want to remove this swimlane: "%s"?' => 'Дійсно видалити доріжку "%s"?', + 'Inactive swimlanes' => 'Неактивні доріжки', + 'Remove a swimlane' => 'Видалити доріжку', + 'Swimlane modification for the project "%s"' => 'Редагування доріжки в проєкті "%s"', + 'Swimlane removed successfully.' => 'Доріжку успішно видалено.', + 'Swimlanes' => 'Доріжки', + 'Swimlane updated successfully.' => 'Доріжку успішно оновлено.', + 'Unable to remove this swimlane.' => 'Не вдалося видалити доріжку.', + 'Unable to update this swimlane.' => 'Не вдалося оновити доріжку.', + 'Your swimlane has been created successfully.' => 'Доріжку успішно створено.', + 'Example: "Bug, Feature Request, Improvement"' => 'Наприклад, "Дефект, Нова функція, Вдосконалення"', + 'Default categories for new projects (Comma-separated)' => 'Стандартні категорії для нових проєктів (розділені комами)', + 'Integrations' => 'Інтеграції', + 'Integration with third-party services' => 'Інтеграції із сторонніми сервісами', + 'Subtask Id' => 'ID підзадачі', + 'Subtasks' => 'Підзадачі', + 'Subtasks Export' => 'Експорт підзадач', + 'Task Title' => 'Заголовок задачі', + 'Untitled' => 'Без назви', + 'Application default' => 'Значення за замовчуванням із додатка', + 'Language:' => 'Мова:', + 'Timezone:' => 'Часовий пояс:', + 'All columns' => 'Всі колонки', + 'Next' => 'Далі', + '#%d' => '#%d', + 'All swimlanes' => 'Всі доріжки', + 'All colors' => 'Всі кольори', + 'Moved to column %s' => 'Переміщено до колонки %s', + 'User dashboard' => 'Панель користувача', + 'Allow only one subtask in progress at the same time for a user' => 'Дозволяти кожному користувачу мати лише одну підзадачу в роботі одночасно', + 'Edit column "%s"' => 'Редагування колонки "%s"', + '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?' => 'Який вміст цього проєкту слід скопіювати?', + 'Disallow login form' => 'Вимкнути форму входу', + 'Start' => 'Початок', + 'End' => 'Завершення', + 'Task age in days' => 'Вік задачі в днях', + 'Days in this column' => 'Днів у поточній колонці', + '%dd' => '%d д', + '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' => 'Змінити мітку зв\'язку', + 'Opposite label' => 'Протилежна мітка', + 'Remove a link' => 'Видалити посилання', + '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' => '< 1 год', + '%dh' => '%d год', + 'Expand tasks' => 'Розгорнути задачі', + 'Collapse tasks' => 'Згорнути задачі', + 'Expand/collapse tasks' => 'Згорнути/розгорнути задачі', + 'Close dialog box' => 'Закрити діалогове вікно', + 'Submit a form' => 'Надіслати форму', + 'Board view' => 'Перегляд дошки', + 'Keyboard shortcuts' => 'Комбінації клавіш', + 'Open board switcher' => 'Відкрити перемикач проєкту', + 'Application' => 'Додаток', + 'Compact view' => 'Стислий вигляд', + 'Horizontal scrolling' => 'Горизонтальне гортання', + 'Compact/wide view' => 'Стислий/широкий вигляд', + 'Currency' => 'Валюта', + 'Personal project' => 'Персональний проєкт', + 'AUD - Australian Dollar' => 'AUD – Австралійський долар', + 'CAD - Canadian Dollar' => 'CAD – Канадський долар', + 'CHF - Swiss Francs' => 'CHF – Швейцарський франк', + 'Custom Stylesheet' => 'Додаткові стилі', + 'EUR - Euro' => 'EUR – Євро', + 'GBP - British Pound' => 'GBP – Фунт стерлінгів', + 'INR - Indian Rupee' => 'INR – Індійська рупія', + 'JPY - Japanese Yen' => 'JPY – Японська йєна', + 'NZD - New Zealand Dollar' => 'NZD – Новозеландський долар', + 'PEN - Peruvian Sol' => 'PEN - Перуанський соль', + 'RSD - Serbian dinar' => 'RSD – Сербський динар', + 'CNY - Chinese Yuan' => 'CNY – Китайський юань', + 'USD - US Dollar' => 'USD – Американський долар', + 'VES - Venezuelan Bolívar' => 'VES – Венесуельський болівар', + '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' => 'Вихідна колонка', + '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' => 'Змінити основну валюту', + 'Reference currency' => 'Основна валюта', + 'The currency rate has been added successfully.' => 'Курс валют успішно додано.', + 'Unable to add this currency rate.' => 'Не вдалося додати курс валют.', + 'Webhook URL' => 'Webhook URL', + '%s removed the assignee of the task %s' => '%s зняв відповідального з задачі %s', + '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' => 'Двофакторна автентифікація', + 'This QR code contains the key URI: ' => 'Цей QR-код містить URL ключа: ', + 'Check my code' => 'Перевірити код', + 'Secret key: ' => 'Секретний ключ: ', + 'Test your device' => 'Перевірте ваш пристрій', + 'Assign a color when the task is moved to a specific column' => 'Призначити колір, коли задачу переміщено до певної колонки', + '%s via Kanboard' => '%s через Kanboard', + 'Burndown chart' => 'Діаграма згорання задач', + 'This chart show the task complexity over the time (Work Remaining).' => 'Цей графік показує оцінку часу необхідного для завершення роботи над задачами, що залишилися.', + 'Screenshot taken %s' => 'Знімок екрану зроблено %s', + 'Add a screenshot' => 'Додати знімок екрану', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Зробіть знімок екрану та вставте сюди натиснувши CTRL+V або ⌘+V.', + 'Screenshot uploaded successfully.' => 'Знімок екрану успішно завантажено.', + 'SEK - Swedish Krona' => 'SEK – Шведська крона', + 'Identifier' => 'Ідентифікатор', + 'Disable two factor authentication' => 'Вимкнути двоетапну перевірку', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Ви дійсно бажаєте вимкнути двоетапну перевірку для користувача "%s"?', + 'Edit link' => 'Змінити зв\'язок', + 'Start to type task title...' => 'Почніть друкувати заголовок задачі…', + 'A task cannot be linked to itself' => 'Задачу неможливо зв\'язати саму з собою', + 'The exact same link already exists' => 'Такий зв\'язок вже існує', + 'Recurrent task is scheduled to be generated' => 'Створення повторюваної задачі заплановано', + 'Score' => 'Складність', + 'The identifier must be unique' => 'Ідентифікатор повинен бути унікальним', + 'This linked task id doesn\'t exists' => 'Ідентифікатор пов\'язаної задачі не існує', + 'This value must be alphanumeric' => 'Значення має бути буквено-цифровим', + 'Edit recurrence' => 'Змінити повторення', + 'Generate recurrent task' => 'Створити повторювану задачу', + 'Trigger to generate recurrent task' => 'Подія, що викличе створення повторюваної задачі', + 'Factor to calculate new due date' => 'Коефіцієнт для розрахунку нового терміну виконання', + 'Timeframe to calculate new due date' => 'Проміжок часу для розрахунку нового терміну виконання', + 'Base date to calculate new due date' => 'Базова дата для розрахунку нового терміну виконання', + 'Action date' => 'Дата дії', + 'Base date to calculate new due date: ' => 'Базова дата для розрахунку нового терміну виконання: ', + 'This task has created this child task: ' => 'Ця задача: ', + 'Day(s)' => 'Дні', + 'Existing due date' => 'Наявний термін виконання', + 'Factor to calculate new due date: ' => 'Коефіцієнт для розрахунку нового терміну виконання: ', + 'Month(s)' => 'Місяці', + 'This task has been created by: ' => 'Ця задача створена:', + 'Recurrent task has been generated:' => 'Була створена повторювана задача:', + 'Timeframe to calculate new due date: ' => 'Період часу для розрахунку нового терміну виконання: ', + 'Trigger to generate recurrent task: ' => 'Подія, що викликає створення повторюваної задачі: ', + 'When task is closed' => 'Задачу закрито', + 'When task is moved from first column' => 'Задачу переміщено до першої колонки', + 'When task is moved to last column' => 'Задачу переміщено до останньої колонки', + 'Year(s)' => 'Роки', + 'Project settings' => 'Налаштування проєкту', + 'Automatically update the start date' => 'Автоматично оновити дату початку', + 'iCal feed' => 'Стрічка iCal', + 'Preferences' => 'Налаштування', + 'Security' => 'Безпека', + 'Two factor authentication disabled' => 'Двофакторну автентифікацію вимкнуто', + 'Two factor authentication enabled' => 'Двофакторну автентифікацію увімкнено', + 'Unable to update this user.' => 'Не вдалося оновити цього користувача.', + 'There is no user management for personal projects.' => 'Керування користувачами відсутнє для персональних проєктів.', + 'User that will receive the email' => 'Користувач, який отримає e-mail', + 'Email subject' => 'Тема e-mail', + 'Date' => 'Дата', + 'Add a comment log when moving the task between columns' => 'Додати коментар до задачі при переміщенні між колонками', + 'Move the task to another column when the category is changed' => 'Перемістити задачу до іншої колонки, якщо змінено категорію', + 'Send a task by email to someone' => 'Надіслати задачу на вказаний E-mail', + 'Reopen a task' => 'Відкрити задачу', + 'Notification' => 'Сповіщення', + '%s moved the task #%d to the first swimlane' => '%s перемістив задачу #%d на першу доріжку', + 'Swimlane' => 'Доріжка', + '%s moved the task %s to the first swimlane' => '%s перемістив задачу %s на першу доріжку', + '%s moved the task %s to the swimlane "%s"' => '%s перемістив задачу %s на доріжку "%s"', + 'This report contains all subtasks information for the given date range.' => 'Цей звіт надає інформацію по підзадачам для вказаного проміжку часу.', + 'This report contains all tasks information for the given date range.' => 'Цей звіт надає інформацію по задачам для вказаного проміжку часу.', + 'Project activities for %s' => 'Діяльність в проєкті для "%s"', + 'view the board on Kanboard' => 'переглянути дошку в Kanboard', + 'The task has been moved to the first swimlane' => 'Задачу переміщено на першу доріжку', + 'The task has been moved to another swimlane:' => 'Задачу переміщено на іншу доріжку:', + 'New title: %s' => 'Новий заголовок: %s', + 'The task is not assigned anymore' => 'Задача не має відповідального', + 'New assignee: %s' => 'Новий відповідальний: %s', + 'There is no category now' => 'Категорія відсутня', + 'New category: %s' => 'Нова категорія: %s', + 'New color: %s' => 'Новий колір: %s', + 'New complexity: %d' => 'Нова складність: %d', + 'The due date has been removed' => 'Термін виконання видалено', + 'There is no description anymore' => 'Опис тепер відсутній', + 'Recurrence settings has been modified' => 'Змінено налаштування повторюваності', + 'Time spent changed: %sh' => 'Змінений витрачений час: %s год', + 'Time estimated changed: %sh' => 'Змінено оцінку часу: %s год', + 'The field "%s" has been updated' => 'Змінено поле "%s"', + 'The description has been modified:' => 'Змінений опис:', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Ви дійсно бажаєте закрити задачу "%s" разом із підзадачами?', + 'I want to receive notifications for:' => 'Отримувати сповіщення про:', + 'All tasks' => 'Всі задачі', + 'Only for tasks assigned to me' => 'Задачі призначені мені', + 'Only for tasks created by me' => 'Задачі створені мною', + 'Only for tasks created by me and tasks assigned to me' => 'Задачі створені мною або призначені мені', + '%%Y-%%m-%%d' => '%%d.%%m.%%Y', + 'Total for all columns' => 'Загалом для всіх колонок', + 'You need at least 2 days of data to show the chart.' => 'Для відображення графіка необхідні дані щонайменше за 2 дні.', + '<15m' => '< 15 хв', + '<30m' => '< 30 хв', + 'Stop timer' => 'Зупинити таймер', + 'Start timer' => 'Запустити таймер', + 'My activity stream' => 'Cтрічка подій', + 'Search tasks' => 'Пошук задач', + 'Reset filters' => 'Скинути фільтри', + 'My tasks due tomorrow' => 'Мої задачі з терміном виконання "завтра"', + 'Tasks due today' => 'Задачі з терміном виконання "сьогодні"', + 'Tasks due tomorrow' => 'Задачі з термімном виконання "завтра"', + 'Tasks due yesterday' => 'Задачі з терміном виконання "учора"', + 'Closed tasks' => 'Закриті задачі', + 'Open tasks' => 'Відкриті задачі', + 'Not assigned' => 'Не призначено нікому', + 'View advanced search syntax' => 'Переглянути розширений синтаксис пошуку', + 'Overview' => 'Огляд', + 'Board/Calendar/List view' => 'Перегляд Дошки/Календаря/Списку', + 'Switch to the board view' => 'Перемкнутися на дошку', + 'Switch to the list view' => 'Перемкнутися на список', + 'Go to the search/filter box' => 'Перейти до поля пошуку/фільтрів', + 'There is no activity yet.' => 'Діяльність поки що відсутня.', + 'No tasks found.' => 'Задачі відсутні.', + 'Keyboard shortcut: "%s"' => 'Комбінація клавіш: "%s"', + 'List' => 'Список', + 'Filter' => 'Фільтр', + 'Advanced search' => 'Розширений пошук', + 'Example of query: ' => 'Приклад запиту: ', + 'Search by project: ' => 'Пошук за проєктом: ', + 'Search by column: ' => 'Пошук за колонкою: ', + 'Search by assignee: ' => 'Пошук за відповідальним: ', + 'Search by color: ' => 'Пошук за кольором: ', + 'Search by category: ' => 'Пошук за категорією: ', + 'Search by description: ' => 'Пошук за описом: ', + 'Search by due date: ' => 'Пошук за терміном виконання: ', + 'Average time spent in each column' => 'Середній витрачений час для кожної з колонок', + 'Average time spent' => 'Середній витрачений час', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Графік показує середній витрачений час в кожній колонці для останніх %d задач.', + 'Average Lead and Cycle time' => 'Середній час виконання та циклу', + 'Average lead time: ' => 'Середній час виконання: ', + 'Average cycle time: ' => 'Середній час циклу: ', + 'Cycle Time' => 'Час циклу', + 'Lead Time' => 'Час виконання', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Графік показує зміну з часом середнього часу виконання (з очікуванням) та циклу для останніх %d задач.', + 'Average time into each column' => 'Середній час в колонках', + 'Lead and cycle time' => 'Час виконання та циклу', + 'Lead time: ' => 'Час виконання: ', + 'Cycle time: ' => 'Час циклу: ', + 'Time spent in each column' => 'Час витрачений в кожній з колонок', + 'The lead time is the duration between the task creation and the completion.' => 'Час виконання є проміжком часу між створенням задачі та її завершенням.', + 'The cycle time is the duration between the start date and the completion.' => 'Час циклу є проміжком між датою початку та завершення задачі.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Якщо задача не закрита, замість дати завершення використовується поточна.', + 'Set the start date automatically' => 'Автоматично встановити дату початку', + 'Edit Authentication' => 'Змінити автентифікацію', + 'Remote user' => 'Віддалений користувач', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Віддалені користувачі не зберігають свої паролі в базі даних Kanboard. Приклади: облікові записи LDAP, GitHub та Google.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Якщо ви позначите прапорець "Вимкнути форму входу", дані введені у формі входу будуть ігноруватися.', + 'Default task color' => 'Стандартний колір задачі', + 'This feature does not work with all browsers.' => 'Ця функція підтримується не всіма браузерами.', + 'There is no destination project available.' => 'Немає доступних проєктів.', + 'Trigger automatically subtask time tracking' => 'Автоматично запускати таймер підзадачі при зміні статусу', + 'Include closed tasks in the cumulative flow diagram' => 'Враховувати закриті задачі в накопичувальній діаграмі', + 'Current swimlane: %s' => 'Поточна доріжка: %s', + 'Current column: %s' => 'Поточна колонка: %s', + 'Current category: %s' => 'Поточна категорія: %s', + 'no category' => 'без категорії', + 'Current assignee: %s' => 'Поточний відповідальний: %s', + 'not assigned' => 'відсутній', + 'Author:' => 'Автор:', + 'contributors' => 'учасники', + 'License:' => 'Ліцензія:', + 'License' => 'Ліцензія', + 'Enter the text below' => 'Введіть текст нижче', + 'Start date:' => 'Дата початку:', + 'Due date:' => 'Термін виконання:', + 'People who are project managers' => 'Користувачі, що є керівниками проєктів', + 'People who are project members' => 'Користувачі, що є учасниками проєктів', + 'NOK - Norwegian Krone' => 'NOK – Норвезька крона', + 'Show this column' => 'Показувати цю колонку', + 'Hide this column' => 'Приховати цю колонку', + 'End date' => 'Дата завершення', + 'Users overview' => 'Користувачі в проєктах', + 'Members' => 'Учасники', + 'Shared project' => 'Спільний проєкт', + 'Project managers' => 'Керівники проєктів', + 'Projects list' => 'Список проєктів', + 'End date:' => 'Дата завершення:', + 'Change task color when using a specific task link' => 'Змінити колір задачі, якщо використовується певний тип зв\'язків задач', + 'Task link creation or modification' => 'Створення або зміна зв\'язків задачі', + 'Milestone' => 'Віха', + 'Reset the search/filter box' => 'Скинути пошук/фільтр', + 'Documentation' => 'Документація', + 'Author' => 'Автор', + 'Version' => 'Версія', + 'Plugins' => 'Розширення', + 'There is no plugin loaded.' => 'Розширення відсутні.', + 'My notifications' => 'Мої сповіщення', + 'Custom filters' => 'Додаткові фільтри', + 'Your custom filter has been created successfully.' => 'Додатковий фільтр успішно створено.', + 'Unable to create your custom filter.' => 'Не вдалося створити додатковий фільтр.', + 'Custom filter removed successfully.' => 'Додатковий фільтр успішно видалено.', + 'Unable to remove this custom filter.' => 'Не вдалося видалити додатковий фільтр.', + 'Edit custom filter' => 'Редагувати додатковий фільтр', + 'Your custom filter has been updated successfully.' => 'Додатковий фільтр успішно оновлено.', + 'Unable to update custom filter.' => 'Не вдалося оновити додатковий фільтр.', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Новий прикріплений файл до задачі #%d: %s', + 'New comment on task #%d' => 'Новий коментар до задачі #%d', + 'Comment updated on task #%d' => 'Оновлено коментар до #%d', + 'New subtask on task #%d' => 'Нова підзадача задачі #%d', + 'Subtask updated on task #%d' => 'Підзадачу задачі #%d оновлено', + 'New task #%d: %s' => 'Нова задача #%d: %s', + 'Task updated #%d' => 'Задачу #%d оновлено', + 'Task #%d closed' => 'Задачу #%d закрито', + 'Task #%d opened' => 'Задачу #%d відкрито', + 'Column changed for task #%d' => 'Задачу #%d переміщено до іншої колонки', + 'New position for task #%d' => 'Позицію задачі #%d змінено', + 'Swimlane changed for task #%d' => 'Задачу #%d переміщено на іншу доріжку', + 'Assignee changed on task #%d' => 'Змінено відповідального у задачі #%d', + '%d overdue tasks' => '%d прострочених задач', + 'No notification.' => 'Сповіщення відсутні.', + 'Mark all as read' => 'Позначити все прочитаним', + 'Mark as read' => 'Позначити прочитаним', + 'Total number of tasks in this column across all swimlanes' => 'Загальне число задач в цій колонці на всіх доріжках', + 'Collapse swimlane' => 'Згорнути доріжку', + 'Expand swimlane' => 'Розгорнути доріжку', + 'Add a new filter' => 'Додати новий фільтр', + 'Share with all project members' => 'Зробити спільним з учасниками проєкту', + 'Shared' => 'Спільний', + 'Owner' => 'Власник', + 'Unread notifications' => 'Непрочитані сповіщення', + 'Notification methods:' => 'Способи сповіщення:', + 'Unable to read your file' => 'Не вдалося прочитати ваш файл', + '%d task(s) have been imported successfully.' => '%d задач(і) успішно імпортовано.', + 'Nothing has been imported!' => 'Нічого не було імпортовано!', + 'Import users from CSV file' => 'Імпортувати користувачів з CSV-файлу', + '%d user(s) have been imported successfully.' => '%d користувач(ів) успішно імпортовано.', + 'Comma' => 'Кома', + 'Semi-colon' => 'Крапка з комою', + 'Tab' => 'Табуляція', + 'Vertical bar' => 'Вертикальна риска', + 'Double Quote' => 'Подвійні лапки', + 'Single Quote' => 'Одинарні лапки', + '%s attached a file to the task #%d' => '%s прикріпив файл в задачу #%d', + 'There is no column or swimlane activated in your project!' => 'В проєкті відсутні активовані колонки або доріжки!', + 'Append filter (instead of replacement)' => 'Додавати фільтр замість заміни наявного', + 'Append/Replace' => 'Додати/Замінити', + 'Append' => 'Додати', + 'Replace' => 'Замінити', + 'Import' => 'Імпорт', + 'Change sorting' => 'Змінити сортування', + 'Tasks Importation' => 'Імпорт задач', + 'Delimiter' => 'Розділювач', + 'Enclosure' => 'Обрамлення тексту', + 'CSV File' => 'Файл CSV', + 'Instructions' => 'Інструкції', + 'Your file must use the predefined CSV format' => 'Ваш файл має бути у відповідному форматі CSV', + 'Your file must be encoded in UTF-8' => 'Вміст файлу повинен бути у кодуванні UTF-8', + 'The first row must be the header' => 'Перший рядок повинен містити заголовок', + 'Duplicates are not verified for you' => 'Перевірка на дублікати відсутня', + 'The due date must use the ISO format: YYYY-MM-DD' => 'Термін виконання має бути датою у форматі ISO: РРРР-ММ-ДД', + 'Download CSV template' => 'Завантажити шаблон CSV', + 'No external integration registered.' => 'Зовнішніх інтеграцій не зареєстровано.', + 'Duplicates are not imported' => 'Дублікати не імпортуються', + 'Usernames must be lowercase and unique' => 'Логіни повинні бути унікальними та в нижньому регістрі', + 'Passwords will be encrypted if present' => 'Наявні паролі будуть хешовані', + '%s attached a new file to the task %s' => '%s прикріпив файл до задачі %s', + 'Link type' => 'Тип посилання', + 'Assign automatically a category based on a link' => 'Встановити категорію на основі зв\'язку', + 'BAM - Konvertible Mark' => 'BAM – Конвертовна марка', + 'Assignee Username' => 'Логін відповідального', + 'Assignee Name' => 'Ім\'я відповідального', + 'Groups' => 'Групи', + 'Members of %s' => 'Учасники %s', + 'New group' => 'Нова група', + 'Group created successfully.' => 'Групу успішно створено.', + 'Unable to create your group.' => 'Не вдалося створити групу.', + 'Edit group' => 'Редагування групи', + 'Group updated successfully.' => 'Групу успішно оновлено.', + 'Unable to update your group.' => 'Не вдалося оновити групу.', + 'Add group member to "%s"' => 'Додати до групи "%s"', + 'Group member added successfully.' => 'Успішно додано учасника групи.', + 'Unable to add group member.' => 'Не вдалося додати учасника групи.', + 'Remove user from group "%s"' => 'Вилучити користувача із групи "%s"', + 'User removed successfully from this group.' => 'Користувача успішно вилучено з групи.', + 'Unable to remove this user from the group.' => 'Не вдалося вилучити користувача з групи.', + 'Remove group' => 'Видалити групу', + 'Group removed successfully.' => 'Групу успішно видалено.', + 'Unable to remove this group.' => 'Не вдалося видалити групу.', + 'Project Permissions' => 'Дозволи проєкту', + 'Manager' => 'Керівник', + 'Project Manager' => 'Керівник проєкту', + 'Project Member' => 'Учасник проєкту', + 'Project Viewer' => 'Спостерігач проєкту', + 'Your account is locked for %d minutes' => 'Ваш обліковий запис заблоковано на %d хвилин(и)', + 'Invalid captcha' => 'Неправильний код перевірки', + 'The name must be unique' => 'Назва має бути унікальною', + 'View all groups' => 'Переглянути всі групи', + 'There is no user available.' => 'Немає доступних користувачів.', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Дійсно вилучити користувача "%s" із групи "%s"?', + 'There is no group.' => 'Немає створених груп.', + 'Add group member' => 'Додати учасника групи', + 'Do you really want to remove this group: "%s"?' => 'Дійсно видалити групу "%s"?', + 'There is no user in this group.' => 'В цій групі відсутні учасники.', + 'Permissions' => 'Дозволи', + 'Allowed Users' => 'Дозволи користувачів', + 'No specific user has been allowed.' => 'Дозволів користувачів не задано.', + 'Role' => 'Роль', + 'Enter user name...' => 'Вкажіть ім\'я користувача…', + 'Allowed Groups' => 'Дозволи груп', + 'No group has been allowed.' => 'Дозволів груп не задано.', + 'Group' => 'Група', + 'Group Name' => 'Назва групи', + 'Enter group name...' => 'Вказіть назву групи…', + 'Role:' => 'Роль:', + 'Project members' => 'Учасники проєкту', + '%s mentioned you in the task #%d' => '%s послався на вас у задачі #%d', + '%s mentioned you in a comment on the task #%d' => '%s послався на вас у коментарі до задачі #%d', + 'You were mentioned in the task #%d' => 'На вас послалися у задачі #%d', + 'You were mentioned in a comment on the task #%d' => 'На вас послалися у коментарі до задачі №%d', + 'Estimated hours: ' => 'Оцінено годин: ', + 'Actual hours: ' => 'Витрачено годин: ', + 'Hours Spent' => 'Витрачено годин', + 'Hours Estimated' => 'Оцінено годин', + 'Estimated Time' => 'Оцінка часу', + 'Actual Time' => 'Витрачено часу', + 'Estimated vs actual time' => 'Оцінений та витрачений час', + 'RUB - Russian Ruble' => 'RUB – Російский рубль', + 'Assign the task to the person who does the action when the column is changed' => 'Призначити користувачу, який змінив колонку задачі', + 'Close a task in a specific column' => 'Закрити задачу в певній колонці', + 'Time-based One-time Password Algorithm' => 'Time-based One-time Password Algorithm', + 'Two-Factor Provider: ' => 'Постачальник другого фактора: ', + 'Disable two-factor authentication' => 'Вимкнути двофакторну автентифікацію', + 'Enable two-factor authentication' => 'Увімкнути двофакторну автентифікацію', + 'There is no integration registered at the moment.' => 'Зареєстровані інтеграції відсутні.', + 'Password Reset for Kanboard' => 'Скидання пароля Kanboard', + 'Forgot password?' => 'Забули пароль?', + 'Enable "Forget Password"' => 'Увімкнути "Забув пароль"', + 'Password Reset' => 'Скидання пароля', + 'New password' => 'Новий пароль', + 'Change Password' => 'Змінити пароль', + 'To reset your password click on this link:' => 'Для скидання пароля перейдіть за посиланням:', + 'Last Password Reset' => 'Останнє скидання пароля', + 'The password has never been reinitialized.' => 'Пароль ніколи не скидався.', + 'Creation' => 'Встановлено', + 'Expiration' => 'Дійсний до', + 'Password reset history' => 'Історія скидання пароля', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Всі задачі в колонці "%s" на доріжці "%s" успішно закрито.', + 'Do you really want to close all tasks of this column?' => 'Дійсно закрити всі задачі у колонці?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => 'буде закрито %d задач(і) у колонці "%s" на доріжці "%s".', + 'Close all tasks in this column and this swimlane' => 'Закрити всі задачі в цій колонці', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Відсутні розширення, що надають сповіщення для проєкта. Налаштувати окремі сповіщення можна у вашому профілі користувача.', + 'My dashboard' => 'Моя панель', + 'My profile' => 'Мій профіль', + 'Project owner: ' => 'Власник проєкту: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'Ідентифікатор проєкта є необов\'язковим і може складатися лише з латиниці та цифр: MIYPROEKT.', + 'Project owner' => 'Власник проєкту', + 'Personal projects do not have users and groups management.' => 'Керування користувачами та групами відсутнє в персональних проєктах.', + 'There is no project member.' => 'Учасники проєкту відсутні.', + 'Priority' => 'Пріорітет', + 'Task priority' => 'Пріорітет задачі', + 'General' => 'Основне', + 'Dates' => 'Дати', + 'Default priority' => 'Стандартний пріорітет', + 'Lowest priority' => 'Найнижчий пріорітет', + 'Highest priority' => 'Найвищий пріорітет', + 'Close a task when there is no activity' => 'Закрити задачу, якщо немає діяльності', + 'Duration in days' => 'Тривалість в годинах', + 'Send email when there is no activity on a task' => 'Надіслати E-mail, якщо діяльність в задачі відсутня', + 'Unable to fetch link information.' => 'Не вдалося отримати інформацію про посилання.', + 'Daily background job for tasks' => 'Щоденна фонова задача планувальника для задач', + 'Auto' => 'Авто', + 'Related' => 'Пов\'язаний', + 'Attachment' => 'Вкладення', + 'Web Link' => 'Web-посилання', + 'External links' => 'Зовнішні посилання', + 'Add external link' => 'Додати зовнішнє посилання', + 'Type' => 'Тип', + 'Dependency' => 'Залежність', + 'Add internal link' => 'Додати зв\'язок', + 'Add a new external link' => 'Додати нове зовнішнє посилання', + 'Edit external link' => 'Редагувати зовнішнє посилання', + 'External link' => 'Зовнішнє посилання', + 'Copy and paste your link here...' => 'Скопіюйте та вставте посилання сюди…', + 'URL' => 'URL', + 'Internal links' => 'Внутрішні зв\'язки', + 'Assign to me' => 'Призначити собі', + 'Me' => 'Мені', + 'Do not duplicate anything' => 'З жодного', + 'Projects management' => 'Керування проєктами', + 'Users management' => 'Керування користувачами', + 'Groups management' => 'Керування групами', + 'Create from another project' => 'Копіювати з іншого проєкту', + 'open' => 'відкрита', + 'closed' => 'закрита', + 'Priority:' => 'Пріорітет:', + 'Reference:' => 'Зовнішній ID:', + 'Complexity:' => 'Складність:', + 'Swimlane:' => 'Доріжка:', + 'Column:' => 'Колонка:', + 'Position:' => 'Позиція:', + 'Creator:' => 'Автор:', + 'Time estimated:' => 'Оцінка часу:', + '%s hours' => '%s годин', + 'Time spent:' => 'Витрачений час:', + 'Created:' => 'Створена:', + 'Modified:' => 'Редагована:', + 'Completed:' => 'Завершена:', + 'Started:' => 'Розпочата:', + 'Moved:' => 'Переміщена: ', + 'Task #%d' => 'Задача #%d', + 'Time format' => 'Формат часу', + 'Start date: ' => 'Дата початку: ', + 'End date: ' => 'Дата завершення: ', + 'New due date: ' => 'Новий термін виконання: ', + 'Start date changed: ' => 'Дату початку змінено: ', + 'Disable personal projects' => 'Вимкнути персональні проєкти', + 'Do you really want to remove this custom filter: "%s"?' => 'Дійсно видалити додатковий фільтр "%s"?', + 'Remove a custom filter' => 'Видалити додатковий фільтр', + 'User activated successfully.' => 'Користувача успішно розблоковано.', + 'Unable to enable this user.' => 'Не вдалося розблокувати користувача.', + 'User disabled successfully.' => 'Користувача успішно заблоковано.', + 'Unable to disable this user.' => 'Не вдалося заблокувати користувача.', + 'All files have been uploaded successfully.' => 'Всі файли успішно завантажено.', + 'The maximum allowed file size is %sB.' => 'Максимально дозволений розмір файлу – %s байт.', + 'Drag and drop your files here' => 'Перетягніть ваші файли сюди', + 'choose files' => 'оберіть файли', + 'View profile' => 'Переглянути профіль', + 'Two Factor' => 'Двофакторна автентифікація', + 'Disable user' => 'Заблокувати користувача', + 'Do you really want to disable this user: "%s"?' => 'Ви дійсно бажаєте заблокувати користувача "%s"?', + 'Enable user' => 'Розблокувати користувача', + 'Do you really want to enable this user: "%s"?' => 'Ви дійсно бажаєте розблокувати користувача "%s"?', + 'Download' => 'Завантажити', + 'Uploaded: %s' => 'Дата завантаження: %s', + 'Size: %s' => 'Розмір: %s', + 'Uploaded by %s' => 'Завантажений %s', + 'Filename' => 'Ім\'я файлу', + 'Size' => 'Розмір', + 'Column created successfully.' => 'Колонку успішно створено.', + 'Another column with the same name exists in the project' => 'В проєкті вже існує колонка з такою назвою', + 'Default filters' => 'Стандартні фільтри', + 'Your board doesn\'t have any columns!' => 'Дошка не має жодної колонки!', + 'Change column position' => 'Змінити позицію колонки', + 'Switch to the project overview' => 'Переключитися на огляд проєкту', + 'User filters' => 'Користувацькі фільтри', + 'Category filters' => 'Фільтри за категоріями', + 'Upload a file' => 'Завантажити файл', + 'View file' => 'Переглянути файл', + 'Last activity' => 'Остання діяльність', + 'Change subtask position' => 'Змінити позицію задачі', + 'This value must be greater than %d' => 'Значення має бути більшим за %d', + 'Another swimlane with the same name exists in the project' => 'В проєкті вже існує доріжка з такою назвою', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Наприклад : https://example.kanboard.org/ (використовується для створення абсолютних URL)', + 'Actions duplicated successfully.' => 'Дії успішно скопійовано.', + 'Unable to duplicate actions.' => 'Не вдалося скопіювати дії.', + 'Add a new action' => 'Додати нову дію', + 'Import from another project' => 'Імпортувати з іншого проєкта', + 'There is no action at the moment.' => 'Автоматичні дії відсутні.', + 'Import actions from another project' => 'Імпортувати дії з іншого проєкта', + 'There is no available project.' => 'Немає доступних проєктів.', + 'Local File' => 'Локальний файл', + 'Configuration' => 'Конфігурація', + 'PHP version:' => 'Версія PHP:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'Версія ОС:', + 'Database version:' => 'Версія бази даних:', + 'Browser:' => 'Браузер:', + 'Task view' => 'Перегляд задачі', + 'Edit task' => 'Редагувати задачу', + 'Edit description' => 'Редагувати опис', + 'New internal link' => 'Новий зв\'язок', + 'Display list of keyboard shortcuts' => 'Показати перелік клавіатурних комбінацій', + 'Avatar' => 'Аватар', + 'Upload my avatar image' => 'Завантажити зображення аватара', + 'Remove my image' => 'Видалити зображення', + 'The OAuth2 state parameter is invalid' => 'Параметр OAuth2 "state" неправильний', + 'User not found.' => 'Користувача не знайдено.', + 'Search in activity stream' => 'Шукати серед подій', + 'My activities' => 'Моя діяльність', + 'Activity until yesterday' => 'Діяльність до вчорашньго дня', + 'Activity until today' => 'Діяльність до сьогоднішнього дня', + 'Search by creator: ' => 'Шукати за автором: ', + 'Search by creation date: ' => 'Шукати за датою створення: ', + 'Search by task status: ' => 'Шукати за статусом задачі: ', + 'Search by task title: ' => 'Шукати за заголовком задачі: ', + 'Activity stream search' => 'Пошук серед подій', + 'Projects where "%s" is manager' => 'Проєкти, в яких "%s" є керівником', + 'Projects where "%s" is member' => 'Проєкти, в яких "%s" є учасником', + 'Open tasks assigned to "%s"' => 'Відкриті задачі призначені "%s"', + 'Closed tasks assigned to "%s"' => 'Закриті задачі призначені "%s"', + 'Assign automatically a color based on a priority' => 'Встановити автоматично колір на основі пріоритету', + 'Overdue tasks for the project(s) "%s"' => 'Прострочені задачі в проєктах "%s"', + 'Upload files' => 'Завантажити файли', + 'Installed Plugins' => 'Встановлені розширення', + 'Plugin Directory' => 'Каталог розширень', + 'Plugin installed successfully.' => 'Розширення успішно встановлено.', + 'Plugin updated successfully.' => 'Розширення успішно оновлено.', + 'Plugin removed successfully.' => 'Розширення успішно видалено.', + 'Subtask converted to task successfully.' => 'Підзадачу успішно перетворено в задачу.', + 'Unable to convert the subtask.' => 'Не вдалося перетворити підзадачу в задачу.', + 'Unable to extract plugin archive.' => 'Не вдалося розпакувати архів розширення.', + 'Plugin not found.' => 'Розширення не знайдено.', + 'You don\'t have the permission to remove this plugin.' => 'Вам не дозволено видалити це розширення.', + 'Unable to download plugin archive.' => 'Не вдалося завантажити архів розширення.', + 'Unable to write temporary file for plugin.' => 'Не вдалося створити тимчасовий файл для розширення.', + 'Unable to open plugin archive.' => 'Не вдалося відкрити архів розширення.', + 'There is no file in the plugin archive.' => 'В архіві розширення відсутній файл.', + 'Create tasks in bulk' => 'Створити багато задач одразу', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Ваш Kanboard не налаштований для встановлення розширень з користувацького інтерфейсу.', + 'There is no plugin available.' => 'Відсутні доступні розширення.', + 'Install' => 'Встановити', + 'Update' => 'Оновити', + 'Up to date' => 'Найновішої версії', + 'Not available' => 'Недоступне', + 'Remove plugin' => 'Видалити розширення', + 'Do you really want to remove this plugin: "%s"?' => 'Ви дійсно бажаєте видалити розширення "%s"?', + 'Uninstall' => 'Видалити', + 'Listing' => 'Список', + 'Metadata' => 'Метадані', + 'Manage projects' => 'Керування проєктами', + 'Convert to task' => 'Перетворити в задачу', + 'Convert sub-task to task' => 'Перетворити підзадачу в задачу', + 'Do you really want to convert this sub-task to a task?' => 'Дійсно перетворити підзадачу в задачу?', + 'My task title' => 'Заголовки задач', + 'Enter one task by line.' => 'По одній задачі на рядок.', + 'Number of failed login:' => 'Число провалених спроб входу:', + 'Account locked until:' => 'Обліковий запис заблоковано до:', + 'Email settings' => 'Налаштування E-mail', + 'Email sender address' => 'Адреса E-mail відправника', + 'Email transport' => 'Транспорт E-Mail', + 'Webhook token' => 'Ключ Webhook', + 'Project tags management' => 'Керування мітками в проєкті', + 'Tag created successfully.' => 'Мітку успішно створено.', + 'Unable to create this tag.' => 'Не вдалося створити мітку.', + 'Tag updated successfully.' => 'Мітку успішно оновлено.', + 'Unable to update this tag.' => 'Не вдалося оновити мітку.', + 'Tag removed successfully.' => 'Мітку успішно видалено.', + 'Unable to remove this tag.' => 'Не вдалося видалити мітку.', + 'Global tags management' => 'Керування спільними мітками', + 'Tags' => 'Мітки', + 'Tags management' => 'Керування мітками', + 'Add new tag' => 'Додати нову мітку', + 'Edit a tag' => 'Редагувати мітку', + 'Project tags' => 'Мітки проєкту', + 'There is no specific tag for this project at the moment.' => 'В проєкті відсутні мітки.', + 'Tag' => 'Мітка', + 'Remove a tag' => 'Видалити мітку', + 'Do you really want to remove this tag: "%s"?' => 'Дійсно видалити мітку "%s"?', + 'Global tags' => 'Спільні мітки', + 'There is no global tag at the moment.' => 'Спільні мітки відсутні.', + 'This field cannot be empty' => 'Це поле не може бути пустим', + 'Close a task when there is no activity in a specific column' => 'Закрити задачу, коли відсутня діяльність в певній колонці', + '%s removed a subtask for the task #%d' => '%s видалив підзадачу задачі #%d', + '%s removed a comment on the task #%d' => '%s видалив коментар до задачі #%d', + 'Comment removed on task #%d' => 'Коментар до задачі #%d видалено', + 'Subtask removed on task #%d' => 'Підзадачу задачі #%d видалено', + 'Hide tasks in this column in the dashboard' => 'Не показувати задачі з цієї колонки на панелі', + '%s removed a comment on the task %s' => '%s видалив коментар до задачі %s', + '%s removed a subtask for the task %s' => '%s видалив підзадачу задачі %s', + 'Comment removed' => 'Коментар видалено', + 'Subtask removed' => 'Підзадачу видалено', + '%s set a new internal link for the task #%d' => '%s створив новий зв\'язок задачі #%d', + '%s removed an internal link for the task #%d' => '%s видалив зв\'язок задачі #%d', + 'A new internal link for the task #%d has been defined' => 'Створено новий зв\'язок задачі #%d', + 'Internal link removed for the task #%d' => 'Зв\'язок задачі #%d видалено', + '%s set a new internal link for the task %s' => '%s створив новий зв\'язок задачі %s', + '%s removed an internal link for the task %s' => '%s видалив зв\'язок задачі %s', + 'Automatically set the due date on task creation' => 'Автоматично встановити термін виконання при створенні задачі', + 'Move the task to another column when closed' => 'Перемістити задачу до іншої колонки при закритті', + 'Move the task to another column when not moved during a given period' => 'Перемістити задачу до іншої колонки, якщо її не переміщали протягом вказаного часу', + 'Dashboard for %s' => 'Панель %s', + 'Tasks overview for %s' => 'Огляд задач %s', + 'Subtasks overview for %s' => 'Огляд підзадач %s', + 'Projects overview for %s' => 'Огляд проєктів %s', + 'Activity stream for %s' => 'Стрічка подій %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Встановити колір, якщо задачу переміщено на певну доріжку', + 'Assign a priority when the task is moved to a specific swimlane' => 'Встановити пріоритет, якщо задачу переміщено на певну доріжку', + 'User unlocked successfully.' => 'Користувача успішно розблоковано.', + 'Unable to unlock the user.' => 'Не вдалося розблокувати користувача.', + 'Move a task to another swimlane' => 'Перемістити задачу на іншу доріжку', + 'Creator Name' => 'Ім\'я автора', + 'Time spent and estimated' => 'Оцінка часу та витрачений час', + 'Move position' => 'Перемістити', + 'Move task to another position on the board' => 'Перемістити задачу в іншу частину дошки', + 'Insert before this task' => 'Вставити перед цією задачею', + 'Insert after this task' => 'Вставити після цієї задачі', + 'Unlock this user' => 'Розблокувати користувача', + 'Custom Project Roles' => 'Додаткові ролі в проєкті', + 'Add a new custom role' => 'Додати нову', + 'Restrictions for the role "%s"' => 'Обмеження ролі "%s"', + 'Add a new project restriction' => 'Додати нове проєктне обмеження', + 'Add a new drag and drop restriction' => 'Додати обмеження на переміщення задач', + 'Add a new column restriction' => 'Додати нове обмеження в колонці', + 'Edit this role' => 'Редагувати цю роль', + 'Remove this role' => 'Видалити цю роль', + 'There is no restriction for this role.' => 'Обмеження для ролі відсутні.', + 'Only moving task between those columns is permitted' => 'Обмежити переміщення задач між цими колонками', + 'Close a task in a specific column when not moved during a given period' => 'Закривати задачі в цій колонці, що не переміщувались протягом вказаного часу', + 'Edit columns' => 'Редагування колонок', + 'The column restriction has been created successfully.' => 'Обмеження для колонки успішно створено.', + 'Unable to create this column restriction.' => 'Не вдалося створити обмеження для колонки.', + 'Column restriction removed successfully.' => 'Обмеження для колонки успішно видалено.', + 'Unable to remove this restriction.' => 'Не вдалося видалити обмеження для колонки.', + 'Your custom project role has been created successfully.' => 'Додаткову роль в проєкті успішно створено.', + 'Unable to create custom project role.' => 'Не вдалося створити додаткову роль в проєкті.', + 'Your custom project role has been updated successfully.' => 'Додаткову роль в проєкті успішно оновлено.', + 'Unable to update custom project role.' => 'Не вдалося оновити додаткову роль в проєкті.', + 'Custom project role removed successfully.' => 'Додаткову роль видалено з проєкту.', + 'Unable to remove this project role.' => 'Не вдалося видалити додаткову роль з проєкту.', + 'The project restriction has been created successfully.' => 'Проєктне обмеження успішно створено.', + 'Unable to create this project restriction.' => 'Не вдалося створити проєктне обмеження.', + 'Project restriction removed successfully.' => 'Проєктне обмеження успішно видалено.', + 'You cannot create tasks in this column.' => 'Ви не можете створювати задачі в цій колонці.', + 'Task creation is permitted for this column' => 'Створення задач дозволене в цій колонці', + 'Closing or opening a task is permitted for this column' => 'Дозволено закривати і відкривати задачі в цій колонці', + 'Task creation is blocked for this column' => 'Створення задач блоковане для цієї колонки', + 'Closing or opening a task is blocked for this column' => 'Закривати і відкривати задачі заблоковано в цій колонці', + 'Task creation is not permitted' => 'Створення задач заборонено', + 'Closing or opening a task is not permitted' => 'Заборонено закривати чи відкривати задачі', + 'New drag and drop restriction for the role "%s"' => 'Нове обмеження на перетягування для ролі "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Користувачі з цією роллю зможуть переміщувати задачі лише між вказаними колонками.', + 'Remove a column restriction' => 'Вилучити обмеження колонки', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Видалити обмеження на переміщення із колонки "%s" в колонку "%s"?', + 'New column restriction for the role "%s"' => 'Нове обмеження для колонки ролі "%s"', + 'Rule' => 'Правило', + 'Do you really want to remove this column restriction?' => 'Видалити обмеження колонки?', + 'Custom roles' => 'Додаткові ролі', + 'New custom project role' => 'Нова додаткова роль', + 'Edit custom project role' => 'Редагування додаткової ролі', + 'Remove a custom role' => 'Видалення додаткової ролі', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Видалити додаткову роль "%s"? Всі користувачі з цією роллю отримають роль учасників проєкту.', + 'There is no custom role for this project.' => 'Додаткові ролі користувачів проєкту відсутні.', + 'New project restriction for the role "%s"' => 'Нове проєктне обмеження для ролі "%s"', + 'Restriction' => 'Обмеження', + 'Remove a project restriction' => 'Видалити проєктне обмеження', + 'Do you really want to remove this project restriction: "%s"?' => 'Видалити проєктне обмеження "%s"?', + 'Duplicate to multiple projects' => 'Дублювати в декілька проєктів', + 'This field is required' => 'Це поле є обов\'язковим', + 'Moving a task is not permitted' => 'Переміщення задачі заборонено', + 'This value must be in the range %d to %d' => 'Значення має бути в інтервалі між %d та %d', + 'You are not allowed to move this task.' => 'Ви не маєте дозволу переміщувати цю задачу.', + 'API User Access' => 'Користувацький доступ до API', + 'Preview' => 'Передперегляд', + 'Write' => 'Редагувати', + 'Write your text in Markdown' => 'Ваш текст у форматі Markdown', + 'No personal API access token registered.' => 'Персональний ключ доступу до API відсутній.', + 'Your personal API access token is "%s"' => 'Ваш персональний ключ доступу до API "%s"', + 'Remove your token' => 'Видалити ваш ключ доступу', + 'Generate a new token' => 'Генерувати новий ключ доступу', + 'Showing %d-%d of %d' => 'Показано %d–%d із %d', + 'Outgoing Emails' => 'Надіслані E-mail', + 'Add or change currency rate' => 'Додати або змінити курс валют', + 'Reference currency: %s' => 'Еталонна валюта: %s', + 'Add custom filters' => 'Додати власні фільтри', + 'Export' => 'Експортувати', + 'Add link label' => 'Додати мітку зв\'язку', + 'Incompatible Plugins' => 'Несумісні розширення', + 'Compatibility' => 'Сумісність', + 'Permissions and ownership' => 'Дозволи та власник', + 'Priorities' => 'Пріорітети', + 'Close this window' => 'Закрити це вікно', + 'Unable to upload this file.' => 'Не вдалося завантажити цей файл.', + 'Import tasks' => 'Імпортувати задачі', + 'Choose a project' => 'Оберіть проєкт', + 'Profile' => 'Профіль', + 'Application role' => 'Роль в межах додатку', + '%d invitations were sent.' => '%d запрошень надіслано.', + '%d invitation was sent.' => '%d запрошення надіслано.', + 'Unable to create this user.' => 'Не вдалося створити користувача.', + 'Kanboard Invitation' => 'Запрошення Kanboard', + 'Visible on dashboard' => 'Показувати на панелі', + 'Created at:' => 'Створений:', + 'Updated at:' => 'Змінений:', + 'There is no custom filter.' => 'Додаткові фільтри відсутні.', + 'New User' => 'Новий користувач', + 'Authentication' => 'Автентифікація', + 'If checked, this user will use a third-party system for authentication.' => 'Якщо позначено, користувач буде використовувати сторонню систему для автентифікації.', + 'The password is necessary only for local users.' => 'Пароль обов\'язковий лише для локальних користувачів.', + 'You have been invited to register on Kanboard.' => 'Вас запросили зареєструватися в Kanboard.', + 'Click here to join your team' => 'Клацніть тут щоб приєднатися до команди', + 'Invite people' => 'Запросити людей', + 'Emails' => 'Адреси E-mail', + 'Enter one email address by line.' => 'По одній адресі E-mail на рядок.', + 'Add these people to this project' => 'Додати цих людей до проєкта', + 'Add this person to this project' => 'Додати цю людину до проєкта', + 'Sign-up' => 'Зареєструватися', + 'Credentials' => 'Дані для входу', + 'New user' => 'Новий користувач', + 'This username is already taken' => 'Цей логін вже зайнято', + 'Your profile must have a valid email address.' => 'Ваш профіль повинен мати правильний E-mail.', + 'TRL - Turkish Lira' => 'TRL – Турецька ліра', + 'The project email is optional and could be used by several plugins.' => 'E-mail проєкту не є обов\'язковим і може використовуватися розширеннями.', + 'The project email must be unique across all projects' => 'E-mail проєкту має бути унікальним для всіх проєктів', + 'The email configuration has been disabled by the administrator.' => 'Налаштування E-mail вимкнуто адміністратором.', + 'Close this project' => 'Закрити цей проєкт', + 'Open this project' => 'Відкрити цей проєкт', + 'Close a project' => 'Закрити проєкт', + 'Do you really want to close this project: "%s"?' => 'Дійсно закрити проєкт "%s"?', + 'Reopen a project' => 'Повторне відкриття проєкту', + 'Do you really want to reopen this project: "%s"?' => 'Повторно відкрити проєкт "%s"?', + 'This project is open' => 'Проєкт відкритий', + 'This project is closed' => 'Проєкт закритий', + 'Unable to upload files, check the permissions of your data folder.' => 'Не вдалося завантажити файли, перевірте дозволи встановлені на директорію даних.', + 'Another category with the same name exists in this project' => 'Категорія з таким іменем вже існує в цьому проєкті', + 'Comment sent by email successfully.' => 'Коментар успішно надіслано на E-mail.', + 'Sent by email to "%s" (%s)' => 'Надіслано на E-mail "%s" (%s)', + 'Unable to read uploaded file.' => 'Не вдалося прочитати завантажений файл.', + 'Database uploaded successfully.' => 'Базу даних успішно завантажено.', + 'Task sent by email successfully.' => 'Задачу успішно надіслано на E-mail.', + 'There is no category in this project.' => 'Категорії в проєкті відсутні.', + 'Send by email' => 'Надіслати на e-mail', + 'Create and send a comment by email' => 'Створити та надіслати коментар на E-mail', + 'Subject' => 'Тема', + 'Upload the database' => 'Завантажити базу даних', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Ви можете завантажити попередньо збережену базу даних SQLite (стиснуту Gzip).', + 'Database file' => 'Файл бази даних', + 'Upload' => 'Завантажити', + 'Your project must have at least one active swimlane.' => 'Проєкт має мати хоча б одну активну доріжку.', + 'Project: %s' => 'Проєкт: %s', + 'Automatic action not found: "%s"' => 'Автоматичну дію не знайдено: "%s"', + '%d projects' => '%d проєктів', + '%d project' => '%d проєкт', + 'There is no project.' => 'Проєкти відсутні.', + 'Sort' => 'Сортувати', + 'Project ID' => 'ID проєкту', + 'Project name' => 'Назва проєкту', + 'Public' => 'Публічний', + 'Personal' => 'Персональний', + '%d tasks' => '%d задач', + '%d task' => '%d задача', + 'Task ID' => 'ID задачі', + 'Assign automatically a color when due date is expired' => 'Призначити колір автоматично при порушенні терміну виконання', + 'Total score in this column across all swimlanes' => 'Сумарна складність задач в цій колонці на всіх доріжках', + 'HRK - Kuna' => 'HRK – Хорватська куна', + 'ARS - Argentine Peso' => 'ARS – Аргентинський песо', + 'COP - Colombian Peso' => 'COP – Колумбійський песо', + '%d groups' => '%d груп(и)', + '%d group' => '%d група', + 'Group ID' => 'ID групи', + 'External ID' => 'Зовнішній ID', + '%d users' => '%d користувачі(в)', + '%d user' => '%d користувач', + 'Hide subtasks' => 'Приховати підзадачі', + 'Show subtasks' => 'Показати підзадачі', + 'Authentication Parameters' => 'Параметри автентифікації', + 'API Access' => 'Доступ до API', + 'No users found.' => 'Користувачі відсутні.', + 'User ID' => 'ID користувача', + 'Notifications are activated' => 'Сповіщення увімкнено', + 'Notifications are disabled' => 'Сповіщення вимкнуто', + 'User disabled' => 'Користувача заблоковано', + '%d notifications' => '%d сповіщень', + '%d notification' => '%d сповіщення', + 'There is no external integration installed.' => 'Зовнішніх інтеграцій не встановлено.', + 'You are not allowed to update tasks assigned to someone else.' => 'Ви не маєте дозволу змінювати задачі призначені комусь іншому.', + 'You are not allowed to change the assignee.' => 'Ви не маєте дозволу змінювати відповідального.', + 'Task suppression is not permitted' => 'Видалення задач заборонене', + 'Changing assignee is not permitted' => 'Зміна відповідального заборонена', + 'Update only assigned tasks is permitted' => 'Дозволене редагування лише призначених задач', + 'Only for tasks assigned to the current user' => 'Лише задачі призначені поточному користувачу', + 'My projects' => 'Мої проєкти', + 'You are not a member of any project.' => 'Ви не є учасником жодного з проєктів.', + 'My subtasks' => 'Мої підзадачі', + '%d subtasks' => '%d підзадач', + '%d subtask' => '%d підзадача', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Переміщення задач обмежити до призначених поточному користувачу та між колоноками', + '[DUPLICATE]' => '[КОПІЯ]', + 'DKK - Danish Krona' => 'DKK – Датська крона', + 'Remove user from group' => 'Вилучити користувача з групи', + 'Assign the task to its creator' => 'Призначити задачу автору', + 'This task was sent by email to "%s" with subject "%s".' => 'Задачу відправлено на E-mail "%s" з темою листа "%s".', + 'Predefined Email Subjects' => 'Шаблонні теми E-mail', + 'Write one subject by line.' => 'По одній темі на рядок.', + 'Create another link' => 'Створити ще один зв\'язок', + 'BRL - Brazilian Real' => 'BRL – Бразильський реал', + 'Add a new Kanboard task' => 'Додати нову задачу в Kanboard', + 'Subtask not started' => 'Підзадача в очікуванні', + 'Subtask currently in progress' => 'Йде робота над підзадачею', + 'Subtask completed' => 'Підзадачу завершено', + 'Subtask added successfully.' => 'Підзадачу успішно додано.', + '%d subtasks added successfully.' => '%d підзадач успішно додано.', + 'Enter one subtask by line.' => 'По одній підзадачі на рядок.', + 'Predefined Contents' => 'Шаблони вмісту', + 'Predefined contents' => 'Шаблони вмісту', + 'Predefined Task Description' => 'Шаблон опису задачі', + 'Do you really want to remove this template? "%s"' => 'Дійсно видалити шаблон "%s"?', + 'Add predefined task description' => 'Додати шаблон опису задачі', + 'Predefined Task Descriptions' => 'Шаблонні описи задач', + 'Template created successfully.' => 'Шаблон успішно створено.', + 'Unable to create this template.' => 'Не вдалося створити шаблон.', + 'Template updated successfully.' => 'Шаблон успішно оновлено.', + 'Unable to update this template.' => 'Не вдалося оновити шаблон.', + 'Template removed successfully.' => 'Шаблон успішно видалено.', + 'Unable to remove this template.' => 'Не вдалося видалити шаблон.', + 'Template for the task description' => 'Опис з шаблона', + 'The start date is greater than the end date' => 'Дата початку не може бути пізніше дати завершення', + 'Tags must be separated by a comma' => 'Мітки розділяються комою', + 'Only the task title is required' => 'Обов\'язковим полем є лише заголовок задачі', + 'Creator Username' => 'Логін автора', + 'Color Name' => 'Назва кольору', + 'Column Name' => 'Назва колонки', + 'Swimlane Name' => 'Назва доріжки', + 'Time Estimated' => 'Оцінка часу', + 'Time Spent' => 'Витрачений час', + 'External Link' => 'Зовнішнє посилання', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Ця функція вмикає для проєкта стрічки iCal та RSS, а також публічний перегляд дошки.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Зупинити таймери всіх підзадач при переміщенні до іншої колонки', + 'Subtask Title' => 'Заголовок підзадачі', + 'Add a subtask and activate the timer when moving a task to another column' => 'Додати підзадачу та увімкнути таймер при переміщенні задачі до іншої колонки', + 'days' => 'днів', + 'minutes' => 'хвилин', + 'seconds' => 'секунд', + 'Assign automatically a color when preset start date is reached' => 'Автоматично встановити колір при настанні вказаної дати', + 'Move the task to another column once a predefined start date is reached' => 'Перемістити задачу до іншої колонки при настанні вказаної дати', + 'This task is now linked to the task %s with the relation "%s"' => 'Задача зв\'язана з задачею %s відношенням "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'Зв\'яз відношенням "%s" із задачею %s вилучено', + 'Custom Filter:' => 'Власний фільтр:', + 'Unable to find this group.' => 'Не вдалося знайти таку групу.', + '%s moved the task #%d to the column "%s"' => '%s перемістив задачу #%d до колонки "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => '%s перемістив задачу #%d на позицію %d в колонці "%s"', + '%s moved the task #%d to the swimlane "%s"' => '%s перемістив задачу #%d на доріжку "%s"', + '%sh spent' => '%sh витрачено', + '%sh estimated' => '%sh за оцінкою', + 'Select All' => 'Вибрати все', + 'Unselect All' => 'Прибрати вибір', + 'Apply action' => 'Застосувати дію', + 'Move selected tasks to another column or swimlane' => 'Перемістити вибрані задачі до іншої колонки', + 'Edit tasks in bulk' => 'Відредагувати багато задач одразу', + 'Choose the properties that you would like to change for the selected tasks.' => 'Оберіть властивості, які ви бажаєте змінити в обраних задачах.', + 'Configure this project' => 'Налаштувати цей проєкт', + 'Start now' => 'Почати зараз', + '%s removed a file from the task #%d' => '%s видалив файл із задачі #%d', + 'Attachment removed from task #%d: %s' => 'Вкладення видалено із задачі #%d: %s', + 'No color' => 'Без кольору', + 'Attachment removed "%s"' => 'Вкладення видалено "%s"', + '%s removed a file from the task %s' => '%s видалив файл із задачі %s', + 'Move the task to another swimlane when assigned to a user' => 'Перемістити цю задачу на іншу доріжку, коли призначено користувачу', + 'Destination swimlane' => 'Доріжка призначення', + 'Assign a category when the task is moved to a specific swimlane' => 'Призначити категорію, коли ця задача мереміщена на вказану доріжку', + 'Move the task to another swimlane when the category is changed' => 'Перемістити цю задачу на іншу доріжку при зміні категорії', + 'Reorder this column by priority (ASC)' => 'Впорядкувати цю колонку за зростанням пріоритету', + 'Reorder this column by priority (DESC)' => 'Впорядкувати цю колонку за спаданням пріоритету', + 'Reorder this column by assignee and priority (ASC)' => 'Впорядкувати цю колонку за відповідальним та зростанням пріоритету', + 'Reorder this column by assignee and priority (DESC)' => 'Впорядкувати цю колонку за відповідальним та спаданням пріоритету', + 'Reorder this column by assignee (A-Z)' => 'Впорядкувати цю колонку за відповідальним (А-Я)', + 'Reorder this column by assignee (Z-A)' => 'Впорядкувати цю колонку за відповідальним (Я-А)', + 'Reorder this column by due date (ASC)' => 'Впорядкувати цю колонку за зростанням терміну виконання', + 'Reorder this column by due date (DESC)' => 'Впорядкувати цю колонку за спаданням терміну виконання', + 'Reorder this column by id (ASC)' => 'Впорядкувати цю колонку за ID (ASC)', + 'Reorder this column by id (DESC)' => 'Впорядкувати цю колонку за ID (DESC)', + '%s moved the task #%d "%s" to the project "%s"' => '%s перемістив задачу #%d "%s" до проєкту "%s"', + 'Task #%d "%s" has been moved to the project "%s"' => 'Задача #%d "%s" переміщено до проєкту "%s"', + 'Move the task to another column when the due date is less than a certain number of days' => 'Перемістити цю задачу до іншої колонки, коли до терміну виконання залишилося менше вказаного числа днів', + 'Automatically update the start date when the task is moved away from a specific column' => 'Автоматично оновлювати дату початку, коли задачу переміщено із вказаної колонки', + 'HTTP Client:' => 'HTTP-клієнт:', + 'Assigned' => 'Призначені будь-кому', + 'Task limits apply to each swimlane individually' => 'Ліміт задач застосовується до кожної доріжки окремо', + 'Column task limits apply to each swimlane individually' => 'Ліміт задач у колонці застосовується до кожної доріжки окремо', + 'Column task limits are applied to each swimlane individually' => 'Ліміт задач у колонці застосовано до кожної доріжки окремо', + 'Column task limits are applied across swimlanes' => 'Ліміт задач у колонці застосовується до всіх доріжок разом', + 'Task limit: ' => 'Ліміт задач:', + 'Change to global tag' => 'Перетворити на спільну мітку', + 'Do you really want to make the tag "%s" global?' => 'Дійсно зробити мітку "%s" спільною?', + 'Enable global tags for this project' => 'Увімкнути спільні мітки для цього проєкту', + 'Group membership(s):' => 'Належність до груп:', + '%s is a member of the following group(s): %s' => '%s належить до наступних груп: %s', + '%d/%d group(s) shown' => '%d/%d груп показано', + 'Subtask creation or modification' => 'Створення чи редагування підзадач', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Призначити виконавцем задачі заданого користувача, коли задачу переміщено до вказаної доріжки', + 'Comment' => 'Коментар', + 'Collapse vertically' => 'Згорнути вертикально', + 'Expand vertically' => 'Розгорнути вертикально', + 'MXN - Mexican Peso' => 'MXN - Мексиканський песо', + 'Estimated vs actual time per column' => 'Запланований та реально витрачений час по колонках', + 'HUF - Hungarian Forint' => 'HUF - Угорський форинт', + 'XBT - Bitcoin' => 'XBT – Біткоїн', + 'You must select a file to upload as your avatar!' => 'Ви повинні вибрати файл для завантаження у якості вашого аватару!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'Вибраний файл не є зображенням! (Допустимі розширення: *.gif, *.jpg, *.jpeg, *.png)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Автоматично призначити дату завершення, якщо завдання переміщене із зазначеної колонки', + 'No other projects found.' => 'Інших проектів не знайдено.', + 'Tasks copied successfully.' => 'Завдання успішно скопійовано.', + 'Unable to copy tasks.' => 'Не вдалося скопіювати завдання.', + 'Theme' => 'Тема', + 'Theme:' => 'Тема:', + 'Light theme' => 'Світла тема', + 'Dark theme' => 'Темна тема', + 'Automatic theme - Sync with system' => 'Автоматичний вибір - використовувати системні налаштування', + 'Application managers or more' => 'Менеджери додатків або більше', + 'Administrators' => 'Адміністратори', + 'Visibility:' => 'Видимість:', + 'Standard users' => 'Стандартні користувачі', + 'Visibility is required' => 'Видимість обов’язкова', + 'The visibility should be an app role' => 'Видимість має бути роллю додатка', + 'Reply' => 'Відповісти', + '%s wrote: ' => '%s написав: ', + 'Number of visible tasks in this column and swimlane' => 'Кількість видимих ​​завдань у цьому стовпці та доріжці', + 'Number of tasks in this swimlane' => 'Кількість завдань у цій доріжці', + 'Unable to find another subtask in progress, you can close this window.' => 'Не вдається знайти інше підзавдання у процесі, можна закрити це вікно.', + 'This theme is invalid' => 'Ця тема недійсна', + 'This role is invalid' => 'Ця роль недійсна', + 'This timezone is invalid' => 'Цей часовий пояс недійсний', + 'This language is invalid' => 'Ця мова недійсна', + 'This URL is invalid' => 'Ця URL-адреса недійсна', + 'Date format invalid' => 'Недійсний формат дати', + 'Time format invalid' => 'Недійсний формат часу', + 'Invalid Mail transport' => 'Недійсний транспорт пошти', + 'Color invalid' => 'Колір недійсний', + 'This value must be greater or equal to %d' => 'Це значення має бути більше або дорівнювати %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Додайте BOM на початку файлу (обов’язково для Microsoft Excel)', + 'Just add these tag(s)' => 'Просто додайте ці теги', + 'Remove internal link(s)' => 'Видалити внутрішні посилання', + 'Import tasks from another project' => 'Імпортувати завдання з іншого проекту', + 'Select the project to copy tasks from' => 'Виберіть проект, з якого потрібно скопіювати завдання', + 'The total maximum allowed attachments size is %sB.' => 'Загальний максимально дозволений розмір вкладень становить %sБ.', + 'Add attachments' => 'Додати вкладення', + 'Task #%d "%s" is overdue' => 'Задача #%d "%s" прострочена', + 'Enable notifications by default for all new users' => 'Увімкнути сповіщення за замовчуванням для всіх нових користувачів', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Призначати завдання його автору для визначених колонок, якщо виконавець не встановлений вручну', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Призначати завдання поточному користувачу під час переміщення в указану колонку, якщо нікого не призначено', +]; diff --git a/app/Locale/vi_VN/translations.php b/app/Locale/vi_VN/translations.php new file mode 100644 index 0000000..3985a27 --- /dev/null +++ b/app/Locale/vi_VN/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => ',', + 'number.thousands_separator' => ' ', + 'None' => 'Chưa có', + 'Edit' => 'Chỉnh sửa', + 'Remove' => 'Xóa bỏ', + 'Yes' => 'Có', + 'No' => 'Không', + 'cancel' => 'hủy bỏ', + 'or' => 'hoặc là', + 'Yellow' => 'Màu vàng', + 'Blue' => 'Màu xanh da trời', + 'Green' => 'Màu xanh lá', + 'Purple' => 'Màu tím', + 'Red' => 'Đỏ', + 'Orange' => 'Trái cam', + 'Grey' => 'Xám', + 'Brown' => 'Nâu', + 'Deep Orange' => 'Deep Orange', + 'Dark Grey' => 'Màu xám đen', + 'Pink' => 'Hồng', + 'Teal' => 'Teal', + 'Cyan' => ' Cyan', + 'Lime' => 'Vôi', + 'Light Green' => 'Màu xanh lợt', + 'Amber' => 'Amber', + 'Save' => 'Lưu', + 'Login' => 'Đăng nhập', + 'Official website:' => 'Trang web chinh thưc:', + 'Unassigned' => 'Chưa được gán', + 'View this task' => 'Xem công việc này', + 'Remove user' => 'Xóa người dùng', + 'Do you really want to remove this user: "%s"?' => 'Bạn có thực sự muốn loại bỏ người dùng này: "%s"?', + 'All users' => 'Tất cả người dùng', + 'Username' => 'Tên đăng nhập', + 'Password' => 'Mật khẩu', + 'Administrator' => 'Người quản lý', + 'Sign in' => 'Đăng nhập', + 'Users' => 'Người dùng', + 'Forbidden' => 'Forbidden', + 'Access Forbidden' => 'Truy cập bị cấm', + 'Edit user' => 'Chỉnh sửa người dùng', + 'Logout' => 'Đăng xuất', + 'Bad username or password' => 'Tên đăng nhập hoặc mật khẩu xâu', + 'Edit project' => 'Chỉnh sửa dự án', + 'Name' => 'Tên', + 'Projects' => 'Dự án', + 'No project' => 'Không có dự án', + 'Project' => 'Dự án', + 'Status' => 'Trạng thái', + 'Tasks' => 'Nhiệm vụ', + 'Board' => 'Bảng', + 'Actions' => 'Hành động', + 'Inactive' => 'Không hoạt động', + 'Active' => 'Hoạt động', + 'Unable to update this board.' => 'Không thể cập nhật bảng này.', + 'Disable' => 'Vô hiệu hóa', + 'Enable' => 'Bật', + 'New project' => 'Dự án mới', + 'Do you really want to remove this project: "%s"?' => 'Bạn có thực sự muốn loại bỏ dự án này: "%s"?', + 'Remove project' => 'Loại bỏ dự án', + 'Edit the board for "%s"' => 'Chỉnh sửa bảng cho "%s"', + 'Add a new column' => 'Thêm cột mới', + 'Title' => 'Chức vụ', + 'Assigned to %s' => 'Được giao cho %s', + 'Remove a column' => 'Loại bỏ một cột', + 'Unable to remove this column.' => 'Không thể xoá cột này.', + 'Do you really want to remove this column: "%s"?' => 'Bạn có thực sự muốn loại bỏ cột này: "%s"?', + 'Settings' => 'Cài đặt', + 'Application settings' => 'Cài đặt ứng dụng', + 'Language' => 'Ngôn ngữ', + 'Webhook token:' => 'Mã thông báo Webhook:', + 'API token:' => 'Mã thông báo API:', + 'Database size:' => 'Kích thước cơ sở dữ liệu:', + 'Download the database' => 'Tải về cơ sở dữ liệu', + 'Optimize the database' => 'Tối ưu hóa cơ sở dữ liệu', + '(VACUUM command)' => '(Lệnh VACUUM)', + '(Gzip compressed Sqlite file)' => '(Gzip nén tập tin Sqlite)', + 'Close a task' => 'Đóng một nhiệm vụ', + 'Column' => 'Cột', + 'Color' => 'Màu', + 'Assignee' => 'Người được chuyển nhượng', + 'Create another task' => 'Tạo một nhiệm vụ khác', + 'New task' => 'Nhiệm vụ mới', + 'Open a task' => 'Mở một nhiệm vụ', + 'Do you really want to open this task: "%s"?' => 'Bạn có thực sự muốn mở công việc này: "%s"?', + 'Back to the board' => 'Trở lại bảng', + 'There is nobody assigned' => 'Không ai được chỉ định', + 'Column on the board:' => 'Cột trên bảng:', + 'Close this task' => 'Đóng nhiệm vụ này', + 'Open this task' => 'Mở nhiệm vụ này', + 'There is no description.' => 'Không có mô tả.', + 'Add a new task' => 'Thêm một nhiệm vụ mới', + 'The username is required' => 'Tên người dùng được yêu cầu', + 'The maximum length is %d characters' => 'Chiều dài tối đa là %d ký tự', + 'The minimum length is %d characters' => 'Chiều dài tối thiểu là %d ký tự', + 'The password is required' => 'Mật khẩu là bắt buộc', + 'This value must be an integer' => 'Giá trị này phải là một số nguyên', + 'The username must be unique' => 'Tên người dùng phải là duy nhất', + 'The user id is required' => 'Người sử dụng id là bắt buộc', + 'Passwords don\'t match' => 'Mật khẩu don \'t match', + 'The confirmation is required' => 'Cần xác nhận', + 'The project is required' => 'Dự án được yêu cầu', + 'The id is required' => 'Id là bắt buộc', + 'The project id is required' => 'Id dự án được yêu cầu', + 'The project name is required' => 'Tên của dự án được yêu cầu', + 'The title is required' => 'Tiêu đề là bắt buộc', + 'Settings saved successfully.' => 'Cài đặt đã lưu thành công.', + 'Unable to save your settings.' => 'Không thể lưu cài đặt của bạn.', + 'Database optimization done.' => 'Tối ưu hóa cơ sở dữ liệu được thực hiện.', + 'Your project has been created successfully.' => 'Dự án của bạn đã được tạo thành công.', + 'Unable to create your project.' => 'Không thể tạo dự án của bạn.', + 'Project updated successfully.' => 'Dự án được cập nhật thành công.', + 'Unable to update this project.' => 'Không thể cập nhật dự án này.', + 'Unable to remove this project.' => 'Không thể xóa dự án này.', + 'Project removed successfully.' => 'Dự án đã được loại bỏ thành công.', + 'Project activated successfully.' => 'Dự án được kích hoạt thành công.', + 'Unable to activate this project.' => 'Không thể kích hoạt dự án này.', + 'Project disabled successfully.' => 'Dự án bị vô hiệu thành công.', + 'Unable to disable this project.' => 'Không thể vô hiệu hoá dự án này.', + 'Unable to open this task.' => 'Không thể mở nhiệm vụ này.', + 'Task opened successfully.' => 'Nhiệm vụ thành công.', + 'Unable to close this task.' => 'Không thể đóng nhiệm vụ này.', + 'Task closed successfully.' => 'Nhiệm vụ đóng thành công.', + 'Unable to update your task.' => 'Không thể cập nhật công việc của bạn.', + 'Task updated successfully.' => 'Nhiệm vụ được cập nhật thành công.', + 'Unable to create your task.' => 'Không thể tạo ra nhiệm vụ của bạn.', + 'Task created successfully.' => 'Nhiệm vụ thành công.', + 'User created successfully.' => 'Người dùng đã thành công.', + 'Unable to create your user.' => 'Không thể tạo người dùng của bạn.', + 'User updated successfully.' => 'Người dùng cập nhật thành công.', + 'User removed successfully.' => 'Người dùng đã xoá thành công.', + 'Unable to remove this user.' => 'Không thể xóa người dùng này.', + 'Board updated successfully.' => 'Bảng đã được cập nhật thành công.', + 'Ready' => 'Sẳn sàng', + 'Backlog' => 'Backlog', + 'Work in progress' => 'Công việc đang tiến hành', + 'Done' => 'Làm xong', + 'Application version:' => 'Phiên bản ứng dụng:', + 'Id' => 'ID', + 'Public link' => 'Liên kết công cộng', + 'Timezone' => 'Múi giờ', + 'Sorry, I didn\'t find this information in my database!' => 'Xin lỗi, tôi không tìm thấy thông tin này trong cơ sở dữ liệu của tôi!', + 'Page not found' => 'Trang không tìm thấy', + 'Complexity' => 'Sự phức tạp', + 'Task limit' => 'Nhiệm vụ giới hạn', + 'Task count' => 'Nhiệm vụ', + 'User' => 'Người dùng', + 'Comments' => 'Bình luận', + 'Comment is required' => 'Bình luận là bắt buộc', + 'Comment added successfully.' => 'Bình luận được thêm thành công.', + 'Unable to create your comment.' => 'Không thể tạo bình luận của bạn.', + 'Due Date' => 'Ngày đáo hạn', + 'Invalid date' => 'Ngày không hợp lệ', + 'Automatic actions' => 'Hành động tự động', + 'Your automatic action has been created successfully.' => 'Hành động tự động của bạn đã được tạo thành công.', + 'Unable to create your automatic action.' => 'Không thể tạo hành động tự động của bạn.', + 'Remove an action' => 'Loại bỏ một hành động', + 'Unable to remove this action.' => 'Không thể loại bỏ hành động này.', + 'Action removed successfully.' => 'Hành động đã được xoá thành công.', + 'Automatic actions for the project "%s"' => 'Hành động tự động cho dự án "%s"', + 'Add an action' => 'Thêm một hành động', + 'Event name' => 'Tên sự kiện', + 'Action' => 'Hoạt động', + 'Event' => 'Biến cố', + 'When the selected event occurs execute the corresponding action.' => 'Khi sự kiện đã chọn xảy ra, hãy thực hiện hành động tương ứng.', + 'Next step' => 'Bước tiếp theo', + 'Define action parameters' => 'Xác định các tham số hành động', + 'Do you really want to remove this action: "%s"?' => 'Bạn có thực sự muốn loại bỏ hành động này: "%s"?', + 'Remove an automatic action' => 'Hủy bỏ một hành động tự động', + 'Assign the task to a specific user' => 'Chỉ định nhiệm vụ cho một người dùng cụ thể', + 'Assign the task to the person who does the action' => 'Chỉ định nhiệm vụ cho người thực hiện hành động', + 'Duplicate the task to another project' => 'Nhân đôi công việc với một dự án khác', + 'Move a task to another column' => 'Di chuyển một nhiệm vụ tới một cột khác', + 'Task modification' => 'Sửa đổi tác vụ', + 'Task creation' => 'Nhiệm vụ sáng tạo', + 'Closing a task' => 'Đóng một nhiệm vụ', + 'Assign a color to a specific user' => 'Chỉ định màu cho một người dùng cụ thể', + 'Position' => 'Chức vụ', + 'Duplicate to project' => 'Nhân bản với một dự án khác', + 'Duplicate' => 'Bản sao', + 'Link' => 'Liên kết', + 'Comment updated successfully.' => 'Bình luận cập nhật thành công.', + 'Unable to update your comment.' => 'Không thể cập nhật nhận xét của bạn.', + 'Remove a comment' => 'Xóa một bình luận', + 'Comment removed successfully.' => 'Đã xóa nhận xét thành công.', + 'Unable to remove this comment.' => 'Không thể xóa nhận xét này.', + 'Do you really want to remove this comment?' => 'Bạn có thật sự muốn xóa nhận xét này?', + 'Current password for the user "%s"' => 'Mật khẩu hiện tại cho người dùng "%s"', + 'The current password is required' => 'Mật khẩu hiện tại là bắt buộc', + 'Wrong password' => 'Sai mật khẩu', + 'Unknown' => 'Không xác định', + 'Last logins' => 'Đăng nhập lần cuối', + 'Login date' => 'Ngày đăng nhập', + 'Authentication method' => 'Phương pháp xác thực', + 'IP address' => 'Địa chỉ IP', + 'User agent' => 'Đại lý người dùng', + 'Persistent connections' => 'Kết nối liên tục', + 'No session.' => 'Không có phiên.', + 'Expiration date' => 'Ngày hết hạn', + 'Remember Me' => 'Nhớ tôi', + 'Creation date' => 'Ngày thành lập', + 'Everybody' => 'Mọi người', + 'Open' => 'Mở', + 'Closed' => 'Đóng', + 'Search' => 'Tìm kiếm', + 'Nothing found.' => 'Không kết quả.', + 'Due date' => 'Ngày đáo hạn', + 'Description' => 'Mô tả', + '%d comments' => '%d nhận xét', + '%d comment' => '%d nhận xét', + 'Email address invalid' => 'Địa chỉ email không hợp lệ', + 'Your external account is not linked anymore to your profile.' => 'Tài khoản bên ngoài của bạn không được liên kết với hồ sơ của bạn nữa.', + 'Unable to unlink your external account.' => 'Không thể hủy liên kết tài khoản bên ngoài của bạn.', + 'External authentication failed' => 'Xác thực bên ngoài không thành công', + 'Your external account is linked to your profile successfully.' => 'Tài khoản bên ngoài của bạn được liên kết với hồ sơ của bạn thành công.', + 'Email' => 'E-mail', + 'Task removed successfully.' => 'Nhiệm vụ đã được gỡ bỏ thành công.', + 'Unable to remove this task.' => 'Không thể xóa tác vụ này.', + 'Remove a task' => 'Loại bỏ một nhiệm vụ', + 'Do you really want to remove this task: "%s"?' => 'Bạn có thực sự muốn loại bỏ công việc này: "%s"?', + 'Assign automatically a color based on a category' => 'Chỉ định tự động một màu dựa trên một loại', + 'Assign automatically a category based on a color' => 'Chỉ định tự động một thể loại dựa trên màu', + 'Task creation or modification' => 'Tạo tác hoặc sửa đổi nhiệm vụ', + 'Category' => 'Thể loại', + 'Category:' => 'Thể loại:', + 'Categories' => 'Thể loại', + 'Your category has been created successfully.' => 'Danh mục của bạn đã được tạo thành công.', + 'This category has been updated successfully.' => 'Danh mục này đã được cập nhật thành công.', + 'Unable to update this category.' => 'Không thể cập nhật danh mục này.', + 'Remove a category' => 'Loại bỏ một danh mục', + 'Category removed successfully.' => 'Loại bỏ thành công.', + 'Unable to remove this category.' => 'Không thể xóa loại này.', + 'Category modification for the project "%s"' => 'Sửa đổi loại cho dự án "%s"', + 'Category Name' => 'Tên danh mục', + 'Add a new category' => 'Thêm một loại mới', + 'Do you really want to remove this category: "%s"?' => 'Bạn có thực sự muốn loại bỏ loại này: "%s"?', + 'All categories' => 'Tất cả danh mục', + 'No category' => 'Không có loại', + 'The name is required' => 'Tên là bắt buộc', + 'Remove a file' => 'Hủy bỏ một tập tin', + 'Unable to remove this file.' => 'Không thể xóa tệp này.', + 'File removed successfully.' => 'Đã xoá tệp thành công.', + 'Attach a document' => 'Đính kèm một tài liệu', + 'Do you really want to remove this file: "%s"?' => 'Bạn có thực sự muốn xóa tệp này: "%s"?', + 'Attachments' => 'File đính kèm', + 'Edit the task' => 'Chỉnh sửa nhiệm vụ', + 'Add a comment' => 'Thêm nhận xét', + 'Edit a comment' => 'Chỉnh sửa một nhận xét', + 'Summary' => 'Tóm lược', + 'Time tracking' => 'Theo dõi thời gian', + 'Estimate:' => 'Ước tính:', + 'Spent:' => 'Spent:', + 'Do you really want to remove this sub-task?' => 'Bạn có thật sự muốn loại bỏ nhiệm vụ phụ này?', + 'Remaining:' => 'Còn lại:', + 'hours' => 'giờ', + 'estimated' => 'ước tính', + 'Sub-Tasks' => 'Nhiệm vụ phụ', + 'Add a sub-task' => 'Thêm một nhiệm vụ phụ', + 'Original estimate' => 'Ước tính ban đầu', + 'Create another sub-task' => 'Tạo một nhiệm vụ phụ khác', + 'Time spent' => 'Thời gian đã bỏ ra', + 'Edit a sub-task' => 'Chỉnh sửa một tiểu nhiệm vụ', + 'Remove a sub-task' => 'Loại bỏ một nhiệm vụ phụ', + 'The time must be a numeric value' => 'Thời gian phải là một giá trị số', + 'Todo' => 'Làm', + 'In progress' => 'Trong tiến trình', + 'Sub-task removed successfully.' => 'Nhiệm vụ phụ được xoá thành công.', + 'Unable to remove this sub-task.' => 'Không thể loại bỏ nhiệm vụ phụ này.', + 'Sub-task updated successfully.' => 'Tiểu nhiệm vụ được cập nhật thành công.', + 'Unable to update your sub-task.' => 'Không thể cập nhật tiểu nhiệm vụ của bạn.', + 'Unable to create your sub-task.' => 'Không thể tạo ra nhiệm vụ phụ của bạn.', + 'Maximum size: ' => 'Kích thước tối đa: ', + 'Display another project' => 'Hiển thị một dự án khác', + 'Created by %s' => 'Đã tạo bởi %s', + 'Tasks Export' => 'Nhiệm vụ xuất khẩu', + 'Start Date' => 'Ngày bắt đầu', + 'Execute' => 'Thực hiện', + 'Task Id' => 'Nhiệm vụ Id', + 'Creator' => 'Người sáng tạo', + 'Modification date' => 'Ngày sửa đổi', + 'Completion date' => 'Ngày kết thúc', + 'Clone' => 'Clone', + 'Project cloned successfully.' => 'Dự án nhân bản thành công.', + 'Unable to clone this project.' => 'Không thể nhân bản dự án này.', + 'Enable email notifications' => 'Bật thông báo qua email', + 'Task position:' => 'Nhiệm vụ:', + 'The task #%d has been opened.' => 'Nhiệm vụ #%d đã được mở ra.', + 'The task #%d has been closed.' => 'Nhiệm vụ #%d đã được đóng lại.', + 'Sub-task updated' => 'Tiểu nhiệm vụ cập nhật', + 'Title:' => 'Chức vụ:', + 'Status:' => 'Trạng thái:', + 'Assignee:' => 'Người được chuyển nhượng:', + 'Time tracking:' => 'Theo dõi thời gian:', + 'New sub-task' => 'Nhiệm vụ phụ mới', + 'New attachment added "%s"' => 'Tập tin đính kèm mới được thêm vào "%s"', + 'New comment posted by %s' => 'Bình luận mới được đăng bởi %s', + 'New comment' => 'Bình luận mới', + 'Comment updated' => 'Bình luận cập nhật', + 'New subtask' => 'Bổ sung phụ', + 'I only want to receive notifications for these projects:' => 'Tôi chỉ muốn nhận thông báo cho các dự án đó:', + 'view the task on Kanboard' => 'xem công việc trên Kanboard', + 'Public access' => 'Truy cập công cộng', + 'Disable public access' => 'Vô hiệu hoá quyền truy cập công cộng', + 'Enable public access' => 'Bật quyền truy cập công cộng', + 'Public access disabled' => 'Truy cập công cộng bị vô hiệu hóa', + 'Move the task to another project' => 'Di chuyển công việc sang dự án khác', + 'Move to project' => 'Chuyển sang dự án khác', + 'Do you really want to duplicate this task?' => 'Bạn có thực sự muốn lặp lại nhiệm vụ này?', + 'Duplicate a task' => 'Nhân đôi một nhiệm vụ', + 'External accounts' => 'Tài khoản bên ngoài', + 'Account type' => 'Kiểu tài khoản', + 'Local' => 'Địa phương', + 'Remote' => 'Xa', + 'Enabled' => 'Bật', + 'Disabled' => 'Vô hiệu', + 'Login:' => 'Đăng nhập:', + 'Full Name:' => 'Họ và tên:', + 'Email:' => 'E-mail:', + 'Notifications:' => 'Thông báo:', + 'Notifications' => 'Thông báo', + 'Account type:' => 'Kiểu tài khoản:', + 'Edit profile' => 'Chỉnh sửa hồ sơ', + 'Change password' => 'Đổi mật khẩu', + 'Password modification' => 'Sửa đổi mật khẩu', + 'External authentications' => 'Xác thực bên ngoài', + 'Never connected.' => 'Không bao giờ kết nối.', + 'No external authentication enabled.' => 'Không bật xác thực bên ngoài.', + 'Password modified successfully.' => 'Đã sửa đổi mật khẩu thành công.', + 'Unable to change the password.' => 'Không thể thay đổi mật khẩu.', + 'Change category' => 'Thay đổi loại', + '%s updated the task %s' => ' %s đã cập nhật công việc %s', + '%s opened the task %s' => ' %s mở nhiệm vụ %s', + '%s moved the task %s to the position #%d in the column "%s"' => ' %s di chuyển nhiệm vụ %s đến vị trí #%d trong cột "%s"', + '%s moved the task %s to the column "%s"' => ' %s di chuyển công việc %s đến cột "%s"', + '%s created the task %s' => ' %s đã tạo ra nhiệm vụ %s', + '%s closed the task %s' => ' %s đã đóng công việc %s', + '%s created a subtask for the task %s' => ' %s tạo ra một phụ cho nhiệm vụ %s', + '%s updated a subtask for the task %s' => ' %s cập nhật một công việc phụ cho nhiệm vụ %s', + 'Assigned to %s with an estimate of %s/%sh' => 'Được giao cho %s với ước tính %s / %sh', + 'Not assigned, estimate of %sh' => 'Không được chỉ định, ước tính %sh', + '%s updated a comment on the task %s' => ' %s đã cập nhật một bình luận về nhiệm vụ %s', + '%s commented the task %s' => ' %s bình luận về nhiệm vụ %s', + '%s\'s activity' => 'Hoạt động của %s', + 'RSS feed' => 'Nguồn cấp dữ liệu RSS', + '%s updated a comment on the task #%d' => ' %s đã cập nhật một bình luận về nhiệm vụ #%d', + '%s commented on the task #%d' => ' %s bình luận về nhiệm vụ #%d', + '%s updated a subtask for the task #%d' => ' %s đã cập nhật một công việc phụ cho nhiệm vụ #%d', + '%s created a subtask for the task #%d' => ' %s tạo ra một phụ cho nhiệm vụ #%d', + '%s updated the task #%d' => ' %s cập nhật công việc #%d', + '%s created the task #%d' => ' %s đã tạo ra nhiệm vụ #%d', + '%s closed the task #%d' => ' %s đã đóng công việc #%d', + '%s opened the task #%d' => ' %s mở nhiệm vụ #%d', + 'Activity' => 'Hoạt động', + 'Default values are "%s"' => 'Giá trị mặc định là "%s"', + 'Default columns for new projects (Comma-separated)' => 'Cột mặc định cho các dự án mới (tách bằng dấu phẩy)', + 'Task assignee change' => 'Thay đổi nhiệm vụ chuyển nhượng', + '%s changed the assignee of the task #%d to %s' => ' %s đã thay đổi người được giao nhiệm vụ #%d thành %s', + '%s changed the assignee of the task %s to %s' => ' %s đã thay đổi người được giao nhiệm vụ %s sang %s', + 'New password for the user "%s"' => 'Mật khẩu mới cho người dùng "%s"', + 'Choose an event' => 'Chọn một sự kiện', + 'Create a task from an external provider' => 'Tạo một nhiệm vụ từ một nhà cung cấp bên ngoài', + 'Change the assignee based on an external username' => 'Thay đổi người được chuyển nhượng dựa trên tên người dùng bên ngoài', + 'Change the category based on an external label' => 'Thay đổi thể loại dựa trên một nhãn bên ngoài', + 'Reference' => 'Tài liệu tham khảo', + 'Label' => 'Nhãn', + 'Database' => 'Cơ sở dữ liệu', + 'About' => 'Về', + 'Database driver:' => 'Trình điều khiển cơ sở dữ liệu:', + 'Board settings' => 'Cài đặt Bảng', + 'Webhook settings' => 'Cài đặt Webhook', + 'Reset token' => 'Đặt lại mã thông báo', + 'API endpoint:' => 'Điểm cuối API:', + 'Refresh interval for personal board' => 'Khoảng thời gian làm mới cho bảng cá nhân', + 'Refresh interval for public board' => 'Khoảng thời gian làm mới cho hội đồng quản trị', + 'Task highlight period' => 'Nhiệm vụ thời gian nổi bật', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Thời gian (thứ hai) để xem xét một tác vụ đã được sửa đổi gần đây (0 để vô hiệu hoá, 2 ngày theo mặc định)', + 'Frequency in second (60 seconds by default)' => 'Tần số trong giây (mặc định 60 giây)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Tần số trong giây (0 để tắt tính năng này, mặc định là 10 giây)', + 'Application URL' => 'URL ứng dụng', + 'Token regenerated.' => 'Token tái tạo.', + 'Date format' => 'Định dạng ngày tháng', + 'ISO format is always accepted, example: "%s" and "%s"' => 'Định dạng ISO luôn được chấp nhận, ví dụ: "%s" và "%s"', + 'New personal project' => 'Dự án riêng tư mới', + 'This project is personal' => 'Dự án này là riêng tư', + 'Add' => 'Thêm vào', + 'Start date' => 'Ngày bắt đầu', + 'Time estimated' => 'Thời gian ước tính', + 'There is nothing assigned to you.' => 'Không có gì được giao cho bạn.', + 'My tasks' => 'Nhiệm vụ của tôi', + 'Activity stream' => 'Luồng hoạt động', + 'Dashboard' => 'Bảng điều khiển', + 'Confirmation' => 'Xác nhận', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Create a comment from an external provider' => 'Tạo một bình luận từ một nhà cung cấp bên ngoài', + 'Project management' => 'Quản lý dự án', + 'Columns' => 'Cột', + 'Task' => 'Bài tập', + 'Percentage' => 'Phần trăm', + 'Number of tasks' => 'Số nhiệm vụ', + 'Task distribution' => 'Phân phát nhiệm vụ', + 'Analytics' => 'Phân tích', + 'Subtask' => 'Subtask', + 'User repartition' => 'Phân vùng người dùng', + 'Clone this project' => 'Nhân bản dự án này', + 'Column removed successfully.' => 'Bỏ cột thành công.', + 'Not enough data to show the graph.' => 'Không đủ dữ liệu để hiển thị đồ thị.', + 'Previous' => 'Trước', + 'The id must be an integer' => 'Id phải là một số nguyên', + 'The project id must be an integer' => 'Id dự án phải là một số nguyên', + 'The status must be an integer' => 'Trạng thái phải là một số nguyên', + 'The subtask id is required' => 'Cần phải có id phụ đề', + 'The subtask id must be an integer' => 'Số thứ tự subtask phải là một số nguyên', + 'The task id is required' => 'ID công việc được yêu cầu', + 'The task id must be an integer' => 'ID công việc phải là một số nguyên', + 'The user id must be an integer' => 'ID người dùng phải là một số nguyên', + 'This value is required' => 'Giá trị này là bắt buộc', + 'This value must be numeric' => 'Giá trị này phải là số', + 'Unable to create this task.' => 'Không thể tạo ra nhiệm vụ này.', + 'Cumulative flow diagram' => 'Sơ đồ luồng tích luỹ', + 'Daily project summary' => 'Tóm tắt dự án hàng ngày', + 'Daily project summary export' => 'Tóm tắt dự án hàng ngày xuất khẩu', + 'Exports' => 'Xuất khẩu', + 'This export contains the number of tasks per column grouped per day.' => 'Xuất này có chứa số nhiệm vụ cho mỗi cột được nhóm trong một ngày.', + 'Active swimlanes' => 'Đường phố hoạt động', + 'Add a new swimlane' => 'Thêm một đường swimlane mới', + 'Default swimlane' => 'Mặc định swimlane', + 'Do you really want to remove this swimlane: "%s"?' => 'Bạn có thực sự muốn loại bỏ swimlane này: "%s"?', + 'Inactive swimlanes' => 'Bể swimlane không hoạt động', + 'Remove a swimlane' => 'Hủy bỏ swimlane lội', + 'Swimlane modification for the project "%s"' => 'Sửa đổi Swimlane cho dự án %s', + 'Swimlane removed successfully.' => 'Swimlane đã được gỡ bỏ thành công.', + 'Swimlanes' => 'Swimlanes', + 'Swimlane updated successfully.' => 'Swimlane được cập nhật thành công.', + 'Unable to remove this swimlane.' => 'Không thể tháo dải swimlane ra.', + 'Unable to update this swimlane.' => 'Không thể cập nhật swimlane lội này.', + 'Your swimlane has been created successfully.' => 'Làn sóng của bạn đã được tạo ra thành công.', + 'Example: "Bug, Feature Request, Improvement"' => 'Ví dụ: "Lỗi, yêu cầu tính năng, cải tiến"', + 'Default categories for new projects (Comma-separated)' => 'Danh mục mặc định cho các dự án mới (Phân cách bằng dấu phẩy)', + 'Integrations' => 'Tích hợp', + 'Integration with third-party services' => 'Tích hợp với các dịch vụ của bên thứ ba', + 'Subtask Id' => 'Id phụ đề', + 'Subtasks' => 'Công việc', + 'Subtasks Export' => 'Nhiệm vụ xuất khẩu', + 'Task Title' => 'Nhiệm vụ', + 'Untitled' => 'Không có tiêu đề', + 'Application default' => 'Mặc định ứng dụng', + 'Language:' => 'Ngôn ngữ:', + 'Timezone:' => 'Múi giờ:', + 'All columns' => 'Tất cả các cột', + 'Next' => 'Kế tiếp', + '#%d' => '#%d', + 'All swimlanes' => 'Tất cả swimlane', + 'All colors' => 'Đủ màu sắc', + 'Moved to column %s' => 'Đã chuyển đến cột %s', + 'User dashboard' => 'Trang tổng quan của người dùng', + 'Allow only one subtask in progress at the same time for a user' => 'Cho phép chỉ có một phụ đề đang được tiến hành đồng thời cho một người dùng', + 'Edit column "%s"' => 'Chỉnh sửa cột %s ', + 'Select the new status of the subtask: "%s"' => 'Chọn trạng thái mới của phụ đề: "%s"', + 'Subtask timesheet' => 'Lịch biểu phụ đề', + 'There is nothing to show.' => 'Không có gì để hiển thị.', + 'Time Tracking' => 'Theo dõi Thời gian', + 'You already have one subtask in progress' => 'Bạn đã có một phụ trong tiến trình', + 'Which parts of the project do you want to duplicate?' => 'Bạn muốn nhân bản phần nào của dự án?', + 'Disallow login form' => 'Disallow form đăng nhập', + 'Start' => 'Khởi đầu', + 'End' => 'Kết thúc', + 'Task age in days' => 'Nhiệm vụ tuổi tác trong ngày', + 'Days in this column' => 'Ngày trong cột này', + '%dd' => '%dd', + 'Add a new link' => 'Thêm một liên kết mới', + 'Do you really want to remove this link: "%s"?' => 'Bạn có thực sự muốn xoá liên kết này: "%s"?', + 'Do you really want to remove this link with task #%d?' => 'Bạn có thực sự muốn loại bỏ liên kết này với công việc #%d?', + 'Field required' => 'Trường bắt buộc', + 'Link added successfully.' => 'Liên kết thành công.', + 'Link updated successfully.' => 'Liên kết được cập nhật thành công.', + 'Link removed successfully.' => 'Liên kết đã xoá thành công.', + 'Link labels' => 'Liên kết nhãn', + 'Link modification' => 'Liên kết sửa đổi', + 'Opposite label' => 'Ngược lại nhãn', + 'Remove a link' => 'Hủy liên kết', + 'The labels must be different' => 'Các nhãn phải khác nhau', + 'There is no link.' => 'Không có liên kết.', + 'This label must be unique' => 'Nhãn này phải là duy nhất', + 'Unable to create your link.' => 'Không thể tạo liên kết của bạn.', + 'Unable to update your link.' => 'Không thể cập nhật liên kết của bạn.', + 'Unable to remove this link.' => 'Không thể xóa liên kết này.', + 'relates to' => 'liên quan tới', + 'blocks' => 'khối', + 'is blocked by' => 'bị chặn bởi', + 'duplicates' => 'bản sao', + 'is duplicated by' => 'được nhân đôi bởi', + 'is a child of' => 'là một đứa trẻ', + 'is a parent of' => 'là cha mẹ của', + 'targets milestone' => 'mục tiêu cột mốc', + 'is a milestone of' => 'là một mốc quan trọng của', + 'fixes' => 'sửa lỗi', + 'is fixed by' => 'được sửa bởi', + 'This task' => 'Nhiệm vụ này', + '<1h' => '<1 giờ', + '%dh' => '%dh', + 'Expand tasks' => 'Mở rộng các nhiệm vụ', + 'Collapse tasks' => 'Thu hẹp nhiệm vụ', + 'Expand/collapse tasks' => 'Mở rộng / thu gọn nhiệm vụ', + 'Close dialog box' => 'Đóng hộp thoại', + 'Submit a form' => 'Gửi một mẫu', + 'Board view' => 'Quan điểm của hội đồng quản trị', + 'Keyboard shortcuts' => 'Các phím tắt bàn phím', + 'Open board switcher' => 'Open board switcher', + 'Application' => 'Ứng dụng', + 'Compact view' => 'Chế độ xem nhỏ gọn', + 'Horizontal scrolling' => 'Cuộn ngang', + 'Compact/wide view' => 'Chế độ xem nhỏ gọn / rộng', + 'Currency' => 'Tiền tệ', + 'Personal project' => 'Dự án riêng tư', + 'AUD - Australian Dollar' => 'AUD - Đô la Úc', + 'CAD - Canadian Dollar' => 'CAD - Đô la Canada', + 'CHF - Swiss Francs' => 'CHF - Swiss Francs', + 'Custom Stylesheet' => 'Biểu định kiểu Tuỳ chỉnh', + 'EUR - Euro' => 'EUR - Euro', + 'GBP - British Pound' => 'GBP - Bảng Anh', + 'INR - Indian Rupee' => 'INR - Rupi Ấn Độ', + 'JPY - Japanese Yen' => 'JPY - Yên Nhật', + 'NZD - New Zealand Dollar' => 'NZD - Đô la New Zealand', + 'PEN - Peruvian Sol' => 'PEN - Sol Peru', + 'RSD - Serbian dinar' => 'RSD - dinar Serbia', + 'CNY - Chinese Yuan' => 'CNY - Yuan Trung Quốc', + 'USD - US Dollar' => 'Đô la Mỹ - Đô la Mỹ', + 'VES - Venezuelan Bolívar' => 'VES - Bolívar Venezuela', + 'Destination column' => 'Cột đích', + 'Move the task to another column when assigned to a user' => 'Chuyển nhiệm vụ sang cột khác khi được chỉ định cho người dùng', + 'Move the task to another column when assignee is cleared' => 'Di chuyển nhiệm vụ sang cột khác khi người nhận chuyển giao bị xóa', + 'Source column' => 'Cột nguồn', + 'Transitions' => 'Chuyển tiếp', + 'Executer' => 'Executer', + 'Time spent in the column' => 'Thời gian trong cột', + 'Task transitions' => 'Nhiệm vụ chuyển tiếp', + 'Task transitions export' => 'Nhiệm vụ chuyển tiếp xuất khẩu', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Báo cáo này chứa tất cả các cột di chuyển cho mỗi nhiệm vụ với ngày, người sử dụng và thời gian dành cho mỗi quá trình chuyển đổi.', + 'Currency rates' => 'Tỷ giá tiền tệ', + 'Rate' => 'Tỷ lệ', + 'Change reference currency' => 'Thay đổi đồng tiền tham chiếu', + 'Reference currency' => 'Tiền tệ tham khảo', + 'The currency rate has been added successfully.' => 'Tỷ giá đã được thêm thành công.', + 'Unable to add this currency rate.' => 'Không thể thêm tỷ giá tiền tệ này.', + 'Webhook URL' => 'Webhook URL', + '%s removed the assignee of the task %s' => ' %s loại bỏ người nhận chuyển nhượng của nhiệm vụ %s', + 'Information' => 'Thông tin', + 'Check two factor authentication code' => 'Kiểm tra mã xác thực hai lớp', + 'The two factor authentication code is not valid.' => 'Mã xác thực hai lớp không hợp lệ.', + 'The two factor authentication code is valid.' => 'Mã xác thực hai lớp là hợp lệ.', + 'Code' => 'Mã', + 'Two factor authentication' => 'Xác thực hai lớp', + 'This QR code contains the key URI: ' => 'Mã QR này có chứa URI chính:', + 'Check my code' => 'Kiểm tra mã của tôi', + 'Secret key: ' => 'Chìa khoá bí mật: ', + 'Test your device' => 'Kiểm tra thiết bị của bạn', + 'Assign a color when the task is moved to a specific column' => 'Chỉ định một màu khi công việc được chuyển đến một cột cụ thể', + '%s via Kanboard' => ' %s qua Kanboard', + 'Burndown chart' => 'Biểu đồ Burndown', + 'This chart show the task complexity over the time (Work Remaining).' => 'Biểu đồ này cho thấy sự phức tạp của công việc qua thời gian (Work Remaining).', + 'Screenshot taken %s' => 'Đã chụp ảnh chụp màn hình %s', + 'Add a screenshot' => 'Thêm ảnh chụp màn hình', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Chụp ảnh màn hình và nhấn CTRL + V hoặc ⌘ + V để dán vào đây.', + 'Screenshot uploaded successfully.' => 'Ảnh chụp màn hình được tải lên thành công.', + 'SEK - Swedish Krona' => 'SEK - Krona Thu Swedish Điển', + 'Identifier' => 'Bộ nhận dạng', + 'Disable two factor authentication' => 'Vô hiệu hoá hai lớp xác thực', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Bạn có thực sự muốn vô hiệu hóa xác thực hai lớp cho người dùng này: "%s"?', + 'Edit link' => 'Chỉnh sửa liên kết', + 'Start to type task title...' => 'Bắt đầu gõ tên tác vụ ...', + 'A task cannot be linked to itself' => 'Một nhiệm vụ không thể được liên kết với chính nó', + 'The exact same link already exists' => 'Liên kết chính xác đã tồn tại', + 'Recurrent task is scheduled to be generated' => 'Nhiệm vụ lặp lại được dự kiến ​​sẽ được tạo ra', + 'Score' => 'Ghi bàn', + 'The identifier must be unique' => 'Mã nhận diện phải là duy nhất', + 'This linked task id doesn\'t exists' => 'Không có id công việc liên kết nào tồn tại', + 'This value must be alphanumeric' => 'Giá trị này phải là chữ số', + 'Edit recurrence' => 'Chỉnh sửa sự tái phát', + 'Generate recurrent task' => 'Tạo ra công việc lặp đi lặp lại', + 'Trigger to generate recurrent task' => 'Trigger để tạo ra công việc lặp đi lặp lại', + 'Factor to calculate new due date' => 'Yếu tố tính ngày đáo hạn mới', + 'Timeframe to calculate new due date' => 'Khung thời gian để tính toán ngày đáo hạn mới', + 'Base date to calculate new due date' => 'Base date để tính ngày đáo hạn mới', + 'Action date' => 'Ngày hành động', + 'Base date to calculate new due date: ' => 'Căn cứ để tính ngày đáo hạn mới:', + 'This task has created this child task: ' => 'Nhiệm vụ này đã tạo ra nhiệm vụ con này:', + 'Day(s)' => 'Ngày', + 'Existing due date' => 'Ngày hết hạn', + 'Factor to calculate new due date: ' => 'Yếu tố tính ngày đáo hạn mới:', + 'Month(s)' => 'Tháng)', + 'This task has been created by: ' => 'Nhiệm vụ này đã được tạo ra bằng cách:', + 'Recurrent task has been generated:' => 'Nhiệm vụ lặp lại đã được tạo ra:', + 'Timeframe to calculate new due date: ' => 'Khung thời gian tính ngày đáo hạn mới:', + 'Trigger to generate recurrent task: ' => 'Kích hoạt để tạo ra công việc lặp đi lặp lại:', + 'When task is closed' => 'Khi công việc được đóng lại', + 'When task is moved from first column' => 'Khi công việc được di chuyển từ cột đầu tiên', + 'When task is moved to last column' => 'Khi công việc được chuyển đến cột cuối cùng', + 'Year(s)' => 'Năm (s)', + 'Project settings' => 'Cài đặt dự án', + 'Automatically update the start date' => 'Tự động cập nhật ngày bắt đầu', + 'iCal feed' => 'nguồn cấp dữ liệu iCal', + 'Preferences' => 'Sở thích', + 'Security' => 'An ninh', + 'Two factor authentication disabled' => 'Xác thực hai lớp bị vô hiệu', + 'Two factor authentication enabled' => 'Kích hoạt chứng thực hai lớp', + 'Unable to update this user.' => 'Không thể cập nhật người dùng này.', + 'There is no user management for personal projects.' => 'Không có người quản lý người dùng cho các dự án riêng tư.', + 'User that will receive the email' => 'Người dùng sẽ nhận được email', + 'Email subject' => 'Chủ đề email', + 'Date' => 'Ngày', + 'Add a comment log when moving the task between columns' => 'Thêm nhật ký nhận xét khi di chuyển nhiệm vụ giữa các cột', + 'Move the task to another column when the category is changed' => 'Di chuyển công việc sang cột khác khi danh mục được thay đổi', + 'Send a task by email to someone' => 'Gửi một nhiệm vụ qua email cho ai đó', + 'Reopen a task' => 'Mở lại một nhiệm vụ', + 'Notification' => 'Thông báo', + '%s moved the task #%d to the first swimlane' => ' %s di chuyển nhiệm vụ #%d đến người đầu tiên swimlane', + 'Swimlane' => 'Swimlane', + '%s moved the task %s to the first swimlane' => ' %s di chuyển nhiệm vụ %s tới swimlane đầu tiên', + '%s moved the task %s to the swimlane "%s"' => ' %s di chuyển nhiệm vụ %s tới swimlane lội "%s"', + 'This report contains all subtasks information for the given date range.' => 'Báo cáo này chứa tất cả các thông tin phụ cho một phạm vi ngày.', + 'This report contains all tasks information for the given date range.' => 'Báo cáo này chứa tất cả các nhiệm vụ thông tin cho phạm vi ngày nhất định.', + 'Project activities for %s' => 'Các hoạt động dự án cho %s', + 'view the board on Kanboard' => 'xem bảng trên Kanboard', + 'The task has been moved to the first swimlane' => 'Nhiệm vụ đã được dời đến sân vận động đầu tiên', + 'The task has been moved to another swimlane:' => 'Nhiệm vụ đã được chuyển sang một đội swimlane khác: ', + 'New title: %s' => 'Tiêu đề mới: %s', + 'The task is not assigned anymore' => 'Nhiệm vụ không được giao nữa', + 'New assignee: %s' => 'Người được chuyển nhượng mới: %s', + 'There is no category now' => 'Không có danh mục bây giờ', + 'New category: %s' => 'Thể loại mới: %s', + 'New color: %s' => 'Màu mới: %s', + 'New complexity: %d' => 'Sự phức tạp mới: %d', + 'The due date has been removed' => 'Ngày đáo hạn đã bị xoá', + 'There is no description anymore' => 'Không còn mô tả nữa', + 'Recurrence settings has been modified' => 'Cài đặt tái lập đã được sửa đổi', + 'Time spent changed: %sh' => 'Thời gian thay đổi: %sh', + 'Time estimated changed: %sh' => 'Thời gian ước tính thay đổi: %sh', + 'The field "%s" has been updated' => 'Trường "%s" đã được cập nhật', + 'The description has been modified:' => 'Mô tả đã được sửa đổi:', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Bạn có thực sự muốn đóng nhiệm vụ "%s" cũng như tất cả các nhiệm vụ phụ?', + 'I want to receive notifications for:' => 'Tôi muốn nhận thông báo cho:', + 'All tasks' => 'Tất cả các nhiệm vụ', + 'Only for tasks assigned to me' => 'Chỉ đối với những nhiệm vụ được giao cho tôi', + 'Only for tasks created by me' => 'Chỉ cho những công việc do tôi tạo ra', + 'Only for tasks created by me and tasks assigned to me' => 'Chỉ cho những công việc mà tôi tạo ra và được giao cho tôi', + '%%Y-%%m-%%d' => '%%Y - %%m - %%d', + 'Total for all columns' => 'Tổng cho tất cả các cột', + 'You need at least 2 days of data to show the chart.' => 'Bạn cần ít nhất 2 ngày dữ liệu để hiển thị biểu đồ.', + '<15m' => '<15m', + '<30m' => '<30m', + 'Stop timer' => 'Ngừng hẹn giờ', + 'Start timer' => 'Bắt đầu hẹn giờ', + 'My activity stream' => 'Dòng hoạt động của tôi', + 'Search tasks' => 'Các nhiệm vụ tìm kiếm', + 'Reset filters' => 'Đặt lại bộ lọc', + 'My tasks due tomorrow' => 'Nhiệm vụ của tôi vào ngày mai', + 'Tasks due today' => 'Nhiệm vụ hôm nay', + 'Tasks due tomorrow' => 'Nhiệm vụ vào ngày mai', + 'Tasks due yesterday' => 'Nhiệm vụ do ngày hôm qua', + 'Closed tasks' => 'Các nhiệm vụ đã đóng', + 'Open tasks' => 'Mở nhiệm vụ', + 'Not assigned' => 'Không được chỉ định', + 'View advanced search syntax' => 'Xem cú pháp tìm kiếm nâng cao', + 'Overview' => 'Tổng quan', + 'Board/Calendar/List view' => 'Chế độ xem hội đồng quản trị / lịch / danh sách', + 'Switch to the board view' => 'Chuyển sang chế độ xem bảng', + 'Switch to the list view' => 'Chuyển sang chế độ xem danh sách', + 'Go to the search/filter box' => 'Đi tới hộp tìm kiếm / bộ lọc', + 'There is no activity yet.' => 'Không có hoạt động nào.', + 'No tasks found.' => 'Không tìm thấy công việc nào.', + 'Keyboard shortcut: "%s"' => 'Các phím tắt bàn phím "%s"', + 'List' => 'Danh sách', + 'Filter' => 'Lọc', + 'Advanced search' => 'Tìm kiếm nâng cao', + 'Example of query: ' => 'Ví dụ về truy vấn:', + 'Search by project: ' => 'Tìm kiếm theo dự án:', + 'Search by column: ' => 'Tìm kiếm theo cột:', + 'Search by assignee: ' => 'Tìm kiếm bởi người được chuyển nhượng:', + 'Search by color: ' => 'Tìm kiếm theo màu sắc:', + 'Search by category: ' => 'Tìm kiếm theo thể loại:', + 'Search by description: ' => 'Tìm theo mô tả:', + 'Search by due date: ' => 'Tìm kiếm theo ngày đáo hạn:', + 'Average time spent in each column' => 'Thời gian trung bình dành cho từng cột', + 'Average time spent' => 'Thời gian trung bình dành', + 'This chart shows the average time spent in each column for the last %d tasks.' => 'Biểu đồ này cho thấy thời gian trung bình dành cho mỗi cột cho các tác vụ %d cuối cùng.', + 'Average Lead and Cycle time' => 'Thời gian trung bình và thời gian chu kỳ', + 'Average lead time: ' => 'Thời gian dẫn trung bình:', + 'Average cycle time: ' => 'Thời gian chu kỳ trung bình:', + 'Cycle Time' => 'Thời gian chu kỳ', + 'Lead Time' => 'Chì Thời gian', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => 'Biểu đồ này cho thấy thời gian dẫn và thời gian trung bình cho các tác vụ %d cuối cùng qua thời gian.', + 'Average time into each column' => 'Thời gian trung bình vào mỗi cột', + 'Lead and cycle time' => 'Chì và thời gian chu kỳ', + 'Lead time: ' => 'Chì thời gian:', + 'Cycle time: ' => 'Thời gian chu kỳ: ', + 'Time spent in each column' => 'Thời gian dành cho từng cột', + 'The lead time is the duration between the task creation and the completion.' => 'Thời gian dẫn là khoảng thời gian giữa nhiệm vụ sáng tạo và hoàn thành.', + 'The cycle time is the duration between the start date and the completion.' => 'Thời gian chu kỳ là khoảng thời gian giữa ngày bắt đầu và ngày hoàn thành.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Nếu nhiệm vụ không đóng thì thời gian hiện tại được sử dụng thay vì ngày hoàn thành.', + 'Set the start date automatically' => 'Đặt tự động ngày bắt đầu', + 'Edit Authentication' => 'Chỉnh sửa Xác thực', + 'Remote user' => 'Người dùng từ xa', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Người dùng từ xa không lưu mật khẩu của họ trong cơ sở dữ liệu Kanboard, ví dụ: LDAP, Google và Github.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Nếu bạn đánh dấu ô "Disallow form đăng nhập", các thông tin đăng nhập vào mẫu đăng nhập sẽ bị bỏ qua.', + 'Default task color' => 'Màu nhiệm vụ mặc định', + 'This feature does not work with all browsers.' => 'Tính năng này không hoạt động với tất cả các trình duyệt.', + 'There is no destination project available.' => 'Không có dự án điểm đến có sẵn.', + 'Trigger automatically subtask time tracking' => 'Trigger tự động theo dõi thời gian theo dõi', + 'Include closed tasks in the cumulative flow diagram' => 'Bao gồm các nhiệm vụ đã đóng trong sơ đồ luồng tích lũy', + 'Current swimlane: %s' => 'Dòng swimlane hiện tại: %s', + 'Current column: %s' => 'Cột hiện tại: %s', + 'Current category: %s' => 'Thể loại hiện tại: %s', + 'no category' => 'không có loại', + 'Current assignee: %s' => 'Đang chuyển nhượng hiện tại: %s', + 'not assigned' => 'không được chỉ định', + 'Author:' => 'Tác giả:', + 'contributors' => 'người đóng góp', + 'License:' => 'Giấy phép:', + 'License' => 'Giấy phép', + 'Enter the text below' => 'Nhập văn bản dưới đây', + 'Start date:' => 'Ngày bắt đầu:', + 'Due date:' => 'Ngày đáo hạn:', + 'People who are project managers' => 'Những người quản lý dự án', + 'People who are project members' => 'Những người là thành viên của dự án', + 'NOK - Norwegian Krone' => 'NOK - Krone Na Uy', + 'Show this column' => 'Hiển thị cột này', + 'Hide this column' => 'Ẩn cột này', + 'End date' => 'Ngày cuối', + 'Users overview' => 'Tổng quan người dùng', + 'Members' => 'Các thành viên', + 'Shared project' => 'Dự án được chia sẻ', + 'Project managers' => 'Quản lý dự án', + 'Projects list' => 'Danh sách các dự án', + 'End date:' => 'Ngày cuối:', + 'Change task color when using a specific task link' => 'Thay đổi màu nhiệm vụ khi sử dụng liên kết nhiệm vụ cụ thể', + 'Task link creation or modification' => 'Tạo hoặc sửa đổi liên kết nhiệm vụ', + 'Milestone' => 'Milestone', + 'Reset the search/filter box' => 'Đặt lại hộp tìm kiếm / bộ lọc', + 'Documentation' => 'Tài liệu hướng dẫn', + 'Author' => 'Tác giả', + 'Version' => 'Phiên bản', + 'Plugins' => 'Plugins', + 'There is no plugin loaded.' => 'Không có plugin nạp.', + 'My notifications' => 'Thông báo của tôi', + 'Custom filters' => 'Bộ lọc tùy chỉnh', + 'Your custom filter has been created successfully.' => 'Bộ lọc tuỳ chỉnh của bạn đã được tạo thành công.', + 'Unable to create your custom filter.' => 'Không thể tạo bộ lọc tuỳ chỉnh của bạn.', + 'Custom filter removed successfully.' => 'Đã xoá bộ lọc tùy chỉnh.', + 'Unable to remove this custom filter.' => 'Không thể xóa bộ lọc tùy chỉnh này.', + 'Edit custom filter' => 'Chỉnh sửa bộ lọc tùy chỉnh', + 'Your custom filter has been updated successfully.' => 'Bộ lọc tuỳ chỉnh của bạn đã được cập nhật thành công.', + 'Unable to update custom filter.' => 'Không thể cập nhật bộ lọc tuỳ chỉnh.', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Tập tin đính kèm mới trên task #%d: %s', + 'New comment on task #%d' => 'Ý kiến ​​mới về nhiệm vụ #%d', + 'Comment updated on task #%d' => 'Bình luận cập nhật về nhiệm vụ #%d', + 'New subtask on task #%d' => 'Bổ sung phụ về nhiệm vụ #%d', + 'Subtask updated on task #%d' => 'Không cập nhật được nhiệm vụ #%d', + 'New task #%d: %s' => 'Nhiệm vụ mới %d: %s', + 'Task updated #%d' => 'Đã cập nhật nhiệm vụ #%d', + 'Task #%d closed' => 'Nhiệm vụ #%d đóng', + 'Task #%d opened' => 'Nhiệm vụ #%d mở', + 'Column changed for task #%d' => 'Đã thay đổi cột cho tác vụ #%d', + 'New position for task #%d' => 'Vị trí mới cho nhiệm vụ #%d', + 'Swimlane changed for task #%d' => 'Swimlane đã thay đổi cho nhiệm vụ #%d', + 'Assignee changed on task #%d' => 'Người được chỉ định thay đổi về nhiệm vụ #%d', + '%d overdue tasks' => '%d quá hạn tác vụ', + 'No notification.' => 'Không có thông báo.', + 'Mark all as read' => 'Đánh dấu tất cả như đã đọc', + 'Mark as read' => 'Đánh dấu là đã đọc', + 'Total number of tasks in this column across all swimlanes' => 'Tổng số công việc trong cột này trên tất cả các đường swimlane', + 'Collapse swimlane' => 'Thu gọn swimlane', + 'Expand swimlane' => 'Mở rộng swimlane lội', + 'Add a new filter' => 'Thêm một bộ lọc mới', + 'Share with all project members' => 'Chia sẻ với tất cả các thành viên dự án', + 'Shared' => 'Chia sẻ', + 'Owner' => 'Chủ nhân', + 'Unread notifications' => 'Thông báo chưa đọc', + 'Notification methods:' => 'Phương pháp thông báo:', + 'Unable to read your file' => 'Không thể đọc tập tin của bạn', + '%d task(s) have been imported successfully.' => '%d nhiệm vụ đã được nhập thành công.', + 'Nothing has been imported!' => 'Không có gì đã được nhập khẩu!', + 'Import users from CSV file' => 'Nhập khẩu người dùng từ tệp CSV', + '%d user(s) have been imported successfully.' => '%d người dùng đã được nhập thành công.', + 'Comma' => 'Comma', + 'Semi-colon' => 'Bán kết', + 'Tab' => 'Chuyển hướng', + 'Vertical bar' => 'Thanh dọc', + 'Double Quote' => 'Double Quote', + 'Single Quote' => 'Trích dẫn đơn', + '%s attached a file to the task #%d' => ' %s gắn một tập tin vào tác vụ #%d', + 'There is no column or swimlane activated in your project!' => 'Không có cột hoặc swimlane kích hoạt trong dự án của bạn!', + 'Append filter (instead of replacement)' => 'Thêm bộ lọc (thay vì thay thế)', + 'Append/Replace' => 'Thêm / Thay thế', + 'Append' => 'Thêm', + 'Replace' => 'Thay thế', + 'Import' => 'Nhập khẩu', + 'Change sorting' => 'Thay đổi phân loại', + 'Tasks Importation' => 'Nhiệm vụ Nhập khẩu', + 'Delimiter' => 'Delimiter', + 'Enclosure' => 'Bao vây', + 'CSV File' => 'CSV File', + 'Instructions' => 'Hướng dẫn', + 'Your file must use the predefined CSV format' => 'Tệp của bạn phải sử dụng định dạng CSV được xác định trước', + 'Your file must be encoded in UTF-8' => 'Tập tin của bạn phải được mã hoá bằng UTF-8', + 'The first row must be the header' => 'Hàng đầu tiên phải là tiêu đề', + 'Duplicates are not verified for you' => 'Bản sao không được xác minh cho bạn', + 'The due date must use the ISO format: YYYY-MM-DD' => 'Ngày đáo hạn phải sử dụng định dạng ISO: YYYY-MM-DD', + 'Download CSV template' => 'Tải xuống mẫu CSV', + 'No external integration registered.' => 'Không có tích hợp bên ngoài đăng ký.', + 'Duplicates are not imported' => 'Bản sao không được nhập', + 'Usernames must be lowercase and unique' => 'Tên người dùng phải là chữ thường và', + 'Passwords will be encrypted if present' => 'Mật khẩu sẽ được mã hóa nếu có', + '%s attached a new file to the task %s' => ' %s gắn một tập tin mới vào công việc %s', + 'Link type' => 'Loại liên kết', + 'Assign automatically a category based on a link' => 'Chỉ định tự động một thể loại dựa trên liên kết', + 'BAM - Konvertible Mark' => 'BAM - Konvertible Mark', + 'Assignee Username' => 'Assignee Username', + 'Assignee Name' => 'Người được chuyển nhượng', + 'Groups' => 'Các nhóm', + 'Members of %s' => 'Thành viên của %s', + 'New group' => 'Nhóm mới', + 'Group created successfully.' => 'Nhóm được tạo thành công.', + 'Unable to create your group.' => 'Không thể tạo nhóm của bạn.', + 'Edit group' => 'Chỉnh sửa nhóm', + 'Group updated successfully.' => 'Nhóm được cập nhật thành công.', + 'Unable to update your group.' => 'Không thể cập nhật nhóm của bạn.', + 'Add group member to "%s"' => 'Thêm thành viên nhóm vào "%s"', + 'Group member added successfully.' => 'Thành viên nhóm đã thành công.', + 'Unable to add group member.' => 'Không thể thêm thành viên nhóm', + 'Remove user from group "%s"' => 'Xóa người dùng khỏi nhóm %s', + 'User removed successfully from this group.' => 'Người dùng đã xóa thành công khỏi nhóm này.', + 'Unable to remove this user from the group.' => 'Không thể xóa người dùng này khỏi nhóm.', + 'Remove group' => 'Loại bỏ nhóm', + 'Group removed successfully.' => 'Nhóm đã xoá thành công.', + 'Unable to remove this group.' => 'Không thể xóa nhóm này.', + 'Project Permissions' => 'Quyền dự án', + 'Manager' => 'Giám đốc', + 'Project Manager' => 'Quản lý dự án', + 'Project Member' => 'Thành viên Dự án', + 'Project Viewer' => 'Trình xem dự án', + 'Your account is locked for %d minutes' => 'Tài khoản của bạn bị khóa trong %d phút', + 'Invalid captcha' => 'Captcha không hợp lệ', + 'The name must be unique' => 'Cái tên phải là duy nhất', + 'View all groups' => 'Xem tất cả các nhóm', + 'There is no user available.' => 'Không có người dùng nào có sẵn.', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Bạn có thực sự muốn xóa người dùng "%s" khỏi nhóm "%s"?', + 'There is no group.' => 'Không có nhóm.', + 'Add group member' => 'Thêm thành viên nhóm', + 'Do you really want to remove this group: "%s"?' => 'Bạn có thực sự muốn loại bỏ nhóm này: "%s"?', + 'There is no user in this group.' => 'Không có người dùng trong nhóm này.', + 'Permissions' => 'Quyền', + 'Allowed Users' => 'Người dùng được phép', + 'No specific user has been allowed.' => 'Không người dùng nào được cho phép cụ thể.', + 'Role' => 'Vai trò', + 'Enter user name...' => 'Điền tên đăng nhập...', + 'Allowed Groups' => 'Nhóm được phép', + 'No group has been allowed.' => 'Không có nhóm nào được cho phép cụ thể.', + 'Group' => 'Nhóm', + 'Group Name' => 'Tên nhóm', + 'Enter group name...' => 'Nhập tên nhóm ...', + 'Role:' => 'Vai trò:', + 'Project members' => 'Thành viên dự án', + '%s mentioned you in the task #%d' => ' %s đề cập đến bạn trong công việc #%d', + '%s mentioned you in a comment on the task #%d' => ' %s đã đề cập đến bạn trong một nhận xét về nhiệm vụ #%d', + 'You were mentioned in the task #%d' => 'Bạn đã được đề cập trong nhiệm vụ #%d', + 'You were mentioned in a comment on the task #%d' => 'Bạn đã được đề cập trong một nhận xét về nhiệm vụ #%d', + 'Estimated hours: ' => 'Giờ ước tính:', + 'Actual hours: ' => 'Giờ thực tế:', + 'Hours Spent' => 'Thời gian Nghỉ ngơi', + 'Hours Estimated' => 'Số giờ Ước tính', + 'Estimated Time' => 'Thơi gian dự định', + 'Actual Time' => 'Thời gian Thực', + 'Estimated vs actual time' => 'Ước tính thời gian thực', + 'RUB - Russian Ruble' => 'RUB - Nga Ruble', + 'Assign the task to the person who does the action when the column is changed' => 'Chỉ định nhiệm vụ cho người thực hiện hành động khi cột được thay đổi', + 'Close a task in a specific column' => 'Đóng một nhiệm vụ trong một cột cụ thể', + 'Time-based One-time Password Algorithm' => 'Thuật toán mật khẩu một lần dựa trên thời gian', + 'Two-Factor Provider: ' => 'Nhà cung cấp hai nhân tố:', + 'Disable two-factor authentication' => 'Tắt xác thực hai lớp', + 'Enable two-factor authentication' => 'Kích hoạt xác thực hai lớp', + 'There is no integration registered at the moment.' => 'Hiện tại không có hội nhập nào được đăng ký.', + 'Password Reset for Kanboard' => 'Đặt lại mật khẩu cho Kanboard', + 'Forgot password?' => 'Quên mật khẩu?', + 'Enable "Forget Password"' => 'Bật Quên mật khẩu ', + 'Password Reset' => 'Đặt lại mật khẩu', + 'New password' => 'Mật khẩu mới', + 'Change Password' => 'Đổi mật khẩu', + 'To reset your password click on this link:' => 'Để đặt lại mật khẩu của bạn bấm vào liên kết này:', + 'Last Password Reset' => 'Thiết lập lại mật khẩu lần cuối', + 'The password has never been reinitialized.' => 'Mật khẩu chưa bao giờ được khởi tạo lại.', + 'Creation' => 'Sự sáng tạo', + 'Expiration' => 'Hết hạn', + 'Password reset history' => 'Lịch sử đặt lại mật khẩu', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Tất cả nhiệm vụ của cột "%s" và swimlane "%s" đã được đóng thành công.', + 'Do you really want to close all tasks of this column?' => 'Bạn có thực sự muốn đóng tất cả các nhiệm vụ của cột này?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d nhiệm vụ trong cột "%s" và swimlane "%s" sẽ được đóng lại.', + 'Close all tasks in this column and this swimlane' => 'Đóng tất cả các tác vụ của cột này', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Không có plugin đã đăng ký một phương pháp thông báo dự án. Bạn vẫn có thể định cấu hình các thông báo riêng trong hồ sơ người dùng của bạn. ', + 'My dashboard' => 'Bảng điều khiển của tôi', + 'My profile' => 'Thông tin của tôi', + 'Project owner: ' => 'Chủ dự án: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'Định danh dự án là tùy chọn và phải là chữ số, ví dụ: MYPROJECT.', + 'Project owner' => 'Chủ dự án', + 'Personal projects do not have users and groups management.' => 'Các dự án riêng tư không có người dùng và quản lý nhóm.', + 'There is no project member.' => 'Không có thành viên dự án.', + 'Priority' => 'Sự ưu tiên', + 'Task priority' => 'Nhiệm vụ ưu tiên', + 'General' => 'Chung', + 'Dates' => 'Ngày', + 'Default priority' => 'Mức độ ưu tiên mặc định', + 'Lowest priority' => 'Mức ưu tiên thấp nhất', + 'Highest priority' => 'Mức ưu tiên cao nhất', + 'Close a task when there is no activity' => 'Đóng một nhiệm vụ khi không có hoạt động', + 'Duration in days' => 'Thời lượng theo ngày', + 'Send email when there is no activity on a task' => 'Gửi email khi không có hoạt động nào trên một tác vụ', + 'Unable to fetch link information.' => 'Không thể tìm nạp thông tin liên kết.', + 'Daily background job for tasks' => 'Công việc hàng ngày cho các nhiệm vụ', + 'Auto' => 'Tự động', + 'Related' => 'Liên quan', + 'Attachment' => 'Tập tin đính kèm', + 'Web Link' => 'Liên kết Web', + 'External links' => 'Liện kết ngoại', + 'Add external link' => 'Thêm liên kết bên ngoài', + 'Type' => 'Kiểu', + 'Dependency' => 'Sự phụ thuộc', + 'Add internal link' => 'Thêm liên kết nội bộ', + 'Add a new external link' => 'Thêm một liên kết bên ngoài mới', + 'Edit external link' => 'Chỉnh sửa liên kết bên ngoài', + 'External link' => 'Liên kết bên ngoài', + 'Copy and paste your link here...' => 'Sao chép và dán liên kết của bạn ở đây ...', + 'URL' => 'URL', + 'Internal links' => 'Liên kết nội bộ', + 'Assign to me' => 'Chỉ định cho tôi', + 'Me' => 'Tôi', + 'Do not duplicate anything' => 'Đừng trùng lặp bất cứ điều gì', + 'Projects management' => 'Quản lý dự án', + 'Users management' => 'Quản lý người dùng', + 'Groups management' => 'Quản lý nhóm', + 'Create from another project' => 'Tạo ra từ một dự án khác', + 'open' => 'mở', + 'closed' => 'đóng cửa', + 'Priority:' => 'Sự ưu tiên:', + 'Reference:' => 'Tài liệu tham khảo:', + 'Complexity:' => 'Sự phức tạp:', + 'Swimlane:' => 'Swimlane:', + 'Column:' => 'Cột:', + 'Position:' => 'Chức vụ:', + 'Creator:' => 'Người sáng tạo:', + 'Time estimated:' => 'Thời gian ước tính:', + '%s hours' => ' %s hours', + 'Time spent:' => 'Thời gian dành:', + 'Created:' => 'Tạo:', + 'Modified:' => 'Sửa đổi:', + 'Completed:' => 'Đã hoàn thành:', + 'Started:' => 'Đã bắt đầu:', + 'Moved:' => 'Moved:', + 'Task #%d' => 'Nhiệm vụ #%d', + 'Time format' => 'Định dạng thời gian', + 'Start date: ' => 'Ngày bắt đầu:', + 'End date: ' => 'Ngày cuối: ', + 'New due date: ' => 'Ngày đáo hạn mới:', + 'Start date changed: ' => 'Ngày bắt đầu thay đổi:', + 'Disable personal projects' => 'Vô hiệu hoá các dự án riêng tư', + 'Do you really want to remove this custom filter: "%s"?' => 'Bạn có thực sự muốn loại bỏ bộ lọc tuỳ chỉnh này: "%s"?', + 'Remove a custom filter' => 'Xóa một bộ lọc tuỳ chỉnh', + 'User activated successfully.' => 'Người dùng đã kích hoạt thành công.', + 'Unable to enable this user.' => 'Không thể kích hoạt người dùng này.', + 'User disabled successfully.' => 'Người dùng đã vô hiệu thành công.', + 'Unable to disable this user.' => 'Không thể vô hiệu hóa người dùng này.', + 'All files have been uploaded successfully.' => 'Tất cả các tập tin đã được tải lên thành công.', + 'The maximum allowed file size is %sB.' => 'Kích thước tập tin tối đa cho phép là %sB.', + 'Drag and drop your files here' => 'Kéo và thả tệp của bạn ở đây', + 'choose files' => 'chọn tập tin', + 'View profile' => 'Xem hồ sơ', + 'Two Factor' => 'hai lớp', + 'Disable user' => 'Vô hiệu hoá người dùng', + 'Do you really want to disable this user: "%s"?' => 'Bạn thực sự muốn vô hiệu hóa người dùng này: "%s"?', + 'Enable user' => 'Enable user', + 'Do you really want to enable this user: "%s"?' => 'Bạn có thực sự muốn cho phép người dùng này: "%s"?', + 'Download' => 'Tải về', + 'Uploaded: %s' => 'Đã tải lên: %s', + 'Size: %s' => 'Kích thước: %s', + 'Uploaded by %s' => 'Đã tải lên bởi %s', + 'Filename' => 'Filename', + 'Size' => 'Kích thước', + 'Column created successfully.' => 'Cột được tạo thành công.', + 'Another column with the same name exists in the project' => 'Một cột có cùng tên tồn tại trong dự án', + 'Default filters' => 'Bộ lọc mặc định', + 'Your board doesn\'t have any columns!' => 'Bảng của bạn không có bất kỳ cột nào!', + 'Change column position' => 'Thay đổi vị trí cột', + 'Switch to the project overview' => 'Chuyển sang tổng quan về dự án', + 'User filters' => 'Bộ lọc người dùng', + 'Category filters' => 'Bộ lọc danh mục', + 'Upload a file' => 'Tải lên một tài liệu', + 'View file' => 'Xem tài liệu', + 'Last activity' => 'Hoạt động cuối', + 'Change subtask position' => 'Thay đổi vị trí phụ', + 'This value must be greater than %d' => 'Giá trị này phải lớn hơn %d', + 'Another swimlane with the same name exists in the project' => 'Một con tàu khác có cùng tên tồn tại trong dự án ', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => 'Ví dụ: https://example.kanboard.org/ (dùng để tạo URL tuyệt đối)', + 'Actions duplicated successfully.' => 'Hành động trùng lặp thành công.', + 'Unable to duplicate actions.' => 'Không thể lặp lại hành động.', + 'Add a new action' => 'Thêm một hành động mới', + 'Import from another project' => 'Nhập khẩu từ một dự án khác', + 'There is no action at the moment.' => 'Hiện tại không có hành động nào.', + 'Import actions from another project' => 'Nhập khẩu các hành động từ một dự án khác', + 'There is no available project.' => 'Không có dự án sẵn có.', + 'Local File' => 'Local File', + 'Configuration' => 'Cấu hình', + 'PHP version:' => 'Phiên bản PHP:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => 'Phiên bản hệ điều hành:', + 'Database version:' => 'Phiên bản cơ sở dữ liệu:', + 'Browser:' => 'Trình duyệt:', + 'Task view' => 'Công việc xem', + 'Edit task' => 'Chỉnh sửa nhiệm vụ', + 'Edit description' => 'Chỉnh sửa mô tả', + 'New internal link' => 'Liên kết nội bộ mới', + 'Display list of keyboard shortcuts' => 'Hiển thị danh sách các phím tắt', + 'Avatar' => 'Hình đại diện', + 'Upload my avatar image' => 'Tải lên hình ảnh đại diện của tôi', + 'Remove my image' => 'Hủy bỏ hình ảnh của tôi', + 'The OAuth2 state parameter is invalid' => 'Tham số trạng thái OAuth2 không hợp lệ', + 'User not found.' => 'Người dùng không tìm thấy.', + 'Search in activity stream' => 'Tìm kiếm trong luồng hoạt động', + 'My activities' => 'Hoạt động của tôi', + 'Activity until yesterday' => 'Hoạt động cho đến ngày hôm qua', + 'Activity until today' => 'Hoạt động cho đến ngày hôm nay', + 'Search by creator: ' => 'Tìm kiếm theo người sáng tạo:', + 'Search by creation date: ' => 'Tìm kiếm theo ngày tạo:', + 'Search by task status: ' => 'Tìm theo tình trạng công việc:', + 'Search by task title: ' => 'Tìm theo tên công việc:', + 'Activity stream search' => 'Tìm kiếm luồng hoạt động', + 'Projects where "%s" is manager' => 'Dự án nơi "%s" là người quản lý', + 'Projects where "%s" is member' => 'Dự án nơi "%s" là thành viên', + 'Open tasks assigned to "%s"' => 'Mở nhiệm vụ được giao cho "%s"', + 'Closed tasks assigned to "%s"' => 'Đã đóng nhiệm vụ được giao cho "%s"', + 'Assign automatically a color based on a priority' => 'Chỉ định tự động một màu dựa trên một ưu tiên', + 'Overdue tasks for the project(s) "%s"' => 'Các nhiệm vụ quá hạn cho (các) dự án %s', + 'Upload files' => 'Tải tệp lên', + 'Installed Plugins' => 'Plugin cài đặt', + 'Plugin Directory' => 'Plugin Directory', + 'Plugin installed successfully.' => 'Plugin được cài đặt thành công.', + 'Plugin updated successfully.' => 'Plugin được cập nhật thành công.', + 'Plugin removed successfully.' => 'Plugin được xoá thành công.', + 'Subtask converted to task successfully.' => 'Đã thực hiện thành công nhiệm vụ phụ đề đã được chuyển thành.', + 'Unable to convert the subtask.' => 'Không thể chuyển đổi phụ đề.', + 'Unable to extract plugin archive.' => 'Không thể trích xuất kho lưu trữ plugin.', + 'Plugin not found.' => 'Plugin không tìm thấy.', + 'You don\'t have the permission to remove this plugin.' => 'Bạn không có quyền xóa plugin này.', + 'Unable to download plugin archive.' => 'Không thể tải tệp lưu trữ plugin.', + 'Unable to write temporary file for plugin.' => 'Không thể ghi tập tin tạm thời cho plugin.', + 'Unable to open plugin archive.' => 'Không thể mở tệp lưu trữ plugin.', + 'There is no file in the plugin archive.' => 'Không có tệp nào trong kho lưu trữ trình cắm.', + 'Create tasks in bulk' => 'Tạo nhiệm vụ hàng loạt', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Trường hợp Kanboard của bạn không được định cấu hình để cài đặt plugin từ giao diện người dùng.', + 'There is no plugin available.' => 'Không có plugin nào sẵn có.', + 'Install' => 'Cài đặt, dựng lên', + 'Update' => 'Cập nhật', + 'Up to date' => 'Cập nhật', + 'Not available' => 'Không có sẵn', + 'Remove plugin' => 'Xoá plugin', + 'Do you really want to remove this plugin: "%s"?' => 'Bạn có thực sự muốn loại bỏ plugin này: "%s"?', + 'Uninstall' => 'Gỡ cài đặt', + 'Listing' => 'Liệt kê', + 'Metadata' => 'Siêu dữ liệu', + 'Manage projects' => 'Quản lý dự án', + 'Convert to task' => 'Chuyển đổi sang tác vụ', + 'Convert sub-task to task' => 'Chuyển tiểu nhiệm vụ thành nhiệm vụ', + 'Do you really want to convert this sub-task to a task?' => 'Bạn có thực sự muốn chuyển đổi tiểu nhiệm vụ này sang một nhiệm vụ?', + 'My task title' => 'Nhiệm vụ của tôi', + 'Enter one task by line.' => 'Nhập một tác vụ theo dòng.', + 'Number of failed login:' => 'Số lần đăng nhập không thành công:', + 'Account locked until:' => 'Tài khoản bị khoá cho đến khi:', + 'Email settings' => 'Cài đặt email', + 'Email sender address' => 'Địa chỉ người gửi email', + 'Email transport' => 'Vận chuyển email', + 'Webhook token' => 'Mã thông báo của Webhook', + 'Project tags management' => 'Quản lý thẻ dự án', + 'Tag created successfully.' => 'Thẻ được tạo thành công.', + 'Unable to create this tag.' => 'Không thể tạo thẻ này.', + 'Tag updated successfully.' => 'Thẻ được cập nhật thành công.', + 'Unable to update this tag.' => 'Không thể cập nhật thẻ này.', + 'Tag removed successfully.' => 'Thẻ đã xoá thành công.', + 'Unable to remove this tag.' => 'Không thể xóa thẻ này.', + 'Global tags management' => 'Quản lý thẻ toàn cầu', + 'Tags' => 'Thẻ', + 'Tags management' => 'Quản lý thẻ', + 'Add new tag' => 'Thêm thẻ mới', + 'Edit a tag' => 'Chỉnh sửa thẻ', + 'Project tags' => 'Thẻ dự án', + 'There is no specific tag for this project at the moment.' => 'Hiện tại không có thẻ cụ thể cho dự án này.', + 'Tag' => 'Nhãn', + 'Remove a tag' => 'Loại bỏ một thẻ', + 'Do you really want to remove this tag: "%s"?' => 'Bạn có thực sự muốn loại bỏ thẻ này: "%s"?', + 'Global tags' => 'Thẻ toàn cầu', + 'There is no global tag at the moment.' => 'Hiện tại không có nhãn toàn cầu. ', + 'This field cannot be empty' => 'Trường này không thể để trống', + 'Close a task when there is no activity in a specific column' => 'Đóng một nhiệm vụ khi không có hoạt động trong một cột cụ thể', + '%s removed a subtask for the task #%d' => ' %s loại bỏ một subtask cho nhiệm vụ #%d', + '%s removed a comment on the task #%d' => ' %s xóa một nhận xét về nhiệm vụ #%d', + 'Comment removed on task #%d' => 'Đã xóa nhận xét về công việc #%d', + 'Subtask removed on task #%d' => 'Huỷ bỏ các nhiệm vụ #%d', + 'Hide tasks in this column in the dashboard' => 'Ẩn nhiệm vụ trong cột này trong bảng điều khiển', + '%s removed a comment on the task %s' => ' %s xóa một nhận xét về nhiệm vụ %s', + '%s removed a subtask for the task %s' => ' %s loại bỏ một phụ cho nhiệm vụ %s', + 'Comment removed' => 'Đã xóa nhận xét', + 'Subtask removed' => 'Huỷ bỏ bỏ', + '%s set a new internal link for the task #%d' => ' %s thiết lập một liên kết nội bộ mới cho nhiệm vụ #%d', + '%s removed an internal link for the task #%d' => ' %s đã xóa liên kết nội bộ cho tác vụ #%d', + 'A new internal link for the task #%d has been defined' => 'Một liên kết nội bộ mới cho nhiệm vụ #%d đã được xác định', + 'Internal link removed for the task #%d' => 'Đã loại bỏ liên kết nội bộ cho tác vụ #%d', + '%s set a new internal link for the task %s' => ' %s thiết lập một liên kết nội bộ mới cho nhiệm vụ %s', + '%s removed an internal link for the task %s' => ' %s đã xóa liên kết nội bộ cho tác vụ %s', + 'Automatically set the due date on task creation' => 'Tự động thiết lập ngày đáo hạn khi tạo tác vụ', + 'Move the task to another column when closed' => 'Di chuyển nhiệm vụ sang cột khác khi đóng', + 'Move the task to another column when not moved during a given period' => 'Di chuyển công việc sang cột khác khi không di chuyển trong một khoảng thời gian nhất định', + 'Dashboard for %s' => 'Bảng điều khiển cho %s', + 'Tasks overview for %s' => 'Tổng quan về các nhiệm vụ cho %s', + 'Subtasks overview for %s' => 'Tổng quan về các công việc cho %s', + 'Projects overview for %s' => 'Tổng quan về dự án cho %s', + 'Activity stream for %s' => 'Luồng hoạt động cho %s', + 'Assign a color when the task is moved to a specific swimlane' => 'Chỉ định một màu sắc khi nhiệm vụ được chuyển đến một swimlane cụ thể', + 'Assign a priority when the task is moved to a specific swimlane' => 'Chỉ định một ưu tiên khi nhiệm vụ được chuyển đến một con swimlane cụ thể', + 'User unlocked successfully.' => 'Người dùng đã mở khóa thành công.', + 'Unable to unlock the user.' => 'Không thể mở khóa người dùng.', + 'Move a task to another swimlane' => 'Di chuyển một nhiệm vụ đến một swimlane khác', + 'Creator Name' => 'Tên người tạo', + 'Time spent and estimated' => 'Thời gian chi tiêu và ước tính', + 'Move position' => 'Di chuyển vị trí', + 'Move task to another position on the board' => 'Chuyển nhiệm vụ sang vị trí khác trên bảng', + 'Insert before this task' => 'Chèn trước khi tác vụ này', + 'Insert after this task' => 'Chèn sau khi tác vụ này', + 'Unlock this user' => 'Mở khóa người dùng này', + 'Custom Project Roles' => 'Vai trò dự án tùy chỉnh', + 'Add a new custom role' => 'Thêm một vai trò tùy chỉnh mới', + 'Restrictions for the role "%s"' => 'Hạn chế đối với vai trò %s', + 'Add a new project restriction' => 'Thêm một hạn chế dự án mới', + 'Add a new drag and drop restriction' => 'Thêm một hạn chế kéo và thả mới', + 'Add a new column restriction' => 'Thêm một cột mới', + 'Edit this role' => 'Chỉnh sửa vai trò này', + 'Remove this role' => 'Loại bỏ vai trò này', + 'There is no restriction for this role.' => 'Không có sự hạn chế đối với vai trò này.', + 'Only moving task between those columns is permitted' => 'Chỉ di chuyển nhiệm vụ giữa các cột đó được phép', + 'Close a task in a specific column when not moved during a given period' => 'Đóng một nhiệm vụ trong một cột cụ thể khi không di chuyển trong một khoảng thời gian nhất định', + 'Edit columns' => 'Chỉnh sửa cột', + 'The column restriction has been created successfully.' => 'Hạn chế cột đã được tạo thành công.', + 'Unable to create this column restriction.' => 'Không thể tạo giới hạn cột này.', + 'Column restriction removed successfully.' => 'Hạn chế cột đã được xoá thành công.', + 'Unable to remove this restriction.' => 'Không thể xóa bỏ hạn chế này.', + 'Your custom project role has been created successfully.' => 'Vai trò dự án tùy chỉnh của bạn đã được tạo thành công.', + 'Unable to create custom project role.' => 'Không thể tạo vai trò dự án tùy chỉnh.', + 'Your custom project role has been updated successfully.' => 'Vai trò dự án tùy chỉnh của bạn đã được cập nhật thành công.', + 'Unable to update custom project role.' => 'Không thể cập nhật vai trò dự án tùy chỉnh.', + 'Custom project role removed successfully.' => 'Vai trò dự án đã được gỡ bỏ thành công.', + 'Unable to remove this project role.' => 'Không thể xóa vai trò dự án này.', + 'The project restriction has been created successfully.' => 'Sự hạn chế của dự án đã được tạo ra thành công.', + 'Unable to create this project restriction.' => 'Không thể tạo ra sự hạn chế của dự án.', + 'Project restriction removed successfully.' => 'Hạn chế dự án đã được loại bỏ thành công.', + 'You cannot create tasks in this column.' => 'Bạn không thể tạo ra các nhiệm vụ trong cột này.', + 'Task creation is permitted for this column' => 'Tạo tác vụ được cho phép cho cột này', + 'Closing or opening a task is permitted for this column' => 'Cho phép Đóng hay mở một nhiệm vụ cho cột này', + 'Task creation is blocked for this column' => 'Tạo tác vụ bị chặn cho cột này', + 'Closing or opening a task is blocked for this column' => 'Đóng hoặc mở một tác vụ bị chặn cho cột này', + 'Task creation is not permitted' => 'Không được phép tạo công việc', + 'Closing or opening a task is not permitted' => 'Không được phép đóng hay mở một nhiệm vụ', + 'New drag and drop restriction for the role "%s"' => 'Hạn chế kéo và thả mới cho vai trò "%s"', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Những người thuộc vai trò này sẽ chỉ có thể di chuyển nhiệm vụ giữa cột nguồn và cột đích.', + 'Remove a column restriction' => 'Hủy bỏ một hạn chế về cột', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Bạn có thực sự muốn loại bỏ giới hạn cột này: "%s" thành "%s"?', + 'New column restriction for the role "%s"' => 'Hạn chế cột mới cho vai trò %s', + 'Rule' => 'Qui định', + 'Do you really want to remove this column restriction?' => 'Bạn có thực sự muốn loại bỏ hạn chế cột này?', + 'Custom roles' => 'Vai trò tùy chỉnh', + 'New custom project role' => 'Vai trò dự án mới tùy chỉnh', + 'Edit custom project role' => 'Chỉnh sửa vai trò dự án tùy chỉnh', + 'Remove a custom role' => 'Loại bỏ một vai trò tùy chỉnh', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => 'Bạn có thực sự muốn loại bỏ vai trò tùy chỉnh này: "%s"? Tất cả mọi người được giao nhiệm vụ này sẽ trở thành thành viên của dự án.', + 'There is no custom role for this project.' => 'Không có vai trò tùy chỉnh cho dự án này.', + 'New project restriction for the role "%s"' => 'Hạn chế dự án mới cho vai trò "%s"', + 'Restriction' => 'Sự hạn chế', + 'Remove a project restriction' => 'Hủy bỏ một dự án hạn chế', + 'Do you really want to remove this project restriction: "%s"?' => 'Bạn có thực sự muốn loại bỏ hạn chế dự án này: "%s"?', + 'Duplicate to multiple projects' => 'Nhân đôi với nhiều dự án', + 'This field is required' => 'Trường này là bắt buộc', + 'Moving a task is not permitted' => 'Không được phép di chuyển một nhiệm vụ', + 'This value must be in the range %d to %d' => 'Giá trị này phải nằm trong khoảng từ %d đến %d', + 'You are not allowed to move this task.' => 'Bạn không được phép di chuyển nhiệm vụ này.', + 'API User Access' => 'Truy cập Người dùng API', + 'Preview' => 'Xem trước', + 'Write' => 'Viết', + 'Write your text in Markdown' => 'Viết văn bản của bạn trong Markdown', + 'No personal API access token registered.' => 'Không có đăng nhập truy cập API cá nhân.', + 'Your personal API access token is "%s"' => 'Mã thông báo truy cập API cá nhân của bạn là "%s"', + 'Remove your token' => 'Hủy bỏ mã thông báo của bạn', + 'Generate a new token' => 'Tạo mã thông báo mới', + 'Showing %d-%d of %d' => 'Hiển thị %d-%d của %d', + 'Outgoing Emails' => 'Email gửi đi', + 'Add or change currency rate' => 'Thêm hoặc thay đổi tỷ lệ tiền tệ', + 'Reference currency: %s' => 'Tiền tệ tham khảo: %s', + 'Add custom filters' => 'Thêm bộ lọc tùy chỉnh', + 'Export' => 'Xuất khẩu', + 'Add link label' => 'Thêm nhãn liên kết', + 'Incompatible Plugins' => 'Plugin không tương thích', + 'Compatibility' => 'Khả năng tương thích', + 'Permissions and ownership' => 'Quyền và quyền sở hữu', + 'Priorities' => 'Ưu tiên', + 'Close this window' => 'Đóng cửa sổ này', + 'Unable to upload this file.' => 'Không thể tải tệp này lên.', + 'Import tasks' => 'Nhập khẩu các nhiệm vụ', + 'Choose a project' => 'Chọn một dự án', + 'Profile' => 'Hồ sơ', + 'Application role' => 'Vai trò ứng dụng', + '%d invitations were sent.' => '%d lời mời đã được gửi.', + '%d invitation was sent.' => 'Thư mời %d đã được gửi.', + 'Unable to create this user.' => 'Không thể tạo người dùng này.', + 'Kanboard Invitation' => 'Lời mời của Kanboard', + 'Visible on dashboard' => 'Hiển thị trên bảng điều khiển', + 'Created at:' => 'Được tạo vào:', + 'Updated at:' => 'Cập nhật tại:', + 'There is no custom filter.' => 'Không có bộ lọc tuỳ chỉnh.', + 'New User' => 'Người dùng mới', + 'Authentication' => 'Xác thực', + 'If checked, this user will use a third-party system for authentication.' => 'Nếu được chọn, người dùng này sẽ sử dụng hệ thống của bên thứ ba để xác thực.', + 'The password is necessary only for local users.' => 'Mật khẩu là cần thiết chỉ dành cho người dùng cục bộ.', + 'You have been invited to register on Kanboard.' => 'Bạn đã được mời đăng ký trên Kanboard.', + 'Click here to join your team' => 'Nhấp vào đây để tham gia nhóm của bạn', + 'Invite people' => 'Mời mọi người', + 'Emails' => 'Email', + 'Enter one email address by line.' => 'Nhập một địa chỉ email theo dòng.', + 'Add these people to this project' => 'Thêm những người này vào dự án này', + 'Add this person to this project' => 'Thêm người này vào dự án này', + 'Sign-up' => 'Đăng ký', + 'Credentials' => 'Thông tin xác thực', + 'New user' => 'Người dùng mới', + 'This username is already taken' => 'Tên người dùng này đã được dùng', + 'Your profile must have a valid email address.' => 'Tiểu sử của bạn phải có địa chỉ email hợp lệ.', + 'TRL - Turkish Lira' => 'TRL - Lira Thổ Nhĩ Kỳ', + 'The project email is optional and could be used by several plugins.' => 'Email dự án là tùy chọn và có thể được sử dụng bởi một số plugin.', + 'The project email must be unique across all projects' => 'Email của dự án phải là duy nhất trong tất cả các dự án', + 'The email configuration has been disabled by the administrator.' => 'Quản trị viên đã vô hiệu cấu hình email.', + 'Close this project' => 'Đóng dự án này', + 'Open this project' => 'Mở dự án này', + 'Close a project' => 'Đóng một dự án', + 'Do you really want to close this project: "%s"?' => 'Bạn có thực sự muốn đóng dự án này: "%s"?', + 'Reopen a project' => 'Mở lại một dự án', + 'Do you really want to reopen this project: "%s"?' => 'Bạn có thực sự muốn mở lại dự án này: "%s"?', + 'This project is open' => 'Dự án này đang mở', + 'This project is closed' => 'Dự án này đã kết thúc', + 'Unable to upload files, check the permissions of your data folder.' => 'Không thể tải tệp lên, hãy kiểm tra quyền của thư mục dữ liệu của bạn.', + 'Another category with the same name exists in this project' => 'Một thể loại khác có cùng tên tồn tại trong dự án này', + 'Comment sent by email successfully.' => 'Thảo luận gửi qua email thành công.', + 'Sent by email to "%s" (%s)' => 'Gửi qua email tới "%s" ( %s)', + 'Unable to read uploaded file.' => 'Không thể đọc tập tin được tải lên.', + 'Database uploaded successfully.' => 'Cơ sở dữ liệu được tải lên thành công.', + 'Task sent by email successfully.' => 'Nhiệm vụ gửi qua email thành công.', + 'There is no category in this project.' => 'Không có hạng mục trong dự án này.', + 'Send by email' => 'Gửi bằng thư điện tử', + 'Create and send a comment by email' => 'Tạo và gửi một nhận xét qua email', + 'Subject' => 'Môn học', + 'Upload the database' => 'Tải lên cơ sở dữ liệu', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => 'Bạn có thể tải lên cơ sở dữ liệu Sqlite đã tải xuống trước đây (định dạng Gzip)', + 'Database file' => 'Tập tin cơ sở dữ liệu', + 'Upload' => 'Tải lên', + 'Your project must have at least one active swimlane.' => 'Dự án của bạn phải có ít nhất một swimlane hoạt động.', + 'Project: %s' => 'Dự án: %s', + 'Automatic action not found: "%s"' => 'Không tìm thấy hành động tự động: "%s"', + '%d projects' => '%d dự án', + '%d project' => '%d dự án', + 'There is no project.' => 'Không có dự án.', + 'Sort' => 'Sắp xếp', + 'Project ID' => 'ID dự án', + 'Project name' => 'Tên dự án', + 'Public' => 'Công cộng', + 'Personal' => 'Riêng tư', + '%d tasks' => '%d nhiệm vụ', + '%d task' => '%d công việc', + 'Task ID' => 'ID công việc', + 'Assign automatically a color when due date is expired' => 'Chỉ định màu tự động khi ngày hết hạn hết hạn', + 'Total score in this column across all swimlanes' => 'Tổng số điểm trong cột này trên tất cả các đường swimlane', + 'HRK - Kuna' => 'HRK - Kuna', + 'ARS - Argentine Peso' => 'ARS - Peso Argentina', + 'COP - Colombian Peso' => 'COP - Colombia Peso', + '%d groups' => '%d nhóm', + '%d group' => '%d nhóm', + 'Group ID' => 'ID nhóm', + 'External ID' => 'ID bên ngoài', + '%d users' => '%d người dùng', + '%d user' => '%d người dùng', + 'Hide subtasks' => 'Ẩn các nhiệm vụ phụ', + 'Show subtasks' => 'Hiện các nhiệm vụ phụ', + 'Authentication Parameters' => 'Thông số xác thực', + 'API Access' => 'Truy cập API', + 'No users found.' => 'Không tìm thấy người dùng.', + 'User ID' => 'Tên người dùng', + 'Notifications are activated' => 'Thông báo được kích hoạt', + 'Notifications are disabled' => 'Thông báo bị vô hiệu hóa', + 'User disabled' => 'Người dùng đã vô hiệu hóa', + '%d notifications' => '%d thông báo', + '%d notification' => '%d thông báo', + 'There is no external integration installed.' => 'Không có tích hợp bên ngoài được cài đặt.', + 'You are not allowed to update tasks assigned to someone else.' => 'Bạn không được phép cập nhật các nhiệm vụ được giao cho người khác.', + 'You are not allowed to change the assignee.' => 'Bạn không được phép thay đổi người được chuyển nhượng.', + 'Task suppression is not permitted' => 'Không được phép loại bỏ công việc', + 'Changing assignee is not permitted' => 'Không được phép thay đổi người được chuyển nhượng', + 'Update only assigned tasks is permitted' => 'Chỉ được phép cập nhật các nhiệm vụ được giao', + 'Only for tasks assigned to the current user' => 'Chỉ cho các nhiệm vụ được gán cho người dùng hiện tại', + 'My projects' => 'Dự án của tôi', + 'You are not a member of any project.' => 'Bạn không phải là thành viên của bất kỳ dự án nào.', + 'My subtasks' => 'Nhiệm vụ phụ của tôi', + '%d subtasks' => '%d nhiệm vụ phụ', + '%d subtask' => '%d nhiệm vụ phụ', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => 'Chỉ di chuyển nhiệm vụ giữa các cột được phép cho các nhiệm vụ được giao cho người sử dụng hiện tại', + '[DUPLICATE]' => '[BẢN SAO]', + 'DKK - Danish Krona' => 'DKK - Đan Mạch Đan Mạch', + 'Remove user from group' => 'Xóa người dùng khỏi nhóm', + 'Assign the task to its creator' => 'Chỉ định nhiệm vụ cho người sáng tạo', + 'This task was sent by email to "%s" with subject "%s".' => 'Nhiệm vụ này được gửi qua email tới "%s" với chủ đề "%s".', + 'Predefined Email Subjects' => 'Đối tượng Email được Xác định trước', + 'Write one subject by line.' => 'Viết một chủ đề theo dòng.', + 'Create another link' => 'Tạo liên kết khác', + 'BRL - Brazilian Real' => 'BRL - Brazilian Real', + 'Add a new Kanboard task' => 'Thêm nhiệm vụ Kanboard mới', + 'Subtask not started' => 'Nhiệm vụ con chưa bắt đầu', + 'Subtask currently in progress' => 'Nhiệm vụ con đang tiến hành', + 'Subtask completed' => 'Nhiệm vụ con đã hoàn thành', + 'Subtask added successfully.' => 'Nhiệm vụ con đã được thêm thành công.', + '%d subtasks added successfully.' => '%d nhiệm vụ con đã được thêm thành công.', + 'Enter one subtask by line.' => 'Nhập mỗi nhiệm vụ con trên một dòng.', + 'Predefined Contents' => 'Nội dung được xác định trước', + 'Predefined contents' => 'Nội dung được xác định trước', + 'Predefined Task Description' => 'Mô tả nhiệm vụ được xác định trước', + 'Do you really want to remove this template? "%s"' => 'Bạn có thực sự muốn xóa mẫu này? "%s"', + 'Add predefined task description' => 'Thêm mô tả nhiệm vụ được xác định trước', + 'Predefined Task Descriptions' => 'Mô tả nhiệm vụ được xác định trước', + 'Template created successfully.' => 'Mẫu đã được tạo thành công.', + 'Unable to create this template.' => 'Không thể tạo mẫu này.', + 'Template updated successfully.' => 'Mẫu đã được cập nhật thành công.', + 'Unable to update this template.' => 'Không thể cập nhật mẫu này.', + 'Template removed successfully.' => 'Mẫu đã được xóa thành công.', + 'Unable to remove this template.' => 'Không thể xóa mẫu này.', + 'Template for the task description' => 'Mẫu cho mô tả nhiệm vụ', + 'The start date is greater than the end date' => 'Ngày bắt đầu lớn hơn ngày kết thúc', + 'Tags must be separated by a comma' => 'Các thẻ phải được phân tách bằng dấu phẩy', + 'Only the task title is required' => 'Chỉ tiêu đề nhiệm vụ là bắt buộc', + 'Creator Username' => 'Tên người dùng người tạo', + 'Color Name' => 'Tên màu', + 'Column Name' => 'Tên cột', + 'Swimlane Name' => 'Tên làn', + 'Time Estimated' => 'Thời gian ước tính', + 'Time Spent' => 'Thời gian đã sử dụng', + 'External Link' => 'Liên kết ngoài', + 'This feature enables the iCal feed, RSS feed and the public board view.' => 'Tính năng này cho phép nguồn cấp dữ liệu iCal, nguồn cấp dữ liệu RSS và chế độ xem bảng công khai.', + 'Stop the timer of all subtasks when moving a task to another column' => 'Dừng bộ hẹn giờ của tất cả các nhiệm vụ con khi di chuyển một nhiệm vụ sang cột khác', + 'Subtask Title' => 'Tiêu đề nhiệm vụ con', + 'Add a subtask and activate the timer when moving a task to another column' => 'Thêm nhiệm vụ con và kích hoạt bộ hẹn giờ khi di chuyển một nhiệm vụ sang cột khác', + 'days' => 'ngày', + 'minutes' => 'phút', + 'seconds' => 'giây', + 'Assign automatically a color when preset start date is reached' => 'Tự động gán màu khi đạt đến ngày bắt đầu cài đặt trước', + 'Move the task to another column once a predefined start date is reached' => 'Di chuyển nhiệm vụ sang cột khác sau khi đạt đến ngày bắt đầu được xác định trước', + 'This task is now linked to the task %s with the relation "%s"' => 'Nhiệm vụ này hiện được liên kết với nhiệm vụ %s với quan hệ "%s"', + 'The link with the relation "%s" to the task %s has been removed' => 'Liên kết với quan hệ "%s" đến nhiệm vụ %s đã bị xóa', + 'Custom Filter:' => 'Bộ lọc tùy chỉnh:', + 'Unable to find this group.' => 'Không tìm thấy nhóm này.', + '%s moved the task #%d to the column "%s"' => '%s đã di chuyển nhiệm vụ #%d sang cột "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => '%s đã di chuyển nhiệm vụ #%d đến vị trí %d trong cột "%s"', + '%s moved the task #%d to the swimlane "%s"' => '%s đã di chuyển nhiệm vụ #%d đến làn "%s"', + '%sh spent' => '%sh đã dùng', + '%sh estimated' => '%sh ước tính', + 'Select All' => 'Chọn tất cả', + 'Unselect All' => 'Bỏ chọn tất cả', + 'Apply action' => 'Áp dụng hành động', + 'Move selected tasks to another column or swimlane' => 'Di chuyển các nhiệm vụ đã chọn sang cột hoặc làn khác', + 'Edit tasks in bulk' => 'Chỉnh sửa hàng loạt nhiệm vụ', + 'Choose the properties that you would like to change for the selected tasks.' => 'Chọn các thuộc tính mà bạn muốn thay đổi cho các nhiệm vụ đã chọn.', + 'Configure this project' => 'Cấu hình dự án này', + 'Start now' => 'Bắt đầu ngay', + '%s removed a file from the task #%d' => '%s đã xóa một tệp khỏi nhiệm vụ #%d', + 'Attachment removed from task #%d: %s' => 'Đã xóa tệp đính kèm khỏi nhiệm vụ #%d: %s', + 'No color' => 'Không màu', + 'Attachment removed "%s"' => 'Đã xóa tệp đính kèm "%s"', + '%s removed a file from the task %s' => '%s đã xóa một tệp khỏi nhiệm vụ %s', + 'Move the task to another swimlane when assigned to a user' => 'Di chuyển nhiệm vụ sang làn khác khi được giao cho người dùng', + 'Destination swimlane' => 'Làn đích', + 'Assign a category when the task is moved to a specific swimlane' => 'Chỉ định danh mục khi nhiệm vụ được di chuyển đến một làn cụ thể', + 'Move the task to another swimlane when the category is changed' => 'Di chuyển nhiệm vụ sang làn khác khi danh mục được thay đổi', + 'Reorder this column by priority (ASC)' => 'Sắp xếp lại cột này theo mức ưu tiên (Tăng dần)', + 'Reorder this column by priority (DESC)' => 'Sắp xếp lại cột này theo mức ưu tiên (Giảm dần)', + 'Reorder this column by assignee and priority (ASC)' => 'Sắp xếp lại cột này theo người được giao và mức ưu tiên (Tăng dần)', + 'Reorder this column by assignee and priority (DESC)' => 'Sắp xếp lại cột này theo người được giao và mức ưu tiên (Giảm dần)', + 'Reorder this column by assignee (A-Z)' => 'Sắp xếp lại cột này theo người được giao (A-Z)', + 'Reorder this column by assignee (Z-A)' => 'Sắp xếp lại cột này theo người được giao (Z-A)', + 'Reorder this column by due date (ASC)' => 'Sắp xếp lại cột này theo ngày đến hạn (Tăng dần)', + 'Reorder this column by due date (DESC)' => 'Sắp xếp lại cột này theo ngày đến hạn (Giảm dần)', + 'Reorder this column by id (ASC)' => 'Sắp xếp lại cột này theo id (Tăng dần)', + 'Reorder this column by id (DESC)' => 'Sắp xếp lại cột này theo id (Giảm dần)', + '%s moved the task #%d "%s" to the project "%s"' => '%s đã di chuyển nhiệm vụ #%d "%s" sang dự án "%s"', + 'Task #%d "%s" has been moved to the project "%s"' => 'Nhiệm vụ #%d "%s" đã được di chuyển sang dự án "%s"', + 'Move the task to another column when the due date is less than a certain number of days' => 'Di chuyển nhiệm vụ sang cột khác khi ngày đến hạn còn ít hơn một số ngày nhất định', + 'Automatically update the start date when the task is moved away from a specific column' => 'Tự động cập nhật ngày bắt đầu khi nhiệm vụ được di chuyển khỏi một cột cụ thể', + 'HTTP Client:' => 'Máy khách HTTP:', + 'Assigned' => 'Đã giao', + 'Task limits apply to each swimlane individually' => 'Giới hạn nhiệm vụ áp dụng cho từng làn riêng lẻ', + 'Column task limits apply to each swimlane individually' => 'Giới hạn nhiệm vụ của cột áp dụng cho từng làn riêng lẻ', + 'Column task limits are applied to each swimlane individually' => 'Giới hạn nhiệm vụ của cột được áp dụng cho từng làn riêng lẻ', + 'Column task limits are applied across swimlanes' => 'Giới hạn nhiệm vụ của cột được áp dụng trên các làn', + 'Task limit: ' => 'Giới hạn nhiệm vụ: ', + 'Change to global tag' => 'Thay đổi thành thẻ toàn cầu', + 'Do you really want to make the tag "%s" global?' => 'Bạn có thực sự muốn đặt thẻ "%s" thành toàn cầu không?', + 'Enable global tags for this project' => 'Bật thẻ toàn cầu cho dự án này', + 'Group membership(s):' => 'Thành viên nhóm:', + '%s is a member of the following group(s): %s' => '%s là thành viên của (các) nhóm sau: %s', + '%d/%d group(s) shown' => 'Hiển thị %d/%d nhóm', + 'Subtask creation or modification' => 'Tạo hoặc sửa đổi nhiệm vụ con', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => 'Giao nhiệm vụ cho người dùng cụ thể khi nhiệm vụ được di chuyển đến một làn cụ thể', + 'Comment' => 'Bình luận', + 'Collapse vertically' => 'Thu gọn theo chiều dọc', + 'Expand vertically' => 'Mở rộng theo chiều dọc', + 'MXN - Mexican Peso' => 'MXN - Peso Mexico', + 'Estimated vs actual time per column' => 'Thời gian ước tính so với thời gian thực tế trên mỗi cột', + 'HUF - Hungarian Forint' => 'HUF - Forint Hungary', + 'XBT - Bitcoin' => 'XBT - Bitcoin', + 'You must select a file to upload as your avatar!' => 'Bạn phải chọn một tệp để tải lên làm ảnh đại diện của mình!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => 'Tệp bạn tải lên không phải là hình ảnh hợp lệ! (Chỉ cho phép các định dạng *.gif, *.jpg, *.jpeg và *.png!)', + 'Automatically set the due date when the task is moved away from a specific column' => 'Tự động đặt ngày đến hạn khi nhiệm vụ được di chuyển khỏi một cột cụ thể', + 'No other projects found.' => 'Không tìm thấy dự án nào khác.', + 'Tasks copied successfully.' => 'Các nhiệm vụ đã được sao chép thành công.', + 'Unable to copy tasks.' => 'Không thể sao chép các nhiệm vụ.', + 'Theme' => 'Chủ đề', + 'Theme:' => 'Chủ đề:', + 'Light theme' => 'Chủ đề sáng', + 'Dark theme' => 'Chủ đề tối', + 'Automatic theme - Sync with system' => 'Chủ đề tự động - Đồng bộ hóa với hệ thống', + 'Application managers or more' => 'Người quản lý ứng dụng trở lên', + 'Administrators' => 'Quản trị viên', + 'Visibility:' => 'Hiển thị:', + 'Standard users' => 'Người dùng tiêu chuẩn', + 'Visibility is required' => 'Yêu cầu hiển thị', + 'The visibility should be an app role' => 'Hiển thị phải là một vai trò ứng dụng', + 'Reply' => 'Trả lời', + '%s wrote: ' => '%s đã viết: ', + 'Number of visible tasks in this column and swimlane' => 'Số lượng nhiệm vụ hiển thị trong cột và làn này', + 'Number of tasks in this swimlane' => 'Số lượng nhiệm vụ trong làn này', + 'Unable to find another subtask in progress, you can close this window.' => 'Không tìm thấy nhiệm vụ con nào khác đang xử lý, bạn có thể đóng cửa sổ này.', + 'This theme is invalid' => 'Chủ đề này không hợp lệ', + 'This role is invalid' => 'Vai trò này không hợp lệ', + 'This timezone is invalid' => 'Múi giờ này không hợp lệ', + 'This language is invalid' => 'Ngôn ngữ này không hợp lệ', + 'This URL is invalid' => 'URL này không hợp lệ', + 'Date format invalid' => 'Định dạng ngày không hợp lệ', + 'Time format invalid' => 'Định dạng thời gian không hợp lệ', + 'Invalid Mail transport' => 'Vận chuyển thư không hợp lệ', + 'Color invalid' => 'Màu không hợp lệ', + 'This value must be greater or equal to %d' => 'Giá trị này phải lớn hơn hoặc bằng %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => 'Thêm BOM vào đầu tệp (bắt buộc đối với Microsoft Excel)', + 'Just add these tag(s)' => 'Chỉ cần thêm (các) thẻ này', + 'Remove internal link(s)' => 'Xóa (các) liên kết nội bộ', + 'Import tasks from another project' => 'Nhập nhiệm vụ từ dự án khác', + 'Select the project to copy tasks from' => 'Chọn dự án để sao chép nhiệm vụ từ đó', + 'The total maximum allowed attachments size is %sB.' => 'Tổng kích thước tệp đính kèm tối đa được phép là %sB.', + 'Add attachments' => 'Thêm tệp đính kèm', + 'Task #%d "%s" is overdue' => 'Nhiệm vụ #%d "%s" đã quá hạn', + 'Enable notifications by default for all new users' => 'Kích hoạt thông báo theo mặc định cho tất cả người dùng mới', + 'Assign the task to its creator for specific columns if no assignee is set manually' => 'Gán công việc cho người tạo nó đối với các cột cụ thể nếu chưa đặt người phụ trách thủ công', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => 'Gán công việc cho người dùng đang đăng nhập khi chuyển cột sang cột được chỉ định nếu chưa có người dùng được gán', +]; diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php new file mode 100644 index 0000000..34f7503 --- /dev/null +++ b/app/Locale/zh_CN/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => '.', + 'number.thousands_separator' => ',', + 'None' => '无', + 'Edit' => '编辑', + 'Remove' => '移除', + 'Yes' => '是', + 'No' => '否', + 'cancel' => '取消', + 'or' => '或者', + 'Yellow' => '黄色', + 'Blue' => '蓝色', + 'Green' => '绿色', + 'Purple' => '紫色', + 'Red' => '红色', + 'Orange' => '橘色', + 'Grey' => '灰色', + 'Brown' => '褐色', + 'Deep Orange' => '橘红色', + 'Dark Grey' => '深灰色', + 'Pink' => '粉红色', + 'Teal' => '蓝绿色', + 'Cyan' => '青色', + 'Lime' => '黄绿色', + 'Light Green' => '浅绿色', + 'Amber' => '黄褐色', + 'Save' => '保存', + 'Login' => '登录', + 'Official website:' => '官方网站:', + 'Unassigned' => '未指派', + 'View this task' => '查看该任务', + 'Remove user' => '移除用户', + 'Do you really want to remove this user: "%s"?' => '确定要删除用户"%s"吗?', + 'All users' => '所有用户', + 'Username' => '用户名', + 'Password' => '密码', + 'Administrator' => '超级管理员', + 'Sign in' => '登录', + 'Users' => '用户', + 'Forbidden' => '禁止', + 'Access Forbidden' => '禁止进入', + 'Edit user' => '修改用户', + 'Logout' => '退出', + 'Bad username or password' => '用户名或密码错误', + 'Edit project' => '修改项目', + 'Name' => '名称', + 'Projects' => '项目', + 'No project' => '无项目', + 'Project' => '项目', + 'Status' => '状态', + 'Tasks' => '任务', + 'Board' => '看板', + 'Actions' => '动作', + 'Inactive' => '未激活', + 'Active' => '激活', + 'Unable to update this board.' => '无法更新该看板。', + 'Disable' => '停用', + 'Enable' => '启用', + 'New project' => '新建团队项目', + 'Do you really want to remove this project: "%s"?' => '确定要移除项目"%s"吗?', + 'Remove project' => '移除项目', + 'Edit the board for "%s"' => '为"%s"修改看板', + 'Add a new column' => '添加新栏目', + 'Title' => '标题', + 'Assigned to %s' => '指派给 %s', + 'Remove a column' => '移除一个栏目', + 'Unable to remove this column.' => '无法移除该栏目。', + 'Do you really want to remove this column: "%s"?' => '确定要移除栏目"%s"吗?', + 'Settings' => '设置', + 'Application settings' => '应用设置', + 'Language' => '语言', + 'Webhook token:' => '页面钩子令牌:', + 'API token:' => 'API 令牌:', + 'Database size:' => '数据库大小:', + 'Download the database' => '下载数据库', + 'Optimize the database' => '优化数据库', + '(VACUUM command)' => '(VACUUM 指令)', + '(Gzip compressed Sqlite file)' => '(用Gzip压缩的Sqlite文件)', + 'Close a task' => '关闭一个任务', + 'Column' => '栏目', + 'Color' => '颜色', + 'Assignee' => '指派给', + 'Create another task' => '创建另一个任务', + 'New task' => '新建任务', + 'Open a task' => '开启一个任务', + 'Do you really want to open this task: "%s"?' => '你确定要开这个任务吗?"%s"', + 'Back to the board' => '回到看板', + 'There is nobody assigned' => '当前无人被指派', + 'Column on the board:' => '看板上的栏目:', + 'Close this task' => '关闭该任务', + 'Open this task' => '开启该任务', + 'There is no description.' => '当前没有描述。', + 'Add a new task' => '添加新任务', + 'The username is required' => '需要用户名', + 'The maximum length is %d characters' => '最长%d个英文字符', + 'The minimum length is %d characters' => '最短%d个英文字符', + 'The password is required' => '需要密码', + 'This value must be an integer' => '该值必须为整数', + 'The username must be unique' => '用户名必须唯一', + 'The user id is required' => '用户id是必须的', + 'Passwords don\'t match' => '密码不匹配', + 'The confirmation is required' => '需要确认', + 'The project is required' => '需要指定项目', + 'The id is required' => '需要指定id', + 'The project id is required' => '需要指定项目id', + 'The project name is required' => '需要指定项目名称', + 'The title is required' => '需要指定标题', + 'Settings saved successfully.' => '设置成功保存。', + 'Unable to save your settings.' => '无法保存你的设置。', + 'Database optimization done.' => '数据库优化完成。', + 'Your project has been created successfully.' => '您的项目已经成功创建。', + 'Unable to create your project.' => '无法为您创建项目。', + 'Project updated successfully.' => '成功更新项目。', + 'Unable to update this project.' => '无法更新该项目。', + 'Unable to remove this project.' => '无法移除该项目。', + 'Project removed successfully.' => '成功移除项目。', + 'Project activated successfully.' => '项目成功激活。', + 'Unable to activate 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.' => '无法更新您的任务。', + 'Task updated successfully.' => '成功更新任务。', + 'Unable to create your task.' => '无法为您创建任务。', + 'Task created successfully.' => '成功创建任务。', + 'User created successfully.' => '成功创建用户。', + 'Unable to create your user.' => '无法创建用户。', + 'User updated successfully.' => '成功更新用户。', + 'User removed successfully.' => '成功移除用户。', + 'Unable to remove this user.' => '无法移除该用户。', + 'Board updated successfully.' => '看板成功更新。', + 'Ready' => '预备', + 'Backlog' => '待办', + 'Work in progress' => '进行中', + 'Done' => '完成', + 'Application version:' => '应用程序版本:', + 'Id' => '编号', + 'Public link' => '公开链接', + 'Timezone' => '时区', + 'Sorry, I didn\'t find this information in my database!' => '抱歉,无法在数据库中找到该信息!', + 'Page not found' => '页面未找到', + 'Complexity' => '复杂度', + 'Task limit' => '任务限制', + 'Task count' => '任务数', + 'User' => '用户', + 'Comments' => '评论', + 'Comment is required' => '评论不能为空', + 'Comment added successfully.' => '评论成功添加。', + 'Unable to create your comment.' => '无法创建评论。', + 'Due Date' => '到期时间', + 'Invalid date' => '无效日期', + 'Automatic actions' => '自动动作', + 'Your automatic action has been created successfully.' => '您的自动动作已成功创建', + 'Unable to create your automatic action.' => '无法为您创建自动动作。', + 'Remove an action' => '移除一个动作。', + 'Unable to remove this action.' => '无法移除该动作', + 'Action removed successfully.' => '成功移除动作。', + 'Automatic actions for the project "%s"' => '项目"%s"的自动动作', + 'Add an action' => '添加动作', + 'Event name' => '事件名称', + 'Action' => '动作', + 'Event' => '事件', + 'When the selected event occurs execute the corresponding action.' => '当所选事件发生时执行相应动作。', + 'Next step' => '下一步', + 'Define action parameters' => '定义动作参数', + 'Do you really want to remove this action: "%s"?' => '确定要移除动作"%s"吗?', + 'Remove an automatic action' => '移除一个自动动作', + 'Assign the task to a specific user' => '将该任务指派给一个用户', + 'Assign the task to the person who does the action' => '将任务指派给产生该动作的用户', + 'Duplicate the task to another project' => '复制该任务到另一项目', + 'Move a task to another column' => '移动任务到另一栏目', + 'Task modification' => '任务修改', + 'Task creation' => '任务创建', + 'Closing a task' => '正在关闭任务', + 'Assign a color to a specific user' => '为特定用户指派颜色', + 'Position' => '位置', + 'Duplicate to project' => '复制到另一项目', + 'Duplicate' => '复制', + 'Link' => '连接', + 'Comment updated successfully.' => '评论成功更新。', + 'Unable to update your comment.' => '无法更新您的评论。', + 'Remove a comment' => '移除评论', + 'Comment removed successfully.' => '评论成功移除。', + 'Unable to remove this comment.' => '无法移除该评论。', + 'Do you really want to remove this comment?' => '确定要移除评论吗?', + 'Current password for the user "%s"' => '用户"%s"的当前密码', + 'The current password is required' => '需要输入当前密码', + 'Wrong password' => '密码错误', + 'Unknown' => '未知', + 'Last logins' => '上次登录', + 'Login date' => '登录日期', + 'Authentication method' => '认证方式', + 'IP address' => 'IP地址', + 'User agent' => '浏览器标识', + 'Persistent connections' => '登录会话', + 'No session.' => '无会话', + 'Expiration date' => '过期日期', + 'Remember Me' => '记住我', + 'Creation date' => '创建日期', + 'Everybody' => '所有人', + 'Open' => '打开', + 'Closed' => '关闭', + 'Search' => '查找', + 'Nothing found.' => '没找到。', + 'Due date' => '到期时间', + 'Description' => '描述', + '%d comments' => '%d个评论', + '%d comment' => '%d个评论', + 'Email address invalid' => '邮件地址无效', + 'Your external account is not linked anymore to your profile.' => '你的外部账户关联已解除', + 'Unable to unlink your external account.' => '无法关联到你的外部账户', + 'External authentication failed' => '外部认证失败', + 'Your external account is linked to your profile successfully.' => '你已成功关联到你的外部账户', + 'Email' => '邮件', + 'Task removed successfully.' => '任务成功去除', + 'Unable to remove this task.' => '无法移除该任务。', + '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' => '基于一种颜色自动指派分类', + 'Task creation or modification' => '任务创建或修改', + 'Category' => '分类', + 'Category:' => '分类:', + 'Categories' => '分类', + 'Your category has been created successfully.' => '成功为您创建分类。', + 'This category has been updated successfully.' => '成功为您更新分类。', + 'Unable to update this category.' => '无法为您更新分类。', + 'Remove a category' => '移除一个分类', + 'Category removed successfully.' => '分类成功移除。', + 'Unable to remove this category.' => '无法移除该分类。', + 'Category modification for the project "%s"' => '为项目"%s"修改分类', + 'Category Name' => '分类名称', + 'Add a new category' => '加入新分类', + 'Do you really want to remove this category: "%s"?' => '确定要移除分类"%s"吗?', + 'All categories' => '所有分类', + 'No category' => '无分类', + 'The name is required' => '必须要有名字', + 'Remove a file' => '移除一个文件', + 'Unable to remove this file.' => '无法移除该文件。', + 'File removed successfully.' => '文件成功移除。', + 'Attach a document' => '附加文档', + 'Do you really want to remove this file: "%s"?' => '确定要移除文件"%s"吗?', + 'Attachments' => '附件', + 'Edit the task' => '修改任务', + 'Add a comment' => '添加评论', + 'Edit a comment' => '编辑评论', + 'Summary' => '概要', + 'Time tracking' => '时间记录', + 'Estimate:' => '评估:', + 'Spent:' => '花费:', + 'Do you really want to remove this sub-task?' => '请确认是否要删除此子任务?', + 'Remaining:' => '剩余:', + 'hours' => '小时', + 'estimated' => '评估', + 'Sub-Tasks' => '子任务', + 'Add a sub-task' => '添加子任务', + 'Original estimate' => '初步预估', + 'Create another sub-task' => '创建另一个子任务', + 'Time spent' => '时间花费', + 'Edit a sub-task' => '编辑子任务', + 'Remove a sub-task' => '删除子任务', + 'The time must be a numeric value' => '时间必须为数字', + 'Todo' => '待完成', + 'In progress' => '正在进行', + 'Sub-task removed successfully.' => '成功删除子任务', + 'Unable to remove this sub-task.' => '无法删除此任务', + 'Sub-task updated successfully.' => '成功创建子任务', + 'Unable to update your sub-task.' => '无法更新子任务', + 'Unable to create your sub-task.' => '无法创建子任务', + 'Maximum size: ' => '大小上限:', + 'Display another project' => '显示其它项目', + 'Created by %s' => '创建者:%s', + 'Tasks Export' => '任务导出', + 'Start Date' => '开始时间', + 'Execute' => '执行', + 'Task Id' => '任务ID', + 'Creator' => '创建者', + 'Modification date' => '修改日期', + 'Completion date' => '完成日期', + 'Clone' => '克隆', + 'Project cloned successfully.' => '成功复制项目。', + 'Unable to clone this project.' => '无法复制此项目', + 'Enable email notifications' => '启用邮件通知', + 'Task position:' => '任务位置:', + 'The task #%d has been opened.' => '任务#%d已经被开启.', + 'The task #%d has been closed.' => '任务#%d已经被关闭.', + 'Sub-task updated' => '子任务更新', + 'Title:' => '标题:', + 'Status:' => '状态:', + 'Assignee:' => '指派给', + 'Time tracking:' => '时间记录', + 'New sub-task' => '新建子任务', + 'New attachment added "%s"' => '新附件已添加"%s"', + 'New comment posted by %s' => '%s 的新评论', + 'New comment' => '新建评论', + 'Comment updated' => '更新了评论', + 'New subtask' => '新建子任务', + 'I only want to receive notifications for these projects:' => '我仅需要收到下面项目的通知:', + 'view the task on Kanboard' => '在看板中查看此任务', + 'Public access' => '公开访问', + 'Disable public access' => '停止公开访问', + 'Enable public access' => '开启公开访问', + 'Public access disabled' => '已经禁止公开访问', + 'Move the task to another project' => '移动任务到其它项目', + 'Move to project' => '移动到其它项目', + 'Do you really want to duplicate this task?' => '确定要复制此任务吗?', + 'Duplicate a task' => '复制任务', + 'External accounts' => '外部账户', + 'Account type' => '账户类型', + 'Local' => '本地', + 'Remote' => '远程', + 'Enabled' => '启用', + 'Disabled' => '停用', + 'Login:' => '用户名:', + 'Full Name:' => '姓名:', + 'Email:' => '邮件:', + 'Notifications:' => '通知:', + 'Notifications' => '通知设定', + 'Account type:' => '账户类型:', + 'Edit profile' => '编辑属性', + 'Change password' => '修改密码', + 'Password modification' => '修改密码', + 'External authentications' => '外部认证', + 'Never connected.' => '从未连接。', + 'No external authentication enabled.' => '未启用外部认证。', + 'Password modified successfully.' => '已经成功修改密码。', + 'Unable to change the password.' => '无法修改密码。', + 'Change category' => '变更分类', + '%s updated the task %s' => '%s 更新了任务 %s', + '%s opened the task %s' => '%s 开启了任务 %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s 将任务 %s 移动到了"%s"的第#%d个位置', + '%s moved the task %s to the column "%s"' => '%s 移动任务 %s 到栏目 "%s"', + '%s created the task %s' => '%s 创建了任务 %s', + '%s closed the task %s' => '%s 关闭了任务 %s', + '%s created a subtask for the task %s' => '%s 创建了 %s的子任务', + '%s updated a subtask for the task %s' => '%s 更新了 %s的子任务', + 'Assigned to %s with an estimate of %s/%sh' => '分配给 %s,预估需要 %s/%s 小时', + 'Not assigned, estimate of %sh' => '未指派,预估需要 %s 小时', + '%s updated a comment on the task %s' => '%s 更新了任务 %s的评论', + '%s commented the task %s' => '%s 评论了任务 %s', + '%s\'s activity' => '%s的动态', + 'RSS feed' => 'RSS 链接', + '%s updated a comment 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', + '%s created the task #%d' => '%s 创建了任务 #%d', + '%s closed the task #%d' => '%s 关闭了任务 #%d', + '%s opened the task #%d' => '%s 开启了任务 #%d', + 'Activity' => '项目动态', + 'Default values are "%s"' => '默认值为 "%s"', + 'Default columns for new projects (Comma-separated)' => '新建项目的默认栏目(用逗号分开)', + 'Task assignee change' => '任务受理人变更', + '%s changed the assignee of the task #%d to %s' => '%s 将任务 #%d 指派给了 %s', + '%s changed the assignee of the task %s to %s' => '%s 将任务 %s 指派给 %s', + 'New password for the user "%s"' => '用户"%s"的新密码', + 'Choose an event' => '选择一个事件', + 'Create a task from an external provider' => '从外部创建任务', + 'Change the assignee based on an external username' => '根据外部用户名修改任务受理人', + 'Change the category based on an external label' => '根据外部标签修改分类', + 'Reference' => '参考', + 'Label' => '标签', + 'Database' => '数据库', + 'About' => '关于', + 'Database driver:' => '数据库驱动:', + 'Board settings' => '看板设置', + 'Webhook settings' => 'Webhook 设置', + 'Reset token' => '重置令牌', + 'API endpoint:' => 'API 端点:', + 'Refresh interval for personal board' => '私人看板的刷新时间', + 'Refresh interval for public board' => '公共看板的刷新时间', + 'Task highlight period' => '任务高亮时间', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => '多久内的任务视作刚刚修改(单位:秒,设置为0停用,默认是2天', + 'Frequency in second (60 seconds by default)' => '频率,单位为秒(默认是60秒)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => '频率,单位为秒(设置为0停用此功能,默认是10秒)', + 'Application URL' => '应用URL', + 'Token regenerated.' => '重新生成令牌', + 'Date format' => '日期格式', + 'ISO format is always accepted, example: "%s" and "%s"' => 'ISO 格式总是允许的,例如:"%s" 和 "%s"', + 'New personal project' => '新建私有项目', + 'This project is personal' => '此项目为私有项目', + 'Add' => '添加', + 'Start date' => '启动日期', + 'Time estimated' => '预计时间', + 'There is nothing assigned to you.' => '当前无任务指派给你。', + 'My tasks' => '我的任务', + 'Activity stream' => '动态记录', + 'Dashboard' => '面板', + 'Confirmation' => '确认', + 'Webhooks' => '网络钩子', + 'API' => '应用程序接口', + 'Create a comment from an external provider' => '从外部创建一个评论', + 'Project management' => '项目管理', + 'Columns' => '栏目', + 'Task' => '任务', + 'Percentage' => '百分比', + 'Number of tasks' => '任务数', + 'Task distribution' => '任务分布', + 'Analytics' => '统计分析', + 'Subtask' => '子任务', + 'User repartition' => '用户分析', + 'Clone this project' => '复制此项目', + 'Column removed successfully.' => '成功删除了栏目。', + 'Not enough data to show the graph.' => '数据不足,无法绘图。', + 'Previous' => '后退', + 'The id must be an integer' => '编号必须为整数', + 'The project id must be an integer' => '项目编号必须为整数', + 'The status must be an integer' => '状态必须为整数', + 'The subtask id is required' => '必须提供子任务编号', + 'The subtask id must be an integer' => '子任务编号必须为整数', + 'The task id is required' => '需要任务编号', + 'The task id must be an integer' => '任务编号必须为整数', + 'The user id must be an integer' => '用户编号必须为整数', + 'This value is required' => '必须给出这个值', + 'This value must be numeric' => '这个值必须为数字', + 'Unable to create this task.' => '无法创建此任务。', + 'Cumulative flow diagram' => '累积流图表', + 'Daily project summary' => '每日项目汇总', + 'Daily project summary export' => '导出每日项目汇总', + 'Exports' => '导出任务', + 'This export contains the number of tasks per column grouped per day.' => '此导出包含每列的任务数,按天分组', + 'Active swimlanes' => '活动泳道', + 'Add a new swimlane' => '添加新泳道', + 'Default swimlane' => '默认泳道', + 'Do you really want to remove this swimlane: "%s"?' => '确定要删除泳道:"%s"?', + 'Inactive swimlanes' => '非活动泳道', + 'Remove a swimlane' => '删除泳道', + 'Swimlane modification for the project "%s"' => '项目"%s"的泳道变更', + 'Swimlane removed successfully.' => '成功删除泳道', + 'Swimlanes' => '泳道', + 'Swimlane updated successfully.' => '成功更新了泳道。', + 'Unable to remove this swimlane.' => '无法删除此泳道', + 'Unable to update this swimlane.' => '无法更新此泳道', + 'Your swimlane has been created successfully.' => '已经成功创建泳道。', + 'Example: "Bug, Feature Request, Improvement"' => '示例:“缺陷,功能需求,提升', + 'Default categories for new projects (Comma-separated)' => '新项目的默认分类(用逗号分隔)', + 'Integrations' => '整合', + 'Integration with third-party services' => '与第三方服务进行整合', + 'Subtask Id' => '子任务 Id', + 'Subtasks' => '子任务', + 'Subtasks Export' => '子任务导出', + 'Task Title' => '任务标题', + 'Untitled' => '无标题', + 'Application default' => '程序默认', + 'Language:' => '语言:', + 'Timezone:' => '时区:', + 'All columns' => '全部栏目', + 'Next' => '前进', + '#%d' => '#%d', + 'All swimlanes' => '全部泳道', + 'All colors' => '全部颜色', + 'Moved to column %s' => '移动到栏目 %s', + 'User dashboard' => '用户仪表板', + 'Allow only one subtask in progress at the same time for a user' => '每用户同时仅有一个活动子任务', + 'Edit column "%s"' => '编辑栏目"%s"', + '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?' => '要复制项目的哪些内容?', + 'Disallow login form' => '禁止登陆', + 'Start' => '开始', + 'End' => '结束', + 'Task age in days' => '任务存在天数', + 'Days in this column' => '在此栏目的天数', + '%dd' => '%d天', + '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' => '关联修改', + 'Opposite label' => '反向标签', + 'Remove a link' => '删除关联', + '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' => '<1h', + '%dh' => '%dh', + 'Expand tasks' => '展开任务', + 'Collapse tasks' => '折叠任务', + 'Expand/collapse tasks' => '展开/折叠任务', + 'Close dialog box' => '关闭对话框', + 'Submit a form' => '提交表单', + 'Board view' => '面板视图', + 'Keyboard shortcuts' => '键盘快捷方式', + 'Open board switcher' => '打开面板切换器', + 'Application' => '应用程序', + 'Compact view' => '紧凑视图', + 'Horizontal scrolling' => '水平滚动', + 'Compact/wide view' => '紧凑/宽视图', + 'Currency' => '货币', + 'Personal project' => '私有项目', + 'AUD - Australian Dollar' => '澳元', + 'CAD - Canadian Dollar' => '加元', + 'CHF - Swiss Francs' => '瑞士法郎', + 'Custom Stylesheet' => '自定义样式表', + 'EUR - Euro' => '欧元', + 'GBP - British Pound' => '英镑', + 'INR - Indian Rupee' => '印度卢比', + 'JPY - Japanese Yen' => '日元', + 'NZD - New Zealand Dollar' => '新西兰元', + 'PEN - Peruvian Sol' => 'PEN - 秘鲁索尔', + 'RSD - Serbian dinar' => '第纳尔', + 'CNY - Chinese Yuan' => '人民币', + 'USD - US Dollar' => '美元', + 'VES - Venezuelan Bolívar' => '委内瑞拉玻利瓦尔', + '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' => '原栏目', + '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' => '修改参考货币', + 'Reference currency' => '参考货币', + 'The currency rate has been added successfully.' => '成功添加汇率。', + 'Unable to add this currency rate.' => '无法添加此汇率', + 'Webhook URL' => '网络钩子 URL', + '%s removed the assignee of the task %s' => '%s删除了任务%s的受理人', + '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' => '双重认证', + 'This QR code contains the key URI: ' => '此二维码包含密码 URI:', + 'Check my code' => '检查我的认证码', + 'Secret key: ' => '密码:', + 'Test your device' => '测试设备', + 'Assign a color when the task is moved to a specific column' => '任务移动到指定栏目时设置颜色', + '%s via Kanboard' => '%s 通过KanBoard', + 'Burndown chart' => '燃尽图', + 'This chart show the task complexity over the time (Work Remaining).' => '图表显示任务随时间(剩余时间)的复杂度', + 'Screenshot taken %s' => '已截图 %s', + 'Add a screenshot' => '添加截图', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => '获取截图并按CTRL+V或者⌘+V粘贴到这里', + 'Screenshot uploaded successfully.' => '截图上传成功', + 'SEK - Swedish Krona' => '瑞郎', + 'Identifier' => '标识符', + 'Disable two factor authentication' => '禁用双重认证', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => '你真的要禁用 "%s" 的双重认证吗?', + 'Edit link' => '编辑链接', + 'Start to type task title...' => '输入任务标题...', + 'A task cannot be linked to itself' => '任务不能关联到本身', + 'The exact same link already exists' => '相同的关联已存在', + 'Recurrent task is scheduled to be generated' => '循环性任务将按计划生成', + 'Score' => '积分', + 'The identifier must be unique' => '标识符必须唯一', + 'This linked task id doesn\'t exists' => '关联任务不存在', + 'This value must be alphanumeric' => '此值必须是字母或者数字', + 'Edit recurrence' => '编辑循环周期', + 'Generate recurrent task' => '生成循环任务', + 'Trigger to generate recurrent task' => '生成循环任务的触发器', + 'Factor to calculate new due date' => '新到期时间的计算因子', + 'Timeframe to calculate new due date' => '新到期时间的计算周期', + 'Base date to calculate new due date' => '新到期时间的计算基准', + 'Action date' => '操作日期', + 'Base date to calculate new due date: ' => '基准日期', + 'This task has created this child task: ' => '此任务创建了子任务:', + 'Day(s)' => '天', + 'Existing due date' => '当前到期时间', + 'Factor to calculate new due date: ' => '新到期时间的计算因子:', + 'Month(s)' => '月', + 'This task has been created by: ' => '此任务被谁创建:', + 'Recurrent task has been generated:' => '循环任务已生成:', + 'Timeframe to calculate new due date: ' => '新到期时间的计算周期:', + 'Trigger to generate recurrent task: ' => '生成循环任务的触发器', + 'When task is closed' => '当任务关闭时', + 'When task is moved from first column' => '当任务从第一列任务栏移走时', + 'When task is moved to last column' => '当任务移动到最后一列任务栏时', + 'Year(s)' => '年', + 'Project settings' => '项目设置', + 'Automatically update the start date' => '自动更新开始日期', + 'iCal feed' => '日历订阅', + 'Preferences' => '偏好', + 'Security' => '安全', + 'Two factor authentication disabled' => '双重认证已禁用', + 'Two factor authentication enabled' => '双重认证已启用', + 'Unable to update this user.' => '无法更新此用户', + 'There is no user management for personal projects.' => '私有项目下无用户可管理', + 'User that will receive the email' => '用户将收到邮件', + 'Email subject' => '邮件主题', + 'Date' => '日期', + 'Add a comment log when moving the task between columns' => '当任务在栏目间移动时添加评论日志', + 'Move the task to another column when the category is changed' => '当任务分类改变时移动到另一栏', + 'Send a task by email to someone' => '发送任务邮件到用户', + 'Reopen a task' => '重新开始一个任务', + 'Notification' => '通知', + '%s moved the task #%d to the first swimlane' => '%s将任务#%d移动到了首个泳道', + 'Swimlane' => '泳道', + '%s moved the task %s to the first swimlane' => '%s将任务%s移动到了首个泳道', + '%s moved the task %s to the swimlane "%s"' => '%s将任务%s移动到了泳道"%s"下', + 'This report contains all subtasks information for the given date range.' => '该报告包含了指定日期范围内的所有子任务信息。', + 'This report contains all tasks information for the given date range.' => '该报告包含了指定日期范围内的所有任务信息。', + 'Project activities for %s' => '%s的项目活动记录', + 'view the board on Kanboard' => '在看板上查看面板', + 'The task has been moved to the first swimlane' => '该任务已被移动到首个泳道', + 'The task has been moved to another swimlane:' => '该任务已被移动到别的泳道:', + 'New title: %s' => '新标题:%s', + 'The task is not assigned anymore' => '该任务没有指派给任何人', + 'New assignee: %s' => '新指派到:%s', + 'There is no category now' => '当前没有分类', + 'New category: %s' => '新分类:%s', + 'New color: %s' => '新颜色:%s', + 'New complexity: %d' => '新的复杂度:%d', + 'The due date has been removed' => '超期时间已被移除', + 'There is no description anymore' => '当前没有描述', + 'Recurrence settings has been modified' => '循环周期已被更改', + 'Time spent changed: %sh' => '时间花费已变更:%sh', + 'Time estimated changed: %sh' => '时间预估已变更:%sh', + 'The field "%s" has been updated' => '"%s"字段已更新', + 'The description has been modified:' => '描述已更改', + 'Do you really want to close the task "%s" as well as all subtasks?' => '你是否要移除所有子任务的同时父任务"%s"', + 'I want to receive notifications for:' => '我想接收以下相关通知:', + 'All tasks' => '所有任务', + 'Only for tasks assigned to me' => '所有指派给我的任务', + 'Only for tasks created by me' => '所有我创建的任务', + 'Only for tasks created by me and tasks assigned to me' => '所有我创建的和指派给我的任务', + '%%Y-%%m-%%d' => '%%Y-%%m-%%d', + 'Total for all columns' => '所有栏目下的', + 'You need at least 2 days of data to show the chart.' => '当前柱状图至少需要2天的数据。', + '<15m' => '小于15分钟', + '<30m' => '小于30分钟', + 'Stop timer' => '停止计时器', + 'Start timer' => '开启计时器', + 'My activity stream' => '我的动态', + 'Search tasks' => '搜索任务', + 'Reset filters' => '重置过滤器', + 'My tasks due tomorrow' => '我的明天到期的任务', + 'Tasks due today' => '今天到期的任务', + 'Tasks due tomorrow' => '明天到期的任务', + 'Tasks due yesterday' => '昨天到期的任务', + 'Closed tasks' => '已关闭任务', + 'Open tasks' => '打开的任务', + 'Not assigned' => '未指派', + 'View advanced search syntax' => '查看高级搜索语法', + 'Overview' => '概览', + 'Board/Calendar/List view' => '看板/日程/列表视图', + 'Switch to the board view' => '切换到看板视图', + 'Switch to the list view' => '切换到列表视图', + 'Go to the search/filter box' => '前往搜索/过滤箱', + 'There is no activity yet.' => '当前无任何活动。', + 'No tasks found.' => '没有找到任何任务。', + 'Keyboard shortcut: "%s"' => '快捷键: "%s"', + 'List' => '列表', + 'Filter' => '过滤器', + 'Advanced search' => '高级搜索', + 'Example of query: ' => '查询示例:', + 'Search by project: ' => '按项目:', + 'Search by column: ' => '按任务栏:', + 'Search by assignee: ' => '按受理人:', + 'Search by color: ' => '按颜色: ', + 'Search by category: ' => '按分类:', + 'Search by description: ' => '按描述:', + 'Search by due date: ' => '按超期时间:', + 'Average time spent in each column' => '每个任务栏的平均花费时间', + 'Average time spent' => '平均花费时间', + 'This chart shows the average time spent in each column for the last %d tasks.' => '当前柱状图表示最新%d条任务在每个任务栏下的平均花费时间', + 'Average Lead and Cycle time' => '平均工作时间和平均周期时间', + 'Average lead time: ' => '平均工作时间', + 'Average cycle time: ' => '平均周期时间:', + 'Cycle Time' => '周期时间', + 'Lead Time' => '工作时间', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => '该图表显示了过去 %d 个任务的平均和峰值耗时。', + 'Average time into each column' => '每个任务栏的平均时间', + 'Lead and cycle time' => '工作时间和周期时间', + 'Lead time: ' => '工作时间: ', + 'Cycle time: ' => '周期时间: ', + 'Time spent in each column' => '每个任务栏的花费时间', + 'The lead time is the duration between the task creation and the completion.' => '工作时间是任务创建和完成之间的持续时间。', + 'The cycle time is the duration between the start date and the completion.' => '周期时间是开始日期和完成时间之间的持续时间。', + 'If the task is not closed the current time is used instead of the completion date.' => '如果当前任务未关闭时用当前时间代替完成时间', + 'Set the start date automatically' => '设置自动开始日期', + 'Edit Authentication' => '编辑认证信息', + 'Remote user' => '远程用户', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => '远程用户不会在看板数据库保存密码,例如:LDAP,GOOGLE,GitHub。', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => '如果选中“禁止登陆来自”,登陆表单内的验证信息将被忽略。', + 'Default task color' => '默认任务颜色', + 'This feature does not work with all browsers.' => '本功能只在部分浏览器下工作正常。', + 'There is no destination project available.' => '当前没有目标项目可用', + 'Trigger automatically subtask time tracking' => '自动跟踪子任务时间', + 'Include closed tasks in the cumulative flow diagram' => '在累计流程图中包含已关闭任务', + 'Current swimlane: %s' => '当前泳道:%s', + 'Current column: %s' => '当前任务栏:%s', + 'Current category: %s' => '当前分类:%s', + 'no category' => '无分类', + 'Current assignee: %s' => '当前受理人: %s', + 'not assigned' => '未指派', + 'Author:' => '作者', + 'contributors' => '贡献者', + 'License:' => '授权许可:', + 'License' => '授权许可', + 'Enter the text below' => '输入下方的文本', + 'Start date:' => '开始日期', + 'Due date:' => '到期日期', + 'People who are project managers' => '项目管理员', + 'People who are project members' => '项目成员', + 'NOK - Norwegian Krone' => '克朗', + 'Show this column' => '显示任务栏', + 'Hide this column' => '隐藏任务栏', + 'End date' => '结束日期', + 'Users overview' => '用户概览', + 'Members' => '成员', + 'Shared project' => '公开项目', + 'Project managers' => '项目管理员', + 'Projects list' => '项目列表', + 'End date:' => '结束日期', + 'Change task color when using a specific task link' => '当任务关联到指定任务时改变颜色', + 'Task link creation or modification' => '任务链接创建或更新时间', + 'Milestone' => '里程碑', + 'Reset the search/filter box' => '重置搜索/过滤框', + 'Documentation' => '帮助文档', + 'Author' => '作者', + 'Version' => '版本', + 'Plugins' => '插件管理', + 'There is no plugin loaded.' => '当前没有插件载入', + 'My notifications' => '我的通知', + 'Custom filters' => '过滤器', + 'Your custom filter has been created successfully.' => '成功创建过滤器', + 'Unable to create your custom filter.' => '无法创建过滤器', + 'Custom filter removed successfully.' => '成功删除过滤器', + 'Unable to remove this custom filter.' => '无法删除这个过滤器', + 'Edit custom filter' => '编辑过滤器', + 'Your custom filter has been updated successfully.' => '你的过滤器更新成功', + 'Unable to update custom filter.' => '无法更新过滤器', + 'Web' => 'web', + 'New attachment on task #%d: %s' => '任务#%d下的新附件:%s', + 'New comment on task #%d' => '任务#%d下的新评论', + 'Comment updated on task #%d' => '任务#%d的评论已更新', + 'New subtask on task #%d' => '任务#%d下新的子任务', + 'Subtask updated on task #%d' => '任务#%d下的子任务已更新', + 'New task #%d: %s' => '新任务#%d:%s', + 'Task updated #%d' => '任务#%d已更新', + 'Task #%d closed' => '任务#%d已关闭', + 'Task #%d opened' => '任务#%d已打开', + 'Column changed for task #%d' => '任务#%d的任务栏已改变', + 'New position for task #%d' => '任务#%d的新状态', + 'Swimlane changed for task #%d' => '任务#%d的泳道已改变', + 'Assignee changed on task #%d' => '任务#%d的指派人已改变', + '%d overdue tasks' => '%d条超期任务', + 'No notification.' => '没有新通知', + 'Mark all as read' => '标记所有为已读', + 'Mark as read' => '标记为已读', + 'Total number of tasks in this column across all swimlanes' => '此任务栏下的任务数(跨泳道)', + 'Collapse swimlane' => '收起泳道', + 'Expand swimlane' => '展开泳道', + 'Add a new filter' => '添加新过滤器', + 'Share with all project members' => '对项目所有成员共享', + 'Shared' => '共享', + 'Owner' => '所有人', + 'Unread notifications' => '未读通知', + 'Notification methods:' => '通知提醒方式:', + 'Unable to read your file' => '无法读取文件', + '%d task(s) have been imported successfully.' => '成功导入%d条任务。', + 'Nothing has been imported!' => '没有信息被导入!', + 'Import users from CSV file' => '从CSV文件导入用户', + '%d user(s) have been imported successfully.' => '成功导入%d个用户。', + 'Comma' => '逗号', + 'Semi-colon' => '分号', + 'Tab' => '制表符', + 'Vertical bar' => '竖线', + 'Double Quote' => '双引号', + 'Single Quote' => '单引号', + '%s attached a file to the task #%d' => '%s添加文件到任务#%d', + 'There is no column or swimlane activated in your project!' => '当前项目没有活动任务栏或泳道', + 'Append filter (instead of replacement)' => '追加过滤', + 'Append/Replace' => '追加/替换', + 'Append' => '追加', + 'Replace' => '替换', + 'Import' => '导入', + 'Change sorting' => '改变排序', + 'Tasks Importation' => '任务重要性', + 'Delimiter' => '分隔符', + 'Enclosure' => '附件', + 'CSV File' => 'CSV文件', + 'Instructions' => '操作指南', + 'Your file must use the predefined CSV format' => '文件必须为预定格式的CSV文件', + 'Your file must be encoded in UTF-8' => '文件编码必须为UTF-8', + 'The first row must be the header' => '第一行必须为表头', + 'Duplicates are not verified for you' => '无法验证重复信息', + 'The due date must use the ISO format: YYYY-MM-DD' => '超期日期必须为ISO格式:YYYY-MM-DD', + 'Download CSV template' => '下载CSV模板', + 'No external integration registered.' => '没有外部注册信息', + 'Duplicates are not imported' => '重复信息未导入', + 'Usernames must be lowercase and unique' => '用户名必须小写且唯一', + 'Passwords will be encrypted if present' => '密码将被加密', + '%s attached a new file to the task %s' => '"%s"添加了附件到任务"%s"', + 'Link type' => '关联类型', + 'Assign automatically a category based on a link' => '基于链接自动关联分类', + 'BAM - Konvertible Mark' => '波斯尼亚马克', + 'Assignee Username' => '指派用户名', + 'Assignee Name' => '指派名称', + 'Groups' => '用户组', + 'Members of %s' => '“%s”组成员', + 'New group' => '新加用户组', + 'Group created successfully.' => '用户组创建成功', + 'Unable to create your group.' => '无法创建你的用户组', + 'Edit group' => '编辑用户组', + 'Group updated successfully.' => '用户组更新成功', + 'Unable to update your group.' => '无法更新你的用户组', + 'Add group member to "%s"' => '添加到用户组"%s"', + 'Group member added successfully.' => '成功添加用户组成员', + 'Unable to add group member.' => '无法添加用户组成员', + 'Remove user from group "%s"' => '从"%s"组中移除用户', + 'User removed successfully from this group.' => '用户已从该用户组中删除', + 'Unable to remove this user from the group.' => '无法从该用户组中删除用户', + 'Remove group' => '删除用户组', + 'Group removed successfully.' => '用户组已删除', + 'Unable to remove this group.' => '无法删除该用户组', + 'Project Permissions' => '项目权限', + 'Manager' => '管理员', + 'Project Manager' => '项目管理员', + 'Project Member' => '项目成员', + 'Project Viewer' => '项目观察员', + 'Your account is locked for %d minutes' => '你的账户被锁定%d分钟', + 'Invalid captcha' => '验证码无效', + 'The name must be unique' => '请确保用户名唯一', + 'View all groups' => '查看所有用户组', + 'There is no user available.' => '当前没有有效用户', + 'Do you really want to remove the user "%s" from the group "%s"?' => '你确定把用户"%s"从"%s"中移除?', + 'There is no group.' => '当前没有用户组', + 'Add group member' => '添加用户组成员', + 'Do you really want to remove this group: "%s"?' => '你真的想要移除用户组:"%s"?', + 'There is no user in this group.' => '当前用户组下没有成员', + 'Permissions' => '权限', + 'Allowed Users' => '被允许的用户', + 'No specific user has been allowed.' => '没有被允许的用户。', + 'Role' => '角色', + 'Enter user name...' => '输入用户名...', + 'Allowed Groups' => '被允许的用户组', + 'No group has been allowed.' => '没有被允许的用户组。', + 'Group' => '用户组', + 'Group Name' => '用户组名称', + 'Enter group name...' => '输入用户组名称...', + 'Role:' => '角色:', + 'Project members' => '项目成员', + '%s mentioned you in the task #%d' => '%s在任务#%d里提及你', + '%s mentioned you in a comment on the task #%d' => '%s在任务#%d的评论里提及你', + 'You were mentioned in the task #%d' => '你在任务#%d里被提及', + 'You were mentioned in a comment on the task #%d' => '你在任务#%d的评论里被提及', + 'Estimated hours: ' => '预估小时数', + 'Actual hours: ' => '实际小时数', + 'Hours Spent' => '花费小时数', + 'Hours Estimated' => '预估小时数', + 'Estimated Time' => '预估时间', + 'Actual Time' => '实际时间', + 'Estimated vs actual time' => '预估时间 VS 实际时间', + 'RUB - Russian Ruble' => '卢布', + 'Assign the task to the person who does the action when the column is changed' => '当任务所属任务栏改变时分配到指定用户', + 'Close a task in a specific column' => '关闭指定任务栏下的任务', + 'Time-based One-time Password Algorithm' => '基于时间的一次性密码算法', + 'Two-Factor Provider: ' => '双重认证提供商', + 'Disable two-factor authentication' => '禁用双重认证', + 'Enable two-factor authentication' => '启用双重认证', + 'There is no integration registered at the moment.' => '当前没有注册关联', + 'Password Reset for Kanboard' => '重置kanboard密码', + 'Forgot password?' => '忘记密码?', + 'Enable "Forget Password"' => '启用找回密码', + 'Password Reset' => '密码重置', + 'New password' => '新密码', + 'Change Password' => '更改密码', + 'To reset your password click on this link:' => '点击此链接重置你的密码', + 'Last Password Reset' => '上次密码重置', + 'The password has never been reinitialized.' => '密码从未被重新初始化', + 'Creation' => '创建时间', + 'Expiration' => '过期时间', + 'Password reset history' => '密码历史', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => '任务栏 "%s" 和 泳道 "%s" 下的所有任务已关闭', + 'Do you really want to close all tasks of this column?' => '你确定要关闭此任务栏下的所有任务?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '任务栏 "%s" 和 泳道 "%s" 下 %d 条任务将被关闭。', + 'Close all tasks in this column and this swimlane' => '关闭此任务栏下的所有任务', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => '没有插件注册到项目通知接口,你仍然可以在你个人设置里启用单独的通知', + 'My dashboard' => '我的看板', + 'My profile' => '个人信息', + 'Project owner: ' => '项目负责人', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => '项目标识符是可选的,且只能是数字或字母组成,例如:MYPROJECT。', + 'Project owner' => '项目负责人', + 'Personal projects do not have users and groups management.' => '私有项目下没有成员或组可管理', + 'There is no project member.' => '当前没有项目成员', + 'Priority' => '优先级', + 'Task priority' => '任务优先级', + 'General' => '常用', + 'Dates' => '时间', + 'Default priority' => '默认优先级', + 'Lowest priority' => '最低优先级', + 'Highest priority' => '最高优先级', + 'Close a task when there is no activity' => '当任务没有活动记录时关闭任务', + 'Duration in days' => '持续天数', + 'Send email when there is no activity on a task' => '当任务没有活动记录时发送邮件', + 'Unable to fetch link information.' => '无法获取关联信息', + 'Daily background job for tasks' => '每日后台任务', + 'Auto' => '自动', + 'Related' => '相关的', + 'Attachment' => '附件', + 'Web Link' => '网页链接', + 'External links' => '外部关联', + 'Add external link' => '添加外部关联', + 'Type' => '类型', + 'Dependency' => '依赖', + 'Add internal link' => '添加内部关联', + 'Add a new external link' => '添加新的外部关联', + 'Edit external link' => '编辑外部关联', + 'External link' => '外部关联', + 'Copy and paste your link here...' => '复制并粘贴链接到当前位置...', + 'URL' => 'URL', + 'Internal links' => '内部关联', + 'Assign to me' => '指派给我', + 'Me' => '我', + 'Do not duplicate anything' => '全新项目', + 'Projects management' => '项目管理', + 'Users management' => '用户管理', + 'Groups management' => '用户组管理', + 'Create from another project' => '基于现有项目创建', + 'open' => '打开', + 'closed' => '已关闭', + 'Priority:' => '优先级:', + 'Reference:' => '引用:', + 'Complexity:' => '复杂度:', + 'Swimlane:' => '泳道:', + 'Column:' => '栏目:', + 'Position:' => '位置:', + 'Creator:' => '创建者:', + 'Time estimated:' => '预计时间:', + '%s hours' => '%s 小时', + 'Time spent:' => '时间消耗:', + 'Created:' => '已创建:', + 'Modified:' => '已修改:', + 'Completed:' => '已完成:', + 'Started:' => '已开始:', + 'Moved:' => '已移走:', + 'Task #%d' => '任务#%d', + 'Time format' => '时间格式', + 'Start date: ' => '开始时间:', + 'End date: ' => '结束时间:', + 'New due date: ' => '新超期时间:', + 'Start date changed: ' => '开始时间已改变:', + 'Disable personal projects' => '禁用私有项目', + 'Do you really want to remove this custom filter: "%s"?' => '你确定要移除这个自定义过滤器:"%s"?', + 'Remove a custom filter' => '移除过滤器', + 'User activated successfully.' => '用户已激活。', + 'Unable to enable this user.' => '无法启用该用户。', + 'User disabled successfully.' => '用户已禁用。', + 'Unable to disable this user.' => '无法禁用该用户。', + 'All files have been uploaded successfully.' => '所有文件已成功上传。', + 'The maximum allowed file size is %sB.' => '最大上传尺寸 %sB', + 'Drag and drop your files here' => '拖放文件到这里', + 'choose files' => '选择文件', + 'View profile' => '查看个人信息', + 'Two Factor' => '双重认证', + 'Disable user' => '禁用用户', + 'Do you really want to disable this user: "%s"?' => '你确定要禁用该用户:"%s"?', + 'Enable user' => '启用用户', + 'Do you really want to enable this user: "%s"?' => '你确定要启用该用户:"%s"?', + 'Download' => '下载', + 'Uploaded: %s' => '上传:%s', + 'Size: %s' => '大小:%s', + 'Uploaded by %s' => '由%s上传', + 'Filename' => '文件名', + 'Size' => '大小', + 'Column created successfully.' => '新增任务栏成功。', + 'Another column with the same name exists in the project' => '当前项目中重名任务栏已存在', + 'Default filters' => '默认过滤器', + 'Your board doesn\'t have any columns!' => '你的看板没有任何栏目', + 'Change column position' => '更改任务栏位置', + 'Switch to the project overview' => '切换到项目视图', + 'User filters' => '用户过滤器', + 'Category filters' => '分类过滤器', + 'Upload a file' => '上传文件', + 'View file' => '查看文件', + 'Last activity' => '最后活动', + 'Change subtask position' => '更改子任务位置', + 'This value must be greater than %d' => '当前输入值必须大于%d', + 'Another swimlane with the same name exists in the project' => '当前项目中重名泳道已存在', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => '例如: https://example.kanboard.org/(通常用于生成绝对地址)', + 'Actions duplicated successfully.' => '动作复制成功。', + 'Unable to duplicate actions.' => '无法复制动作。', + 'Add a new action' => '添加新动作', + 'Import from another project' => '从另一个项目中导入', + 'There is no action at the moment.' => '当前没有动作。', + 'Import actions from another project' => '从另一个项目中导入动作', + 'There is no available project.' => '当前没有可用项目', + 'Local File' => '本地文件', + 'Configuration' => '配置选项', + 'PHP version:' => 'PHP 版本:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => '系统:', + 'Database version:' => '数据库版本:', + 'Browser:' => '浏览器:', + 'Task view' => '任务浏览', + 'Edit task' => '编辑任务', + 'Edit description' => '编辑描述', + 'New internal link' => '新建内部链接', + 'Display list of keyboard shortcuts' => '显示快捷键列表', + 'Avatar' => '我的头像', + 'Upload my avatar image' => '上传我的头像', + 'Remove my image' => '删除我的头像', + 'The OAuth2 state parameter is invalid' => 'OAuth2状态参数无效', + 'User not found.' => '用户未找到', + 'Search in activity stream' => '在活动足迹里搜索', + 'My activities' => '我的活动足迹', + 'Activity until yesterday' => '今天以前的活动足迹', + 'Activity until today' => '今天为止的活动足迹', + 'Search by creator: ' => '以创建者搜索', + 'Search by creation date: ' => '以创建日期搜索', + 'Search by task status: ' => '以任务状态搜索', + 'Search by task title: ' => '以任务标题搜索', + 'Activity stream search' => '活动足迹搜索', + 'Projects where "%s" is manager' => '"%s" 管理的项目', + 'Projects where "%s" is member' => '"%s" 参与的项目', + 'Open tasks assigned to "%s"' => '指派给"%s"的开放任务', + 'Closed tasks assigned to "%s"' => '指派给"%s"的已结束任务', + 'Assign automatically a color based on a priority' => '基于优先级自动标记颜色', + 'Overdue tasks for the project(s) "%s"' => '"%s"项目下的超期任务', + 'Upload files' => '上传文件', + 'Installed Plugins' => '已安装插件', + 'Plugin Directory' => '插件目录', + 'Plugin installed successfully.' => '插件安装成功。', + 'Plugin updated successfully.' => '插件更新成功。', + 'Plugin removed successfully.' => '插件卸载成功。', + 'Subtask converted to task successfully.' => '子任务成功转换成普通任务。', + 'Unable to convert the subtask.' => '无法转换子任务。', + 'Unable to extract plugin archive.' => '无法解压插件包。', + 'Plugin not found.' => '插件未找到。', + 'You don\'t have the permission to remove this plugin.' => '你没有权限移除此插件。', + 'Unable to download plugin archive.' => '无法下载插件包。', + 'Unable to write temporary file for plugin.' => '无法为插件写入临时文件。', + 'Unable to open plugin archive.' => '无法打开插件包。', + 'There is no file in the plugin archive.' => '当前插件包内无文件。', + 'Create tasks in bulk' => '批量创建任务', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => '你的Kanboard没有启用插件安装。', + 'There is no plugin available.' => '当前无可用插件。', + 'Install' => '安装', + 'Update' => '更新', + 'Up to date' => '已更新', + 'Not available' => '不可用', + 'Remove plugin' => '移除插件', + 'Do you really want to remove this plugin: "%s"?' => '你真的要移除插件:"%s"?', + 'Uninstall' => '移除', + 'Listing' => '列表', + 'Metadata' => '元数据', + 'Manage projects' => '管理项目', + 'Convert to task' => '转化为普通任务', + 'Convert sub-task to task' => '转化子任务为普通任务', + 'Do you really want to convert this sub-task to a task?' => '你真的要将此子任务转化为普通任务?', + 'My task title' => '我的任务标题', + 'Enter one task by line.' => '写入一行任务。', + 'Number of failed login:' => '登录失败次数:', + 'Account locked until:' => '账户被锁定至:', + 'Email settings' => '邮件设置', + 'Email sender address' => '邮件发送地址', + 'Email transport' => '邮件转发', + 'Webhook token' => 'Web钩子Token', + 'Project tags management' => '项目标签管理', + 'Tag created successfully.' => '成功创建标签。', + 'Unable to create this tag.' => '无法创建此标签。', + 'Tag updated successfully.' => '标签更新成功。', + 'Unable to update this tag.' => '无法更新此标签。', + 'Tag removed successfully.' => '标签删除成功。', + 'Unable to remove this tag.' => '无法删除此标签', + 'Global tags management' => '全局标签管理', + 'Tags' => '标签', + 'Tags management' => '标签管理', + 'Add new tag' => '添加新标签', + 'Edit a tag' => '编辑标签', + 'Project tags' => '项目标签', + 'There is no specific tag for this project at the moment.' => '当前项目没有指定任何标签。', + 'Tag' => '标签', + 'Remove a tag' => '移除标签', + 'Do you really want to remove this tag: "%s"?' => '你真的要删除此标签:"%s"?', + 'Global tags' => '全局标签', + 'There is no global tag at the moment.' => '当前没有全局标签。', + 'This field cannot be empty' => '此栏不能为空', + 'Close a task when there is no activity in a specific column' => '当指定栏目没有更新时关闭任务', + '%s removed a subtask for the task #%d' => '"%s"从任务#%d删除了子任务', + '%s removed a comment on the task #%d' => '"%s"从任务#%d删除了评论', + 'Comment removed on task #%d' => '任务#%d上的评论已删除', + 'Subtask removed on task #%d' => '任务#%d上的子任务已删除', + 'Hide tasks in this column in the dashboard' => '在面板首页隐藏此栏下的任务', + '%s removed a comment on the task %s' => '"%s"从任务%s 删除了评论', + '%s removed a subtask for the task %s' => '"%s"从任务%s 删除了子任务', + 'Comment removed' => '评论已删除', + 'Subtask removed' => '子任务已删除', + '%s set a new internal link for the task #%d' => '%s为任务#%d 设置了新内部链接', + '%s removed an internal link for the task #%d' => '%s 从任务 #%d 删除内部链接', + 'A new internal link for the task #%d has been defined' => '#%d新内部链接已定义', + 'Internal link removed for the task #%d' => '#%d内部链接已删除', + '%s set a new internal link for the task %s' => '%s为任务%s设置了新链接', + '%s removed an internal link for the task %s' => '%s删除了任务%s内部链接', + 'Automatically set the due date on task creation' => '创建任务时自动设置到期时间', + 'Move the task to another column when closed' => '当任务关闭时移动到另一栏', + 'Move the task to another column when not moved during a given period' => '当任务在指定时间内未移动时,移动到另一栏', + 'Dashboard for %s' => '%s的看板', + 'Tasks overview for %s' => '%s的任务预览', + 'Subtasks overview for %s' => '%s的子任务预览', + 'Projects overview for %s' => '%s的项目预览', + 'Activity stream for %s' => '%s的活动足迹', + 'Assign a color when the task is moved to a specific swimlane' => '当任务移动到指定泳道时标记颜色', + 'Assign a priority when the task is moved to a specific swimlane' => '当任务移动到指定泳道时标记优先级', + 'User unlocked successfully.' => '用户解锁成功。', + 'Unable to unlock the user.' => '无法解锁用户。', + 'Move a task to another swimlane' => '移动任务到泳道', + 'Creator Name' => '创建人名称', + 'Time spent and estimated' => '时间花费预估', + 'Move position' => '移动位置', + 'Move task to another position on the board' => '移动任务到另一个位置', + 'Insert before this task' => '在此任务之前插入', + 'Insert after this task' => '在此任务之后插入', + 'Unlock this user' => '解锁用户', + 'Custom Project Roles' => '自定义项目角色', + 'Add a new custom role' => '添加新角色', + 'Restrictions for the role "%s"' => '"%s"角色限制', + 'Add a new project restriction' => '添加新的项目限制', + 'Add a new drag and drop restriction' => '添加新的拖放限制', + 'Add a new column restriction' => '添加新的栏目限制', + 'Edit this role' => '编辑角色', + 'Remove this role' => '删除角色', + 'There is no restriction for this role.' => '当前角色无限制。', + 'Only moving task between those columns is permitted' => '只能在以下栏目间移动任务', + 'Close a task in a specific column when not moved during a given period' => '当指定栏目下的任务在指定时间内未移动时关闭任务', + 'Edit columns' => '编辑栏目', + 'The column restriction has been created successfully.' => '成功创建栏目限制。', + 'Unable to create this column restriction.' => '无法创建栏目限制。', + 'Column restriction removed successfully.' => '成功删除栏目限制。', + 'Unable to remove this restriction.' => '无法删除限制。', + 'Your custom project role has been created successfully.' => '自定义项目角色创建成功。', + 'Unable to create custom project role.' => '无法删除自定义项目角色。', + 'Your custom project role has been updated successfully.' => '自定义项目角色更新成功。', + 'Unable to update custom project role.' => '无法更新自定义项目角色。', + 'Custom project role removed successfully.' => '成功删除自定义项目角色。', + 'Unable to remove this project role.' => '无法删除项目角色。', + 'The project restriction has been created successfully.' => '成功创建项目限制。', + 'Unable to create this project restriction.' => '无法创建项目限制。', + 'Project restriction removed successfully.' => '成功删除项目限制。', + 'You cannot create tasks in this column.' => '你不能在此栏目下创建任务。', + 'Task creation is permitted for this column' => '当前栏目下允许创建任务。', + 'Closing or opening a task is permitted for this column' => '当前栏目下允许开关任务。', + 'Task creation is blocked for this column' => '当前栏目下禁止创建任务。', + 'Closing or opening a task is blocked for this column' => '禁止在此栏下开关任务', + 'Task creation is not permitted' => '不能创建任务', + 'Closing or opening a task is not permitted' => '禁止开关任务', + 'New drag and drop restriction for the role "%s"' => '为角色"%s"新建拖动限制', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => '此角色下的用户将只能在源栏目和目标栏目间移动任务。', + 'Remove a column restriction' => '移除栏目限制', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => '你真的要删除栏目限制:"%s"到"%s"?', + 'New column restriction for the role "%s"' => '为角色"%s"增加栏目限制?', + 'Rule' => '规则', + 'Do you really want to remove this column restriction?' => '你真的要移除栏目限制?', + 'Custom roles' => '角色', + 'New custom project role' => '新建项目角色', + 'Edit custom project role' => '编辑项目角色', + 'Remove a custom role' => '删除自定义角色', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => '你真的要删除这个自定义橘色:"%s"?当前角色下的用户将转换成普通项目成员。', + 'There is no custom role for this project.' => '当前项目没有自定义角色', + 'New project restriction for the role "%s"' => '给角色"%s"新建项目限制', + 'Restriction' => '限制', + 'Remove a project restriction' => '移除项目限制', + 'Do you really want to remove this project restriction: "%s"?' => '你真的要删除项目限制:"%s"?', + 'Duplicate to multiple projects' => '复制到多个项目', + 'This field is required' => '此项必填', + 'Moving a task is not permitted' => '禁止移动任务', + 'This value must be in the range %d to %d' => '输入值必须在%d到%d之间', + 'You are not allowed to move this task.' => '你不能移动此任务', + 'API User Access' => 'API access', + 'Preview' => '预览', + 'Write' => '写入', + 'Write your text in Markdown' => '用markdown格式写入文本', + 'No personal API access token registered.' => '没有API access token 注册。', + 'Your personal API access token is "%s"' => '你的API access token 是 “%s”', + 'Remove your token' => '移除token', + 'Generate a new token' => '生成新的token', + 'Showing %d-%d of %d' => '本页显示 %d-%d 条,共有: %d 条', + 'Outgoing Emails' => '目标邮件', + 'Add or change currency rate' => '添加更改汇率', + 'Reference currency: %s' => '参考汇率:%s', + 'Add custom filters' => '添加过滤器', + 'Export' => '导出', + 'Add link label' => '添加链接标签', + 'Incompatible Plugins' => '不兼容插件', + 'Compatibility' => '兼容性', + 'Permissions and ownership' => '权限和所有者', + 'Priorities' => '属性', + 'Close this window' => '关闭窗口', + 'Unable to upload this file.' => '无法上传此文件。', + 'Import tasks' => '导入任务', + 'Choose a project' => '选择项目', + 'Profile' => '个人资料', + 'Application role' => '应用角色', + '%d invitations were sent.' => '%d个邀请已发送。', + '%d invitation was sent.' => '%d个邀请已发送。', + 'Unable to create this user.' => '无法创建该用户。', + 'Kanboard Invitation' => '看板邀请', + 'Visible on dashboard' => '控制面板可见', + 'Created at:' => '创建于:', + 'Updated at:' => '更新于:', + 'There is no custom filter.' => '当前没有自定义过滤。', + 'New User' => '新建用户', + 'Authentication' => '认证', + 'If checked, this user will use a third-party system for authentication.' => '选中时用户将启用第三方系统认证。', + 'The password is necessary only for local users.' => '只有本地用户才需要设置密码。', + 'You have been invited to register on Kanboard.' => '你被邀请注册看板。', + 'Click here to join your team' => '点击这里加入你的团队', + 'Invite people' => '邀请新用户', + 'Emails' => '邮件', + 'Enter one email address by line.' => '输入邮件地址,每行一个。', + 'Add these people to this project' => '添加用户到项目', + 'Add this person to this project' => '添加用户到项目', + 'Sign-up' => '注册', + 'Credentials' => '认证信息', + 'New user' => '新用户', + 'This username is already taken' => '该用户名已被占用', + 'Your profile must have a valid email address.' => '个人资料必须有一个有效email地址。', + 'TRL - Turkish Lira' => '土耳其里拉', + 'The project email is optional and could be used by several plugins.' => '项目邮件地址可选,适用于插件。', + 'The project email must be unique across all projects' => '项目邮件地址必须全局唯一', + 'The email configuration has been disabled by the administrator.' => '邮件配置被管理员禁用。', + 'Close this project' => '关闭项目', + 'Open this project' => '打开项目', + 'Close a project' => '关闭项目', + 'Do you really want to close this project: "%s"?' => '你真的要关闭项目:"%s"?', + 'Reopen a project' => '重开项目', + 'Do you really want to reopen this project: "%s"?' => '你真的要重开项目:"%s"?', + 'This project is open' => '项目已打开', + 'This project is closed' => '项目已关闭', + 'Unable to upload files, check the permissions of your data folder.' => '无法上传文件,请检查你的data目录权限。', + 'Another category with the same name exists in this project' => '该项目下同名分类已存在', + 'Comment sent by email successfully.' => '评论已通过邮件发送成功。', + 'Sent by email to "%s" (%s)' => '已发送邮件至"%s" (%s)', + 'Unable to read uploaded file.' => '无法读取上传文件。', + 'Database uploaded successfully.' => '数据库上传成功。', + 'Task sent by email successfully.' => '任务已通过邮件发送成功。', + 'There is no category in this project.' => '当前项目没有分类。', + 'Send by email' => '发送邮件', + 'Create and send a comment by email' => '通过电子邮件创建和发送评论', + 'Subject' => '主题', + 'Upload the database' => '上传数据库', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => '你可以上传先前下载的sqlite数据库(Gzip 格式)。', + 'Database file' => '数据库文件', + 'Upload' => '上传', + 'Your project must have at least one active swimlane.' => '项目必须包含至少一个活跃泳道。', + 'Project: %s' => '项目:%s', + 'Automatic action not found: "%s"' => '自动动作未找到:"%s"', + '%d projects' => '%d 项目', + '%d project' => '%d 项目', + 'There is no project.' => '当前没有项目。', + 'Sort' => '排序', + 'Project ID' => '项目ID', + 'Project name' => '项目名', + 'Public' => '公开', + 'Personal' => '私有', + '%d tasks' => '%d 任务', + '%d task' => '%d 任务', + 'Task ID' => '任务ID', + 'Assign automatically a color when due date is expired' => '当任务过期时自动标注颜色', + 'Total score in this column across all swimlanes' => '该栏目所有泳道上的总分', + 'HRK - Kuna' => '克罗地亚库纳', + 'ARS - Argentine Peso' => '阿根廷比索', + 'COP - Colombian Peso' => '哥伦比亚比索', + '%d groups' => '%d 用户组', + '%d group' => '%d 用户组', + 'Group ID' => '用户组ID', + 'External ID' => '附属ID', + '%d users' => '%d 用户', + '%d user' => '%d 用户', + 'Hide subtasks' => '显示子任务', + 'Show subtasks' => '隐藏子任务', + 'Authentication Parameters' => '验证参数', + 'API Access' => 'API授权', + 'No users found.' => '未找到用户。', + 'User ID' => '用户ID', + 'Notifications are activated' => '通知已启用', + 'Notifications are disabled' => '通知已禁用', + 'User disabled' => '用户已禁用', + '%d notifications' => '%d 通知', + '%d notification' => '%d 通知', + 'There is no external integration installed.' => '当前没有外部整合安装。', + 'You are not allowed to update tasks assigned to someone else.' => '你没有权限更新任务指派。', + 'You are not allowed to change the assignee.' => '你没有权限更改当前受理人。', + 'Task suppression is not permitted' => '不允许抑制任务', + 'Changing assignee is not permitted' => '不允许更改受理人', + 'Update only assigned tasks is permitted' => '只允许更新已受理任务', + 'Only for tasks assigned to the current user' => '只作用于当前用户已受理任务', + 'My projects' => '我的项目', + 'You are not a member of any project.' => '你不是任何项目的成员。', + 'My subtasks' => '我的子任务', + '%d subtasks' => '%d 子任务', + '%d subtask' => '%d 子任务', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => '只允许在这些栏目间移动受理给当前用户的任务', + '[DUPLICATE]' => '[重复]', + 'DKK - Danish Krona' => '丹麦克朗', + 'Remove user from group' => '从用户组中移除用户', + 'Assign the task to its creator' => '将任务分配给创建者', + 'This task was sent by email to "%s" with subject "%s".' => '任务已通过邮件发送给"%s"(标题:"%s")。', + 'Predefined Email Subjects' => '预定义的邮件主题', + 'Write one subject by line.' => '添加主题(每行)。', + 'Create another link' => '创建新链接', + 'BRL - Brazilian Real' => '巴西雷亚尔', + 'Add a new Kanboard task' => '添加新任务', + 'Subtask not started' => '子任务未开始', + 'Subtask currently in progress' => '子任务进行中', + 'Subtask completed' => '子任务已完成', + 'Subtask added successfully.' => '子任务添加成功。', + '%d subtasks added successfully.' => '%d子任务添加成功。', + 'Enter one subtask by line.' => '添加子任务(每行)。', + 'Predefined Contents' => '预设内容', + 'Predefined contents' => '预设内容', + 'Predefined Task Description' => '预定义任务描述', + 'Do you really want to remove this template? "%s"' => '你真的要移除该模板?"%s"', + 'Add predefined task description' => '添加预定义任务描述', + 'Predefined Task Descriptions' => '预定义任务描述', + 'Template created successfully.' => '模板添加成功。', + 'Unable to create this template.' => '无法添加该模板。', + 'Template updated successfully.' => '模板更新成功。', + 'Unable to update this template.' => '无法更新此模板。', + 'Template removed successfully.' => '模板删除成功。', + 'Unable to remove this template.' => '无法删除该模板。', + 'Template for the task description' => '任务描述模板', + 'The start date is greater than the end date' => '开始日期晚于结束日期', + 'Tags must be separated by a comma' => '标签必须以逗号分割', + 'Only the task title is required' => '只有任务标题是必须的', + 'Creator Username' => '创建人', + 'Color Name' => '颜色名称', + 'Column Name' => '栏目名称', + 'Swimlane Name' => '泳道名称', + 'Time Estimated' => '预计耗时', + 'Time Spent' => '时间花费', + 'External Link' => '外部链接', + 'This feature enables the iCal feed, RSS feed and the public board view.' => '该功能将开启iCal订阅, RSS订阅以及公共查看权限。', + 'Stop the timer of all subtasks when moving a task to another column' => '当移动任务到别的栏目组时停止所有子任务计时器', + 'Subtask Title' => '子任务标题', + 'Add a subtask and activate the timer when moving a task to another column' => '当移动任务到别的栏目组时添加子任务并开始计时', + 'days' => '天', + 'minutes' => '分钟', + 'seconds' => '秒', + 'Assign automatically a color when preset start date is reached' => '当到达预设起始日期时自动指派颜色', + 'Move the task to another column once a predefined start date is reached' => '当到达预定起始日期时移动任务到另一栏目', + 'This task is now linked to the task %s with the relation "%s"' => '该任务与任务:%s 已建立"%s"关联', + 'The link with the relation "%s" to the task %s has been removed' => '该任务与任务:%s 已解除"%s"关联', + 'Custom Filter:' => '自定义过滤器:', + 'Unable to find this group.' => '无法找到该用户组。', + '%s moved the task #%d to the column "%s"' => '%s将任务 #%d 移动到了 "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => '%s将任务 #%d 移动到了 "%s" 的 %d', + '%s moved the task #%d to the swimlane "%s"' => '%s将任务 #%d 移动到了泳道 "%s" 下', + '%sh spent' => '花费%s小时', + '%sh estimated' => '预估%s小时', + 'Select All' => '全选', + 'Unselect All' => '取消', + 'Apply action' => '应用动作', + 'Move selected tasks to another column or swimlane' => '移动选中任务到另一列', + 'Edit tasks in bulk' => '批量编辑任务', + 'Choose the properties that you would like to change for the selected tasks.' => '选择你想要批量编辑的任务属性。', + 'Configure this project' => '项目配置', + 'Start now' => '立即开始', + '%s removed a file from the task #%d' => '%s 从任务 #%d 删除了文件', + 'Attachment removed from task #%d: %s' => '附件已从任务 #%d: %s 中删除', + 'No color' => '未标色', + 'Attachment removed "%s"' => '附件已删除 "%s"', + '%s removed a file from the task %s' => '%s 从任务 %s 中删除了文件', + 'Move the task to another swimlane when assigned to a user' => '将分配给用户的任务移动到另一个泳道', + 'Destination swimlane' => '目标泳道', + 'Assign a category when the task is moved to a specific swimlane' => '将移动到指定泳道的任务设定分类', + 'Move the task to another swimlane when the category is changed' => '当分类改变时将任务移动到另一个泳道', + 'Reorder this column by priority (ASC)' => '按优先级重排(升序)', + 'Reorder this column by priority (DESC)' => '按优先级重排(降序)', + 'Reorder this column by assignee and priority (ASC)' => '按指派和优先级重排(升序)', + 'Reorder this column by assignee and priority (DESC)' => '按指派和优先级重排(降序)', + 'Reorder this column by assignee (A-Z)' => '按指派重排(A-Z)', + 'Reorder this column by assignee (Z-A)' => '按指派重排(Z-A)', + 'Reorder this column by due date (ASC)' => '按逾期时间重排(升序)', + 'Reorder this column by due date (DESC)' => '按逾期时间重排(降序)', + 'Reorder this column by id (ASC)' => '按ID重新排序此列 (升序)', + 'Reorder this column by id (DESC)' => '按ID重新排序此列 (降序)', + '%s moved the task #%d "%s" to the project "%s"' => '%s 已将任务 #%d "%s" 移动到项目"%s" ', + 'Task #%d "%s" has been moved to the project "%s"' => '任务 #%d "%s" 已被移动到项目 "%s"', + 'Move the task to another column when the due date is less than a certain number of days' => '当任务即将在指定天数内超期时移动到另一栏', + 'Automatically update the start date when the task is moved away from a specific column' => '当任务离开指定栏目时自动更新开始时间', + 'HTTP Client:' => 'HTTP客户端', + 'Assigned' => '已分配', + 'Task limits apply to each swimlane individually' => '任务限制分别应用于每个泳道', + 'Column task limits apply to each swimlane individually' => '列任务限制分别应用于每个泳道', + 'Column task limits are applied to each swimlane individually' => '列任务限制已经被分别应用于每个泳道', + 'Column task limits are applied across swimlanes' => '列任务限制已经被跨泳道应用', + 'Task limit: ' => '任务限制', + 'Change to global tag' => '更改为全局标签', + 'Do you really want to make the tag "%s" global?' => '您是否真的想将标签 "%s" 设置为全局标签?', + 'Enable global tags for this project' => '为该项目启用全局标签', + 'Group membership(s):' => '群组成员:', + '%s is a member of the following group(s): %s' => '%s 是以下组的成员: %s', + '%d/%d group(s) shown' => '%d/%d 个组已显示', + 'Subtask creation or modification' => '子任务创建或修改', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => '将任务移至特定泳道时,将任务分配给指定用户', + 'Comment' => '评论', + 'Collapse vertically' => '栏内滚动', + 'Expand vertically' => '全屏滚动', + 'MXN - Mexican Peso' => '墨西哥比索', + 'Estimated vs actual time per column' => '每栏目预计和实际耗时', + 'HUF - Hungarian Forint' => '匈牙利 - 福林', + 'XBT - Bitcoin' => 'XBT - 比特币', + 'You must select a file to upload as your avatar!' => '你必须选择一张图片作为你的头像', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => '你所上传的文件不是有效图像!(只有*.gif, *.jpg, *.jpeg and *.png才被允许!)', + 'Automatically set the due date when the task is moved away from a specific column' => '当任务离开指定栏目时自动设置到期时间', + 'No other projects found.' => '未找到其它项目。', + 'Tasks copied successfully.' => '任务复制成功。', + 'Unable to copy tasks.' => '无法复制任务。', + 'Theme' => '主题', + 'Theme:' => '主题:', + 'Light theme' => '浅色主题', + 'Dark theme' => '深色主题', + 'Automatic theme - Sync with system' => '自动主题 - 同步系统设置', + 'Application managers or more' => '应用程序经理或更多', + 'Administrators' => '管理员', + 'Visibility:' => '可见性:', + 'Standard users' => '标准用户', + 'Visibility is required' => '可见性是必需的', + 'The visibility should be an app role' => '可见性应为应用角色', + 'Reply' => '回复', + '%s wrote: ' => '%s 写道:', + 'Number of visible tasks in this column and swimlane' => '此列和泳道中可见任务的数量', + 'Number of tasks in this swimlane' => '此泳道中的任务数', + 'Unable to find another subtask in progress, you can close this window.' => '找不到另一个正在进行的子任务,您可以关闭此窗口。', + 'This theme is invalid' => '此主题无效', + 'This role is invalid' => '此角色无效', + 'This timezone is invalid' => '此时区无效', + 'This language is invalid' => '此语言无效', + 'This URL is invalid' => '此URL无效', + 'Date format invalid' => '日期格式无效', + 'Time format invalid' => '时间格式无效', + 'Invalid Mail transport' => '无效的邮件传输', + 'Color invalid' => '颜色无效', + 'This value must be greater or equal to %d' => '此值必须大于或等于 %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => '在文件开头添加 BOM(Microsoft Excel 需要)', + 'Just add these tag(s)' => '只需添加这些标签', + 'Remove internal link(s)' => '移除内部链接', + 'Import tasks from another project' => '从其他项目导入任务', + 'Select the project to copy tasks from' => '选择要从中复制任务的项目', + 'The total maximum allowed attachments size is %sB.' => '允许的附件总大小上限为 %sB。', + 'Add attachments' => '添加附件', + 'Task #%d "%s" is overdue' => '任务 #%d "%s" 已逾期', + 'Enable notifications by default for all new users' => '默认启用所有新用户的通知', + 'Assign the task to its creator for specific columns if no assignee is set manually' => '如果未手动指定负责人,则在指定列中将任务分配给创建者', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => '如果未分配用户,则在列变更移动到指定列时将任务分配给已登录用户', +]; diff --git a/app/Locale/zh_TW/translations.php b/app/Locale/zh_TW/translations.php new file mode 100644 index 0000000..2505115 --- /dev/null +++ b/app/Locale/zh_TW/translations.php @@ -0,0 +1,1472 @@ +<?php + +return [ + 'number.decimals_separator' => '.', + 'number.thousands_separator' => ',', + 'None' => '無', + 'Edit' => '編輯', + 'Remove' => '移除', + 'Yes' => '是', + 'No' => '否', + 'cancel' => '取消', + 'or' => '或者', + 'Yellow' => '黄色', + 'Blue' => '藍色', + 'Green' => '綠色', + 'Purple' => '紫色', + 'Red' => '红色', + 'Orange' => '橘色', + 'Grey' => '灰色', + 'Brown' => '褐色', + 'Deep Orange' => '橘红色', + 'Dark Grey' => '深灰色', + 'Pink' => '粉红色', + 'Teal' => '青色', + 'Cyan' => '藍綠色', + 'Lime' => '黃綠色', + 'Light Green' => '淺綠色', + 'Amber' => '黄褐色', + 'Save' => '儲存', + 'Login' => '登入', + 'Official website:' => '官方網站:', + 'Unassigned' => '未指派', + 'View this task' => '檢視任務', + 'Remove user' => '移除使用者', + 'Do you really want to remove this user: "%s"?' => '確定要刪除使用者"%s"嗎?', + 'All users' => '所有使用者', + 'Username' => '使用者名稱', + 'Password' => '密碼', + 'Administrator' => '管理員', + 'Sign in' => '登入', + 'Users' => '使用者', + 'Forbidden' => '禁止', + 'Access Forbidden' => '禁止進入', + 'Edit user' => '修改使用者', + 'Logout' => '登出', + 'Bad username or password' => '使用者名稱或密碼錯誤', + 'Edit project' => '修改專案', + 'Name' => '名稱', + 'Projects' => '專案', + 'No project' => '無專案', + 'Project' => '專案', + 'Status' => '狀態', + 'Tasks' => '任務', + 'Board' => '看板', + 'Actions' => '動作', + 'Inactive' => '未啟用', + 'Active' => '啟用', + 'Unable to update this board.' => '無法更新看板。', + 'Disable' => '停用', + 'Enable' => '啟用', + 'New project' => '新建專案', + 'Do you really want to remove this project: "%s"?' => '確定要移除專案"%s"嗎?', + 'Remove project' => '移除專案', + 'Edit the board for "%s"' => '"%s"修改看板', + 'Add a new column' => '新增欄位', + 'Title' => '標題', + 'Assigned to %s' => '指派给 %s', + 'Remove a column' => '刪除一個欄位', + 'Unable to remove this column.' => '無法刪除此欄位。', + 'Do you really want to remove this column: "%s"?' => '確定要移除此欄位"%s"嗎?', + 'Settings' => '設定', + 'Application settings' => '應用程式設定', + 'Language' => '語言', + 'Webhook token:' => 'Webhook token:', + 'API token:' => 'API token:', + 'Database size:' => '資料庫大小:', + 'Download the database' => '下載資料庫', + 'Optimize the database' => '優化資料庫', + '(VACUUM command)' => '(VACUUM 指令)', + '(Gzip compressed Sqlite file)' => '(用Gzip壓縮的Sqlite文件)', + 'Close a task' => '關閉任務', + 'Column' => '欄位', + 'Color' => '顏色', + 'Assignee' => '負責人', + 'Create another task' => '新增另一個任務', + 'New task' => '新建任務', + 'Open a task' => '打開任務', + 'Do you really want to open this task: "%s"?' => '確定要打開這個任務嗎?"%s"', + 'Back to the board' => '回到看板', + 'There is nobody assigned' => '無人被指派', + 'Column on the board:' => '看板上的欄位:', + 'Close this task' => '關閉任務', + 'Open this task' => '開啟任務', + 'There is no description.' => '没有描述。', + 'Add a new task' => '新增一個任務', + 'The username is required' => '使用者名稱為必要資料', + 'The maximum length is %d characters' => '最長%d個英文字母', + 'The minimum length is %d characters' => '最短%d個英文字母', + 'The password is required' => '需要密碼', + 'This value must be an integer' => '必須為整数', + 'The username must be unique' => '使用者名稱重複', + 'The user id is required' => '帳號id是必要的', + 'Passwords don\'t match' => '密碼不符', + 'The confirmation is required' => '需要確認', + 'The project is required' => '需要指定專案', + 'The id is required' => '需要指定編號', + 'The project id is required' => '需要指定專案編號', + 'The project name is required' => '需要指定專案名稱', + 'The title is required' => '需要指定標題', + 'Settings saved successfully.' => '設定儲存成功。', + 'Unable to save your settings.' => '無法儲存你的設定。', + 'Database optimization done.' => '資料庫優化完成。', + 'Your project has been created successfully.' => '您的專案已經建立完成。', + 'Unable to create your project.' => '無法建立您的專案。', + 'Project updated successfully.' => '更新專案成功。', + 'Unable to update this project.' => '無法更新專案。', + 'Unable to remove this project.' => '無法移除專案。', + 'Project removed successfully.' => '移除專案成功。', + 'Project activated successfully.' => '專案啟用成功。', + 'Unable to activate 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.' => '無法更新您的任務。', + 'Task updated successfully.' => '更新任務成功。', + 'Unable to create your task.' => '無法建立任務。', + 'Task created successfully.' => '任務建立成功。', + 'User created successfully.' => '使用者建立成功。', + 'Unable to create your user.' => '無法建立使用者。', + 'User updated successfully.' => '使用者更新成功。', + 'User removed successfully.' => '使用者移除成功。', + 'Unable to remove this user.' => '無法移除這個使用者。', + 'Board updated successfully.' => '看板更新成功。', + 'Ready' => '預備', + 'Backlog' => '待辦', + 'Work in progress' => '進行中', + 'Done' => '完成', + 'Application version:' => '應用程式版本:', + 'Id' => '編號', + 'Public link' => '公開連結', + 'Timezone' => '時區', + 'Sorry, I didn\'t find this information in my database!' => '抱歉,找不到任何訊息!', + 'Page not found' => '找不到頁面', + 'Complexity' => '複雜度', + 'Task limit' => '任務上限', + 'Task count' => '任務數量', + 'User' => '使用者', + 'Comments' => '評論', + 'Comment is required' => '評論不能空白', + 'Comment added successfully.' => '評論增加完成。', + 'Unable to create your comment.' => '無法建立評論。', + 'Due Date' => '到期日期', + 'Invalid date' => '無效日期', + 'Automatic actions' => '自動化動作', + 'Your automatic action has been created successfully.' => '您的自動化動作已建立', + 'Unable to create your automatic action.' => '無法建立自動化動作。', + 'Remove an action' => '移除一個自動化動作。', + 'Unable to remove this action.' => '無法移除自動化動作', + 'Action removed successfully.' => '移除自動化動作成功。', + 'Automatic actions for the project "%s"' => '專案"%s"的自動化動作', + 'Add an action' => '增加一個動作', + 'Event name' => '事件名稱', + 'Action' => '動作', + 'Event' => '事件', + 'When the selected event occurs execute the corresponding action.' => '當所選事件發生時執行動作。', + 'Next step' => '下一步', + 'Define action parameters' => '定義動作参数', + 'Do you really want to remove this action: "%s"?' => '確定要移除此動作"%s"嗎?', + 'Remove an automatic action' => '移除自動化動作', + 'Assign the task to a specific user' => '將任務指派给使用者', + 'Assign the task to the person who does the action' => '將任務指派给產生動作的使用者', + 'Duplicate the task to another project' => '複製此任務到另一專案', + 'Move a task to another column' => '移動任務到另一欄位', + 'Task modification' => '任務修改', + 'Task creation' => '任務建立', + 'Closing a task' => '正在關閉任務', + 'Assign a color to a specific user' => '指派顏色給特定使用者', + 'Position' => '位置', + 'Duplicate to project' => '複製到另一專案', + 'Duplicate' => '複製', + 'Link' => '連接', + 'Comment updated successfully.' => '評論更新成功。', + 'Unable to update your comment.' => '無法更新您的評論。', + 'Remove a comment' => '移除一則評論', + 'Comment removed successfully.' => '評論移除成功。', + 'Unable to remove this comment.' => '無法移除評論。', + 'Do you really want to remove this comment?' => '確定要移除此評論嗎?', + 'Current password for the user "%s"' => '使用者"%s"的目前密碼', + 'The current password is required' => '需要輸入目前的密碼', + 'Wrong password' => '密碼錯誤', + 'Unknown' => '未知', + 'Last logins' => '上次登入', + 'Login date' => '登入日期', + 'Authentication method' => '認證方式', + 'IP address' => 'IP位址', + 'User agent' => '瀏覽器標示', + 'Persistent connections' => '登入會話', + 'No session.' => '無會話', + 'Expiration date' => '過期日期', + 'Remember Me' => '記住我', + 'Creation date' => '建立日期', + 'Everybody' => '所有人', + 'Open' => '打開', + 'Closed' => '關閉', + 'Search' => '搜尋', + 'Nothing found.' => '找不到任何資料。', + 'Due date' => '到期日期', + 'Description' => '描述', + '%d comments' => '%d個評論', + '%d comment' => '%d個評論', + 'Email address invalid' => '無效的電子信箱', + 'Your external account is not linked anymore to your profile.' => '你的外部帳號關聯已解除', + 'Unable to unlink your external account.' => '無法關聯到你的外部帳號', + 'External authentication failed' => '外部認證失败', + 'Your external account is linked to your profile successfully.' => '你已成功關聯到你的外部帳號', + 'Email' => '電子信箱', + 'Task removed successfully.' => '刪除任務完成', + 'Unable to remove this task.' => '無法刪除任務。', + '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' => '自動指派分類到一個顏色', + 'Task creation or modification' => '任務建立或修改', + 'Category' => '分類', + 'Category:' => '分類:', + 'Categories' => '分類', + 'Your category has been created successfully.' => '建立分類成功。', + 'This category has been updated successfully.' => '更新分類成功。', + 'Unable to update this category.' => '無法更新分類。', + 'Remove a category' => '移除一個分類', + 'Category removed successfully.' => '分類移除成功。', + 'Unable to remove this category.' => '無法移除分類。', + 'Category modification for the project "%s"' => '為專案"%s"修改分類', + 'Category Name' => '分類名稱', + 'Add a new category' => '加入新分類', + 'Do you really want to remove this category: "%s"?' => '確定要移除此分類"%s"ㄇㄚ?', + 'All categories' => '所有分類', + 'No category' => '無分類', + 'The name is required' => '必需要有名字', + 'Remove a file' => '移除一個文件', + 'Unable to remove this file.' => '無法移除文件。', + 'File removed successfully.' => '文件移除成功。', + 'Attach a document' => '附加文件', + 'Do you really want to remove this file: "%s"?' => '確定要移除文件"%s"嗎?', + 'Attachments' => '附件', + 'Edit the task' => '修改任務', + 'Add a comment' => '新增評論', + 'Edit a comment' => '編輯評論', + 'Summary' => '摘要', + 'Time tracking' => '時間紀錄', + 'Estimate:' => '預估:', + 'Spent:' => '花費:', + 'Do you really want to remove this sub-task?' => '確認要刪除此子任務?', + 'Remaining:' => '剩餘:', + 'hours' => '小時', + 'estimated' => '預估', + 'Sub-Tasks' => '子任務', + 'Add a sub-task' => '增加子任務', + 'Original estimate' => '初步預估', + 'Create another sub-task' => '建立另一個子任務', + 'Time spent' => '時間花費', + 'Edit a sub-task' => '編輯子任務', + 'Remove a sub-task' => '刪除子任務', + 'The time must be a numeric value' => '時間必需是數字', + 'Todo' => '待完成', + 'In progress' => '正在進行', + 'Sub-task removed successfully.' => '删除子任務成功', + 'Unable to remove this sub-task.' => '無法删除此任務', + 'Sub-task updated successfully.' => '建立子任務成功', + 'Unable to update your sub-task.' => '無法更新子任務', + 'Unable to create your sub-task.' => '無法建立子任務', + 'Maximum size: ' => '大小上限:', + 'Display another project' => '顯示其它專案', + 'Created by %s' => '建立者:%s', + 'Tasks Export' => '任務匯出', + 'Start Date' => '開始時間', + 'Execute' => '執行', + 'Task Id' => '任務編號', + 'Creator' => '建立者', + 'Modification date' => '修改日期', + 'Completion date' => '完成日期', + 'Clone' => '複製', + 'Project cloned successfully.' => '複製專案成功。', + 'Unable to clone this project.' => '無法複製專案', + 'Enable email notifications' => '啟用郵件通知', + 'Task position:' => '任務位置:', + 'The task #%d has been opened.' => '任務#%d已經被打開.', + 'The task #%d has been closed.' => '任務#%d已經被關閉.', + 'Sub-task updated' => '子任務已更新', + 'Title:' => '標題:', + 'Status:' => '狀態:', + 'Assignee:' => '負責人:', + 'Time tracking:' => '時間紀錄', + 'New sub-task' => '新建子任務', + 'New attachment added "%s"' => '加入新附件"%s"', + 'New comment posted by %s' => '%s 的新評論', + 'New comment' => '新增評論', + 'Comment updated' => '更新了評論', + 'New subtask' => '新建子任務', + 'I only want to receive notifications for these projects:' => '我謹需要收到下面專案的通知:', + 'view the task on Kanboard' => '在看板中查看此任務', + 'Public access' => '公開瀏覽', + 'Disable public access' => '停止公開瀏覽', + 'Enable public access' => '開啟公開瀏覽', + 'Public access disabled' => '已經禁止公開瀏覽', + 'Move the task to another project' => '移動任務到其它專案', + 'Move to project' => '移動到其它專案', + 'Do you really want to duplicate this task?' => '確認要複製此任務嗎?', + 'Duplicate a task' => '複製任務', + 'External accounts' => '外部帳號', + 'Account type' => '帳號類型', + 'Local' => '本地', + 'Remote' => '遠端', + 'Enabled' => '啟用', + 'Disabled' => '停用', + 'Login:' => '使用者名稱:', + 'Full Name:' => '姓名:', + 'Email:' => 'Email:', + 'Notifications:' => '通知:', + 'Notifications' => '通知', + 'Account type:' => '帳號類型:', + 'Edit profile' => '編輯屬性', + 'Change password' => '修改密碼', + 'Password modification' => '修改密碼', + 'External authentications' => '外部認證', + 'Never connected.' => '從未連接。', + 'No external authentication enabled.' => '未啟用外部認證。', + 'Password modified successfully.' => '修改密碼成功。', + 'Unable to change the password.' => '無法修改密碼。', + 'Change category' => '變更分類', + '%s updated the task %s' => '%s 更新了任務 %s', + '%s opened the task %s' => '%s 開啟了任務 %s', + '%s moved the task %s to the position #%d in the column "%s"' => '%s 將任務 %s 移動到"%s"的第#%d個位置', + '%s moved the task %s to the column "%s"' => '%s 移動任務 %s 到欄位 "%s"', + '%s created the task %s' => '%s 建立了任務 %s', + '%s closed the task %s' => '%s 關閉了任務 %s', + '%s created a subtask for the task %s' => '%s 建立了 %s的子任務', + '%s updated a subtask for the task %s' => '%s 更新了 %s的子任務', + 'Assigned to %s with an estimate of %s/%sh' => '分派给 %s,預估需要 %s/%s 小時', + 'Not assigned, estimate of %sh' => '未分派,預估需要 %s 小時', + '%s updated a comment on the task %s' => '%s 更新了任務 %s的評論', + '%s commented the task %s' => '%s 評論了任務 %s', + '%s\'s activity' => '%s的動態', + 'RSS feed' => 'RSS 連結', + '%s updated a comment 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', + '%s created the task #%d' => '%s 建立了任務 #%d', + '%s closed the task #%d' => '%s 關閉了任務 #%d', + '%s opened the task #%d' => '%s 開啟了任務 #%d', + 'Activity' => '動態', + 'Default values are "%s"' => '預設值為 "%s"', + 'Default columns for new projects (Comma-separated)' => '新建專案的預設欄位(用逗號分開)', + 'Task assignee change' => '任務分配變更', + '%s changed the assignee of the task #%d to %s' => '%s 將任務 #%d 分配给了 %s', + '%s changed the assignee of the task %s to %s' => '%s 將任務 %s 分配给 %s', + 'New password for the user "%s"' => '使用者"%s"的新密碼', + 'Choose an event' => '選擇一個事件', + 'Create a task from an external provider' => '從外部建立任務', + 'Change the assignee based on an external username' => '根據外部使用者名稱修改任務分配', + 'Change the category based on an external label' => '根據外部標籤修改分類', + 'Reference' => '參考', + 'Label' => '標籤', + 'Database' => '資料庫', + 'About' => '關於', + 'Database driver:' => '資料庫驅動:', + 'Board settings' => '看板設定', + 'Webhook settings' => 'Webhook 設定', + 'Reset token' => '重設 token', + 'API endpoint:' => 'API endpoint:', + 'Refresh interval for personal board' => '私人看板的更新時間', + 'Refresh interval for public board' => '公共看板的更新時間', + 'Task highlight period' => '任務高亮時間', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => '多久内的任務視為剛剛修改(單位:秒,設為0提用,預設是2天', + 'Frequency in second (60 seconds by default)' => '頻率,單位為秒(預設是60秒)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => '頻率,單位為秒(設為0停用此功能,預設是10秒)', + 'Application URL' => '應用程式URL', + 'Token regenerated.' => 'Token已重新產生', + 'Date format' => '日期格式', + 'ISO format is always accepted, example: "%s" and "%s"' => 'ISO 格式總是允許的,例如:"%s" 和 "%s"', + 'New personal project' => '新建私人專案', + 'This project is personal' => '此專案為私人專案', + 'Add' => '增加', + 'Start date' => '開始日期', + 'Time estimated' => '預計時間', + 'There is nothing assigned to you.' => '目前無任務指派给你。', + 'My tasks' => '我的任務', + 'Activity stream' => '動態記錄', + 'Dashboard' => '儀表板', + 'Confirmation' => '確認', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Create a comment from an external provider' => '從外部建立一個評論', + 'Project management' => '專案管理', + 'Columns' => '欄位', + 'Task' => '任務', + 'Percentage' => '百分比', + 'Number of tasks' => '任務數', + 'Task distribution' => '任務分佈', + 'Analytics' => '分析', + 'Subtask' => '子任務', + 'User repartition' => '使用者分析', + 'Clone this project' => '複製此專案', + 'Column removed successfully.' => '删除欄位成功。', + 'Not enough data to show the graph.' => '資料不足,無法繪圖。', + 'Previous' => '上一頁', + 'The id must be an integer' => '編號必須為整數', + 'The project id must be an integer' => '專案編號必須為整數', + 'The status must be an integer' => '狀態必須為整數', + 'The subtask id is required' => '必須提供子任務編號', + 'The subtask id must be an integer' => '子任務編號必須為整數', + 'The task id is required' => '需要任務編號', + 'The task id must be an integer' => '任務編號必須為整数', + 'The user id must be an integer' => '使用者編號必須為整數', + 'This value is required' => '這個數值是必要的', + 'This value must be numeric' => '這個值必須為數字', + 'Unable to create this task.' => '無法建立此任務。', + 'Cumulative flow diagram' => '累積流程圖', + 'Daily project summary' => '每日專案匯總', + 'Daily project summary export' => '匯出每日專案匯總', + 'Exports' => '匯出', + 'This export contains the number of tasks per column grouped per day.' => '此匯出包含每列的任務數,按天分组', + 'Active swimlanes' => '活動里程碑', + 'Add a new swimlane' => '增加新里程碑', + 'Default swimlane' => '預設里程碑', + 'Do you really want to remove this swimlane: "%s"?' => '確定要删除里程碑:"%s"?', + 'Inactive swimlanes' => '非活動里程碑', + 'Remove a swimlane' => '删除里程碑', + 'Swimlane modification for the project "%s"' => '專案"%s"的里程碑變更', + 'Swimlane removed successfully.' => '删除里程碑成功', + 'Swimlanes' => '里程碑', + 'Swimlane updated successfully.' => '更新里程碑成功。', + 'Unable to remove this swimlane.' => '無法删除此里程碑', + 'Unable to update this swimlane.' => '無法更新此里程碑', + 'Your swimlane has been created successfully.' => '你的里程碑已成功建立。', + 'Example: "Bug, Feature Request, Improvement"' => '範例:“缺陷,功能需求,提升', + 'Default categories for new projects (Comma-separated)' => '新項目的預設分類(用逗號分隔)', + 'Integrations' => '整合', + 'Integration with third-party services' => '與第三方服務整合', + 'Subtask Id' => '子任務編號', + 'Subtasks' => '子任務', + 'Subtasks Export' => '子任務匯出', + 'Task Title' => '任務標題', + 'Untitled' => '無標題', + 'Application default' => '程序預設', + 'Language:' => '語言:', + 'Timezone:' => '時區:', + 'All columns' => '全部欄位', + 'Next' => '下一頁', + '#%d' => '#%d', + 'All swimlanes' => '全部里程碑', + 'All colors' => '全部顏色', + 'Moved to column %s' => '移動到欄位 %s', + 'User dashboard' => '使用者儀俵板', + 'Allow only one subtask in progress at the same time for a user' => '每使用者同時只能有一個活動子任務', + 'Edit column "%s"' => '編輯欄位"%s"', + '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?' => '要複製專案的哪些内容?', + 'Disallow login form' => '禁止登入', + 'Start' => '開始', + 'End' => '結束', + 'Task age in days' => '任務存在天數', + 'Days in this column' => '在此欄位的天數', + '%dd' => '%d天', + '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' => '連結修改', + 'Opposite label' => '反向標籤', + 'Remove a link' => '删除連結', + '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' => '<1h', + '%dh' => '%dh', + 'Expand tasks' => '展開任務', + 'Collapse tasks' => '收合任務', + 'Expand/collapse tasks' => '展開/收合任務', + 'Close dialog box' => '關閉對話框', + 'Submit a form' => '提交表單', + 'Board view' => '面板視圖', + 'Keyboard shortcuts' => '鍵盤快速鍵', + 'Open board switcher' => '打開面板切換器', + 'Application' => '應用程序', + 'Compact view' => '緊湊視圖', + 'Horizontal scrolling' => '水平捲動', + 'Compact/wide view' => '緊湊/寬視圖', + 'Currency' => '貨幣', + 'Personal project' => '私人項目', + 'AUD - Australian Dollar' => '澳幣', + 'CAD - Canadian Dollar' => '加元', + 'CHF - Swiss Francs' => '瑞士法郎', + 'Custom Stylesheet' => '自定義樣式表', + 'EUR - Euro' => '歐元', + 'GBP - British Pound' => '英鎊', + 'INR - Indian Rupee' => '印度盧比', + 'JPY - Japanese Yen' => '日元', + 'NZD - New Zealand Dollar' => '紐西蘭元', + 'PEN - Peruvian Sol' => 'PEN - 秘鲁索尔', + 'RSD - Serbian dinar' => '第納爾', + 'CNY - Chinese Yuan' => '人民幣', + 'USD - US Dollar' => '美元', + 'VES - Venezuelan Bolívar' => 'VES - 委內瑞拉玻利瓦爾', + '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' => '原欄位', + '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' => '修改参考貨幣', + 'Reference currency' => '参考貨幣', + 'The currency rate has been added successfully.' => '增加匯率成功。', + 'Unable to add this currency rate.' => '無法增加此匯率', + 'Webhook URL' => 'Webhook URL', + '%s removed the assignee of the task %s' => '%s删除了任務%s的負責人', + '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' => '雙重認證', + 'This QR code contains the key URI: ' => '此二維條碼包含密碼 URI:', + 'Check my code' => '檢查我的認證碼', + 'Secret key: ' => '密碼:', + 'Test your device' => '測試你的設備', + 'Assign a color when the task is moved to a specific column' => '任務移動到指定欄位時設定顏色', + '%s via Kanboard' => '%s 通過KanBoard', + 'Burndown chart' => '燃盡圖', + 'This chart show the task complexity over the time (Work Remaining).' => '圖表顯示任務複雜度基於時間(剩餘時間)', + 'Screenshot taken %s' => '已截圖 %s', + 'Add a screenshot' => '增加截圖', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => '獲取截圖並按CTRL+V或者⌘+V黏貼到這裡', + 'Screenshot uploaded successfully.' => '截圖上傳成功', + 'SEK - Swedish Krona' => '瑞郎', + 'Identifier' => '標示符', + 'Disable two factor authentication' => '停用雙重認證', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => '你真的要停用 "%s" 的雙重認證嗎?', + 'Edit link' => '編輯連結', + 'Start to type task title...' => '輸入任務標題...', + 'A task cannot be linked to itself' => '任務不能連結到本身', + 'The exact same link already exists' => '相同的連結已存在', + 'Recurrent task is scheduled to be generated' => '循環性任務將按計畫生成', + 'Score' => '積分', + 'The identifier must be unique' => '標示符必須唯一', + 'This linked task id doesn\'t exists' => '連結的任務不存在', + 'This value must be alphanumeric' => '此值必須是字母或者數字', + 'Edit recurrence' => '編輯循環週期', + 'Generate recurrent task' => '生成循環任務', + 'Trigger to generate recurrent task' => '生成循環任務的觸發器', + 'Factor to calculate new due date' => '逾期因素', + 'Timeframe to calculate new due date' => '計算逾期時間表', + 'Base date to calculate new due date' => '計算逾期基準日期', + 'Action date' => '操作日期', + 'Base date to calculate new due date: ' => '基準日期', + 'This task has created this child task: ' => '此任務建立了子任務:', + 'Day(s)' => '天', + 'Existing due date' => '已逾期', + 'Factor to calculate new due date: ' => '超期因素', + 'Month(s)' => '月', + 'This task has been created by: ' => '此任務建立者:', + 'Recurrent task has been generated:' => '循環任務已建立:', + 'Timeframe to calculate new due date: ' => '計算逾期時間表', + 'Trigger to generate recurrent task: ' => '建立循環任務的觸發器', + 'When task is closed' => '當任務關閉時', + 'When task is moved from first column' => '當任務從第一列任務欄移走時', + 'When task is moved to last column' => '當任務移動到最後一列任務欄時', + 'Year(s)' => '年', + 'Project settings' => '專案設定', + 'Automatically update the start date' => '自動更新開始日期', + 'iCal feed' => '日曆訂閱', + 'Preferences' => '偏好', + 'Security' => '安全', + 'Two factor authentication disabled' => '雙重認證已停用', + 'Two factor authentication enabled' => '雙重認證已啟用', + 'Unable to update this user.' => '無法更新此使用者', + 'There is no user management for personal projects.' => '私人專案下無使用者可管理', + 'User that will receive the email' => '使用者將收到郵件', + 'Email subject' => '郵件主旨', + 'Date' => '日期', + 'Add a comment log when moving the task between columns' => '當任務移動到任務欄時增加評論日誌', + 'Move the task to another column when the category is changed' => '當任務分類改變時移動到任務欄', + 'Send a task by email to someone' => '發送任務郵件給使用者', + 'Reopen a task' => '重新開始任務', + 'Notification' => '通知', + '%s moved the task #%d to the first swimlane' => '%s將任務#%d移動到首個里程碑', + 'Swimlane' => '里程碑', + '%s moved the task %s to the first swimlane' => '%s將任務%s移動到首個里程碑', + '%s moved the task %s to the swimlane "%s"' => '%s將任務%s移動到里程碑"%s"下', + 'This report contains all subtasks information for the given date range.' => '該報告包含了指定日期範圍内的所有子任務訊息。', + 'This report contains all tasks information for the given date range.' => '該報告包含了指定日期範圍内的所有任務訊息。', + 'Project activities for %s' => '%s的項目活動紀錄', + 'view the board on Kanboard' => '在看板上查看面板', + 'The task has been moved to the first swimlane' => '該任務已被移動到首個里程碑', + 'The task has been moved to another swimlane:' => '乾任務已被移動到别的里程碑:', + 'New title: %s' => '新標題:%s', + 'The task is not assigned anymore' => '該任務没有指派人', + 'New assignee: %s' => '新指派人:%s', + 'There is no category now' => '目前没有分類', + 'New category: %s' => '新分類:%s', + 'New color: %s' => '新顏色:%s', + 'New complexity: %d' => '新的複雜度:%d', + 'The due date has been removed' => '到期時間已被移除', + 'There is no description anymore' => '目前没有描述', + 'Recurrence settings has been modified' => '循環週期已被更改', + 'Time spent changed: %sh' => '時間花費已變更:%sh', + 'Time estimated changed: %sh' => '時間預估已變更:%sh', + 'The field "%s" has been updated' => '"%s"欄位已更新', + 'The description has been modified:' => '描述已更改', + 'Do you really want to close the task "%s" as well as all subtasks?' => '你是否要關閉此父任務包括所有子任務"%s"', + 'I want to receive notifications for:' => '我想接收以下相關通知:', + 'All tasks' => '所有任務', + 'Only for tasks assigned to me' => '所有指派给我的任務', + 'Only for tasks created by me' => '所有我建立的任務', + 'Only for tasks created by me and tasks assigned to me' => '所有我建立的並且指派给我的任務', + '%%Y-%%m-%%d' => '%%Y-%%m-%%d', + 'Total for all columns' => '所有欄位下的', + 'You need at least 2 days of data to show the chart.' => '柱狀圖至少需要2天的資料。', + '<15m' => '小於15分鐘', + '<30m' => '小於30分鐘', + 'Stop timer' => '停止計時器', + 'Start timer' => '啟動計時器', + 'My activity stream' => '我的動態', + 'Search tasks' => '搜尋任務', + 'Reset filters' => '重置過濾器', + 'My tasks due tomorrow' => '我明天到期的任務', + 'Tasks due today' => '今天到期的任務', + 'Tasks due tomorrow' => '明天到期的任務', + 'Tasks due yesterday' => '昨天到期的任務', + 'Closed tasks' => '已關閉的任務', + 'Open tasks' => '打開的任務', + 'Not assigned' => '未指派', + 'View advanced search syntax' => '查看高级搜尋語法', + 'Overview' => '總覽', + 'Board/Calendar/List view' => '看板/日程/列表視圖', + 'Switch to the board view' => '切换到看板視圖', + 'Switch to the list view' => '切换到列表視圖', + 'Go to the search/filter box' => '前往搜尋/過濾箱', + 'There is no activity yet.' => '目前無任何活動.', + 'No tasks found.' => '找不到任何任務.', + 'Keyboard shortcut: "%s"' => '快速鍵: "%s"', + 'List' => '列表', + 'Filter' => '過濾器', + 'Advanced search' => '高级搜尋', + 'Example of query: ' => '查詢範例: ', + 'Search by project: ' => '依專案: ', + 'Search by column: ' => '依任務欄: ', + 'Search by assignee: ' => '依被指派人: ', + 'Search by color: ' => '依颜色: ', + 'Search by category: ' => '依分類:', + 'Search by description: ' => '依描述:', + 'Search by due date: ' => '依逾期時間:', + 'Average time spent in each column' => '每個任務欄的平均花費時間', + 'Average time spent' => '平均花費時間', + 'This chart shows the average time spent in each column for the last %d tasks.' => '目前柱狀圖表示最新%d條任務在每個任務欄下的平均花費時間', + 'Average Lead and Cycle time' => '平均工作時間和平均週期時間', + 'Average lead time: ' => '平均工作時間', + 'Average cycle time: ' => '平均周期時間: ', + 'Cycle Time' => '週期時間', + 'Lead Time' => '工作時間', + 'This chart shows the average lead and cycle time for the last %d tasks over the time.' => '目前柱狀圖表示最新%d條任務的平均工作時間和週期時間', + 'Average time into each column' => '每個任務欄的平均時間', + 'Lead and cycle time' => '工作時間和週期時間', + 'Lead time: ' => '工作時間: ', + 'Cycle time: ' => '週期時間: ', + 'Time spent in each column' => '每個任務欄的花費時間', + 'The lead time is the duration between the task creation and the completion.' => '工作是任務建立和完成之間的持續時間。', + 'The cycle time is the duration between the start date and the completion.' => '周期時間是開始日期和完成時間之間的持續時間。', + 'If the task is not closed the current time is used instead of the completion date.' => '如果目前任務未關閉時用目前時間代替完成時間', + 'Set the start date automatically' => '設定自動開始日期', + 'Edit Authentication' => '編輯認證訊息', + 'Remote user' => '遠端使用者', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => '遠端使用者不會在看板資料庫保存密碼,例如:LDAP,GOOGLE,GitHub。', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => '如果選擇“禁止登入來自”,登入表單内的驗證訊息將被忽略。', + 'Default task color' => '預設任務顏色', + 'This feature does not work with all browsers.' => '本功能只在部分瀏覽器運作。', + 'There is no destination project available.' => '目前没有目標專案可用', + 'Trigger automatically subtask time tracking' => '自動追蹤子任務時間', + 'Include closed tasks in the cumulative flow diagram' => '在累計流程圖中包含已關閉任務', + 'Current swimlane: %s' => '目前里程碑:%s', + 'Current column: %s' => '目前任務欄:%s', + 'Current category: %s' => '目前分類:%s', + 'no category' => '無分類', + 'Current assignee: %s' => '目前被指派人: %s', + 'not assigned' => '未指派', + 'Author:' => '作者', + 'contributors' => '貢獻者', + 'License:' => '授權許可:', + 'License' => '授權許可', + 'Enter the text below' => '輸入下方的文字', + 'Start date:' => '開始日期', + 'Due date:' => '到期日期', + 'People who are project managers' => '專案管理員', + 'People who are project members' => '專案成員', + 'NOK - Norwegian Krone' => '克朗', + 'Show this column' => '顯示任務欄', + 'Hide this column' => '隱藏任務欄', + 'End date' => '结束日期', + 'Users overview' => '使用者總覽', + 'Members' => '成員', + 'Shared project' => '公開專案', + 'Project managers' => '專案管理員', + 'Projects list' => '專案列表', + 'End date:' => '结束日期', + 'Change task color when using a specific task link' => '當任務關聯到指定任務時改變顏色', + 'Task link creation or modification' => '任務連接建立或更新時間', + 'Milestone' => '里程碑', + 'Reset the search/filter box' => '重設搜尋/過濾框', + 'Documentation' => '文件', + 'Author' => '作者', + 'Version' => '版本', + 'Plugins' => '插件', + 'There is no plugin loaded.' => '目前没有插件載入', + 'My notifications' => '我的通知', + 'Custom filters' => '自定義過濾器', + 'Your custom filter has been created successfully.' => '建立過濾器成功', + 'Unable to create your custom filter.' => '無法建立過濾器', + 'Custom filter removed successfully.' => '删除過濾器成功', + 'Unable to remove this custom filter.' => '無法删除這個過濾器', + 'Edit custom filter' => '編輯過濾器', + 'Your custom filter has been updated successfully.' => '你的過濾器更新成功', + 'Unable to update custom filter.' => '無法更新過濾器', + 'Web' => 'web', + 'New attachment on task #%d: %s' => '任務#%d下的新附件:%s', + 'New comment on task #%d' => '任務#%d下的新評論', + 'Comment updated on task #%d' => '任務#%d的評論已更新', + 'New subtask on task #%d' => '任務#%d下新的子任務', + 'Subtask updated on task #%d' => '任務#%d下的子任務已更新', + 'New task #%d: %s' => '新任務#%d:%s', + 'Task updated #%d' => '任務#%d已更新', + 'Task #%d closed' => '任務#%d已關閉', + 'Task #%d opened' => '任務#%d已打開', + 'Column changed for task #%d' => '任務#%d的任務欄已改變', + 'New position for task #%d' => '任務#%d的新狀態', + 'Swimlane changed for task #%d' => '任務#%d的里程碑已改變', + 'Assignee changed on task #%d' => '任務#%d的指派人已改變', + '%d overdue tasks' => '%d條逾期任務', + 'No notification.' => '没有新通知', + 'Mark all as read' => '標記所有為已讀', + 'Mark as read' => '標記為已讀', + 'Total number of tasks in this column across all swimlanes' => '此任務欄下的任務數(跨里程碑)', + 'Collapse swimlane' => '收起里程碑', + 'Expand swimlane' => '展開里程碑', + 'Add a new filter' => '增加新過濾器', + 'Share with all project members' => '對專案所有成員共享', + 'Shared' => '共享', + 'Owner' => '所有人', + 'Unread notifications' => '未讀通知', + 'Notification methods:' => '通知提醒方式:', + 'Unable to read your file' => '無法讀取文件', + '%d task(s) have been imported successfully.' => '成功匯入%d條任務。', + 'Nothing has been imported!' => '没有資料被匯入!', + 'Import users from CSV file' => '從CSV文件匯入帳號', + '%d user(s) have been imported successfully.' => '成功匯入%d個帳號。', + 'Comma' => '逗號', + 'Semi-colon' => '分號', + 'Tab' => '製表符', + 'Vertical bar' => '豎線', + 'Double Quote' => '雙引號', + 'Single Quote' => '單引號', + '%s attached a file to the task #%d' => '%s增加文件到任務#%d', + 'There is no column or swimlane activated in your project!' => '目前專案没有活動任務欄或里程碑', + 'Append filter (instead of replacement)' => '追加過濾', + 'Append/Replace' => '追加/替换', + 'Append' => '追加', + 'Replace' => '替换', + 'Import' => '匯入', + 'Change sorting' => '改變排序', + 'Tasks Importation' => '任務重要性', + 'Delimiter' => '分隔符', + 'Enclosure' => '附件', + 'CSV File' => 'CSV文件', + 'Instructions' => '操作指南', + 'Your file must use the predefined CSV format' => '文件必須為CSV格式', + 'Your file must be encoded in UTF-8' => '文件編碼必須為UTF-8', + 'The first row must be the header' => '第一行必須為表頭', + 'Duplicates are not verified for you' => '無法驗證重複訊息', + 'The due date must use the ISO format: YYYY-MM-DD' => '到期日期必須為ISO格式:YYYY-MM-DD', + 'Download CSV template' => '下載CSV範本', + 'No external integration registered.' => '没有外部註冊訊息', + 'Duplicates are not imported' => '重複資料未匯入', + 'Usernames must be lowercase and unique' => '帳號名稱必需小寫且不重複', + 'Passwords will be encrypted if present' => '密碼將被加密', + '%s attached a new file to the task %s' => '"%s"增加了附件到任務"%s"', + 'Link type' => '連結類型', + 'Assign automatically a category based on a link' => '根據連結自動分類', + 'BAM - Konvertible Mark' => '波斯尼亞馬克', + 'Assignee Username' => '指派使用者', + 'Assignee Name' => '指派名稱', + 'Groups' => '使用者群組', + 'Members of %s' => '“%s”组成员', + 'New group' => '新增使用者群組', + 'Group created successfully.' => '使用者群组建立成功', + 'Unable to create your group.' => '無法建立使用者群組', + 'Edit group' => '編輯使用者群組', + 'Group updated successfully.' => '使用者群組更新成功', + 'Unable to update your group.' => '無法更新你的使用者群組', + 'Add group member to "%s"' => '增加到群組"%s"', + 'Group member added successfully.' => '增加群組成員成功', + 'Unable to add group member.' => '無法增加群組成员', + 'Remove user from group "%s"' => '從"%s"群組中移除成員', + 'User removed successfully from this group.' => '使用者已從該群組中删除', + 'Unable to remove this user from the group.' => '無法從群組中中删除使用者', + 'Remove group' => '删除群組', + 'Group removed successfully.' => '群組已删除', + 'Unable to remove this group.' => '無法删除群組', + 'Project Permissions' => '專案權限', + 'Manager' => '管理員', + 'Project Manager' => '專案管理員', + 'Project Member' => '專案成員', + 'Project Viewer' => '專案觀察員', + 'Your account is locked for %d minutes' => '你的帳號被鎖定%d分鐘', + 'Invalid captcha' => '驗證碼無效', + 'The name must be unique' => '帳號名稱不能重複', + 'View all groups' => '查看所有群組', + 'There is no user available.' => '目前無有效使用者', + 'Do you really want to remove the user "%s" from the group "%s"?' => '你確定把使用者"%s"從"%s"中移除?', + 'There is no group.' => '目前没有群組', + 'Add group member' => '增加群組成員', + 'Do you really want to remove this group: "%s"?' => '確認要移除群組:"%s"?', + 'There is no user in this group.' => '目前群組下没有成員', + 'Permissions' => '權限', + 'Allowed Users' => '被允許的使用者', + 'No specific user has been allowed.' => '没有被允許的使用者。', + 'Role' => '角色', + 'Enter user name...' => '輸入使用者...', + 'Allowed Groups' => '被允許的群組', + 'No group has been allowed.' => '没有被允許的群組。', + 'Group' => '群組', + 'Group Name' => '群組名稱', + 'Enter group name...' => '輸入群組名稱...', + 'Role:' => '角色:', + 'Project members' => '專案成員', + '%s mentioned you in the task #%d' => '%s在任務#%d裡提到您', + '%s mentioned you in a comment on the task #%d' => '%s在任務#%d的評論中提到您', + 'You were mentioned in the task #%d' => '您在任務#%d中被提到', + 'You were mentioned in a comment on the task #%d' => '您在任務#%d評論中被提到', + 'Estimated hours: ' => '預估小時數', + 'Actual hours: ' => '實際小時數', + 'Hours Spent' => '花費小時數', + 'Hours Estimated' => '預估小時數', + 'Estimated Time' => '預估時間', + 'Actual Time' => '實際時間', + 'Estimated vs actual time' => '預估時間 VS 實際時間', + 'RUB - Russian Ruble' => '盧布', + 'Assign the task to the person who does the action when the column is changed' => '當任務所属任務欄改變時分配到指定使用者', + 'Close a task in a specific column' => '關閉指定任務欄下的任務', + 'Time-based One-time Password Algorithm' => '基於時間的一次性密碼算法', + 'Two-Factor Provider: ' => '雙重認證提供者', + 'Disable two-factor authentication' => '停用雙重認證', + 'Enable two-factor authentication' => '啟用雙重認證', + 'There is no integration registered at the moment.' => '目前没有註冊關聯', + 'Password Reset for Kanboard' => '重設kanboard密碼', + 'Forgot password?' => '忘記密碼?', + 'Enable "Forget Password"' => '啟用找回密碼', + 'Password Reset' => '密碼重置', + 'New password' => '新密碼', + 'Change Password' => '更改密碼', + 'To reset your password click on this link:' => '點擊此連結重置您的密碼', + 'Last Password Reset' => '上次密碼重置', + 'The password has never been reinitialized.' => '密碼從未被重新初始化', + 'Creation' => '建立時間', + 'Expiration' => '過期時間', + 'Password reset history' => '密碼重置紀錄', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => '任務欄 "%s" 和 里程碑 "%s" 下的所有任務已關閉', + 'Do you really want to close all tasks of this column?' => '你確定要關閉此任務欄下的所有任務?', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '任務欄 "%s" 和 里程碑 "%s" 下 %d 任務將被關閉。', + 'Close all tasks in this column and this swimlane' => '關閉此任務欄下的所有任務', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => '没有插件註冊到專案通知API,你仍然可以在你個人設定裡啟用單獨的通知', + 'My dashboard' => '我的儀表板', + 'My profile' => '我的個人訊息', + 'Project owner: ' => '專案負責人', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => '專案標示符是可選的,且只能是數字或字母组成,例如:MYPROJECT。', + 'Project owner' => '專案負責人', + 'Personal projects do not have users and groups management.' => '私人專案下沒有成員或群組可管理', + 'There is no project member.' => '目前没有專案成員', + 'Priority' => '優先權', + 'Task priority' => '任務優先權', + 'General' => '常用', + 'Dates' => '時間', + 'Default priority' => '預設優先權', + 'Lowest priority' => '最低優先權', + 'Highest priority' => '最高優先權', + 'Close a task when there is no activity' => '當没有活動紀錄時關閉任務', + 'Duration in days' => '持續天數', + 'Send email when there is no activity on a task' => '當任務没有活動紀錄時發送郵件', + 'Unable to fetch link information.' => '無法獲取連結資訊', + 'Daily background job for tasks' => '每日後台任務', + 'Auto' => '自動', + 'Related' => '相關的', + 'Attachment' => '附件', + 'Web Link' => '網頁連結', + 'External links' => '外部連結', + 'Add external link' => '增加外部連結', + 'Type' => '類型', + 'Dependency' => '依赖', + 'Add internal link' => '增加内部連結', + 'Add a new external link' => '增加新的外部連結', + 'Edit external link' => '編輯外部連結', + 'External link' => '外部連結', + 'Copy and paste your link here...' => '複製並貼上連結到目前位置...', + 'URL' => 'URL', + 'Internal links' => '内部連結', + 'Assign to me' => '指派给我', + 'Me' => '我', + 'Do not duplicate anything' => '不再重複', + 'Projects management' => '專案管理', + 'Users management' => '用户管理', + 'Groups management' => '群組管理', + 'Create from another project' => '從另一個專案建立', + 'open' => '打開', + 'closed' => '已關閉', + 'Priority:' => '優先權:', + 'Reference:' => '引用:', + 'Complexity:' => '複雜度:', + 'Swimlane:' => '里程碑:', + 'Column:' => '欄位:', + 'Position:' => '位置:', + 'Creator:' => '建立者:', + 'Time estimated:' => '時間已過去:', + '%s hours' => '%s 小時', + 'Time spent:' => '時間消耗:', + 'Created:' => '已建立:', + 'Modified:' => '已修改:', + 'Completed:' => '已完成:', + 'Started:' => '已開始:', + 'Moved:' => '已移走:', + 'Task #%d' => '任務#%d', + 'Time format' => '時間格式', + 'Start date: ' => '開始時間:', + 'End date: ' => '结束時間:', + 'New due date: ' => '新到期時間:', + 'Start date changed: ' => '開始時間已改變:', + 'Disable personal projects' => '停用私人專案', + 'Do you really want to remove this custom filter: "%s"?' => '你確定要移除這個自定義過濾器:"%s"?', + 'Remove a custom filter' => '移除自定義過濾器', + 'User activated successfully.' => '帳號已啟用。', + 'Unable to enable this user.' => '無法啟用帳號。', + 'User disabled successfully.' => '帳號已禁用。', + 'Unable to disable this user.' => '無法禁用帳號。', + 'All files have been uploaded successfully.' => '所有文件已成功上傳。', + 'The maximum allowed file size is %sB.' => '最大上傳尺寸 %sB', + 'Drag and drop your files here' => '拖放文件到這裡', + 'choose files' => '選擇文件', + 'View profile' => '查看個人訊息', + 'Two Factor' => '雙重認證', + 'Disable user' => '禁用帳號', + 'Do you really want to disable this user: "%s"?' => '你確定要禁用帳號:"%s"?', + 'Enable user' => '啟用帳號', + 'Do you really want to enable this user: "%s"?' => '你確定要啟用帳號:"%s"?', + 'Download' => '下載', + 'Uploaded: %s' => '上傳:%s', + 'Size: %s' => '大小:%s', + 'Uploaded by %s' => '由%s上傳', + 'Filename' => '檔案名稱', + 'Size' => '大小', + 'Column created successfully.' => '新增任務欄成功。', + 'Another column with the same name exists in the project' => '目前專案中相同名稱欄位已存在', + 'Default filters' => '預設過濾器', + 'Your board doesn\'t have any columns!' => '你的看板没有任何欄位', + 'Change column position' => '更改任務欄位置', + 'Switch to the project overview' => '切换到项目視圖', + 'User filters' => '使用者過濾器', + 'Category filters' => '分類過濾器', + 'Upload a file' => '上傳文件', + 'View file' => '查看文件', + 'Last activity' => '最後活動', + 'Change subtask position' => '更改子任務位置', + 'This value must be greater than %d' => '目前輸入值必須大於%d', + 'Another swimlane with the same name exists in the project' => '目前專案中相同名稱里程碑已存在', + 'Example: https://example.kanboard.org/ (used to generate absolute URLs)' => '例如: https://example.kanboard.org/(通常用於產生永久網址)', + 'Actions duplicated successfully.' => '動作複製成功。', + 'Unable to duplicate actions.' => '無法複製動作。', + 'Add a new action' => '增加新動作', + 'Import from another project' => '從另一個專案中匯入', + 'There is no action at the moment.' => '目前没有動作。', + 'Import actions from another project' => '從另一個專案中匯入動作', + 'There is no available project.' => '目前没有可用專案', + 'Local File' => '本機文件', + 'Configuration' => '配置選項', + 'PHP version:' => 'PHP 版本:', + 'PHP SAPI:' => 'PHP SAPI:', + 'OS version:' => '作業系统:', + 'Database version:' => '資料庫版本:', + 'Browser:' => '瀏覽器:', + 'Task view' => '任務瀏覽', + 'Edit task' => '編輯任務', + 'Edit description' => '編輯描述', + 'New internal link' => '新增内部連結', + 'Display list of keyboard shortcuts' => '顯示快速鍵列表', + 'Avatar' => '頭像', + 'Upload my avatar image' => '上傳我的頭像', + 'Remove my image' => '删除我的頭像', + 'The OAuth2 state parameter is invalid' => 'OAuth2狀態參數無效', + 'User not found.' => '找不到帳號', + 'Search in activity stream' => '在活動紀錄裡搜尋', + 'My activities' => '我的活動紀錄', + 'Activity until yesterday' => '今天以前的活動紀錄', + 'Activity until today' => '今天為止的活動紀錄', + 'Search by creator: ' => '以建立者搜尋', + 'Search by creation date: ' => '以建立日期搜尋', + 'Search by task status: ' => '以任務狀態搜尋', + 'Search by task title: ' => '以任務標題搜尋', + 'Activity stream search' => '活動紀錄搜尋', + 'Projects where "%s" is manager' => '"%s" 管理的專案', + 'Projects where "%s" is member' => '"%s" 參與的專案', + 'Open tasks assigned to "%s"' => '指派给"%s"的未結束任務', + 'Closed tasks assigned to "%s"' => '指派给"%s"的已结束任務', + 'Assign automatically a color based on a priority' => '基於優先權自動標記顏色', + 'Overdue tasks for the project(s) "%s"' => '"%s"專案下的逾期任務', + 'Upload files' => '上傳文件', + 'Installed Plugins' => '已安装插件', + 'Plugin Directory' => '插件目錄', + 'Plugin installed successfully.' => '插件安装成功。', + 'Plugin updated successfully.' => '插件更新成功。', + 'Plugin removed successfully.' => '插件卸載成功。', + 'Subtask converted to task successfully.' => '子任務成功轉換成普通任務。', + 'Unable to convert the subtask.' => '無法轉換子任務。', + 'Unable to extract plugin archive.' => '無法解壓縮插件包。', + 'Plugin not found.' => '插件未找到。', + 'You don\'t have the permission to remove this plugin.' => '你没有權限移除此插件。', + 'Unable to download plugin archive.' => '無法下載插件包。', + 'Unable to write temporary file for plugin.' => '無法為插件寫入臨時文件。', + 'Unable to open plugin archive.' => '無法打開插件包。', + 'There is no file in the plugin archive.' => '目前插件包内無文件。', + 'Create tasks in bulk' => '批次建立任務', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => '你的Kanboard没有啟用插件安装。', + 'There is no plugin available.' => '目前無可用插件。', + 'Install' => '安装', + 'Update' => '更新', + 'Up to date' => '已更新', + 'Not available' => '不可用', + 'Remove plugin' => '移除插件', + 'Do you really want to remove this plugin: "%s"?' => '你真的要移除插件:"%s"?', + 'Uninstall' => '移除', + 'Listing' => '列表', + 'Metadata' => 'Metadata', + 'Manage projects' => '管理專案', + 'Convert to task' => '轉為普通任務', + 'Convert sub-task to task' => '轉換子任務為普通任務', + 'Do you really want to convert this sub-task to a task?' => '你真的要將此子任務轉換為普通任務?', + 'My task title' => '我的任務標題', + 'Enter one task by line.' => '寫入一行任務。', + 'Number of failed login:' => '登入失敗次數:', + 'Account locked until:' => '帳號被鎖定至:', + 'Email settings' => '郵件設定', + 'Email sender address' => '郵件發送地址', + 'Email transport' => '郵件轉發', + 'Webhook token' => 'Webhook Token', + 'Project tags management' => '專案標籤管理', + 'Tag created successfully.' => '成功建立標籤。', + 'Unable to create this tag.' => '無法建立此標籤。', + 'Tag updated successfully.' => '標籤更新成功。', + 'Unable to update this tag.' => '無法更新此標籤。', + 'Tag removed successfully.' => '標籤删除成功。', + 'Unable to remove this tag.' => '無法删除此標籤', + 'Global tags management' => '全域標籤管理', + 'Tags' => '標籤', + 'Tags management' => '標籤管理', + 'Add new tag' => '增加新標籤', + 'Edit a tag' => '編輯標籤', + 'Project tags' => '專案標籤', + 'There is no specific tag for this project at the moment.' => '目前專案没有指定任何標籤。', + 'Tag' => '標籤', + 'Remove a tag' => '移除標籤', + 'Do you really want to remove this tag: "%s"?' => '你真的要删除此標籤:"%s"?', + 'Global tags' => '全域標籤', + 'There is no global tag at the moment.' => '目前没有全域標籤。', + 'This field cannot be empty' => '此欄不能為空', + 'Close a task when there is no activity in a specific column' => '當指定欄位没有更新時關閉任務', + '%s removed a subtask for the task #%d' => '"%s"從任務#%d删除了子任務', + '%s removed a comment on the task #%d' => '"%s"從任務#%d删除了評論', + 'Comment removed on task #%d' => '任務#%d上的評論已删除', + 'Subtask removed on task #%d' => '任務#%d上的子任務已删除', + 'Hide tasks in this column in the dashboard' => '在儀表板隱藏此欄下的任務', + '%s removed a comment on the task %s' => '"%s"從任務%s 删除了評論', + '%s removed a subtask for the task %s' => '"%s"從任務%s 删除了子任務', + 'Comment removed' => '評論已删除', + 'Subtask removed' => '子任務已删除', + '%s set a new internal link for the task #%d' => '%s為任務#%d 設置了新内部連結', + '%s removed an internal link for the task #%d' => '%s 從任務 #%d 删除内部連結', + 'A new internal link for the task #%d has been defined' => '#%d新内部連結已定義', + 'Internal link removed for the task #%d' => '#%d内部連結已删除', + '%s set a new internal link for the task %s' => '%s為任務%s設置了新連結', + '%s removed an internal link for the task %s' => '%s删除了任務%s内部連結', + 'Automatically set the due date on task creation' => '建立任務時自動設定過期時間', + 'Move the task to another column when closed' => '當任務關閉時移動到另一欄', + 'Move the task to another column when not moved during a given period' => '當任務在指定時間内未移動時,移動到另一欄', + 'Dashboard for %s' => '%s的儀表板', + 'Tasks overview for %s' => '%s的任務預覽', + 'Subtasks overview for %s' => '%s的子任務預覽', + 'Projects overview for %s' => '%s的專案預覽', + 'Activity stream for %s' => '%s的活動紀錄', + 'Assign a color when the task is moved to a specific swimlane' => '當任務移動到指定里程碑時標記顏色', + 'Assign a priority when the task is moved to a specific swimlane' => '當任務移動到指定里程碑時標記優先權', + 'User unlocked successfully.' => '帳號解鎖成功。', + 'Unable to unlock the user.' => '無法解鎖帳號。', + 'Move a task to another swimlane' => '移動任務到里程碑', + 'Creator Name' => '建立者名稱', + 'Time spent and estimated' => '時間花費預估', + 'Move position' => '移動位置', + 'Move task to another position on the board' => '移動任務到另一個位置', + 'Insert before this task' => '在此任務之前插入', + 'Insert after this task' => '在此任務之後插入', + 'Unlock this user' => '解鎖帳號', + 'Custom Project Roles' => '自定義專案角色', + 'Add a new custom role' => '增加新角色', + 'Restrictions for the role "%s"' => '"%s"角色限制', + 'Add a new project restriction' => '增加新的專案限制', + 'Add a new drag and drop restriction' => '增加新的拖放限制', + 'Add a new column restriction' => '增加新的欄位限制', + 'Edit this role' => '編輯角色', + 'Remove this role' => '删除角色', + 'There is no restriction for this role.' => '目前角色無限制。', + 'Only moving task between those columns is permitted' => '只能在以下欄位間移動任務', + 'Close a task in a specific column when not moved during a given period' => '當指定欄位下的任務在指定時間内未移動時關閉任務', + 'Edit columns' => '編輯欄位', + 'The column restriction has been created successfully.' => '建立欄位限制成功。', + 'Unable to create this column restriction.' => '無法建立欄位限制。', + 'Column restriction removed successfully.' => '删除欄位限制成功。', + 'Unable to remove this restriction.' => '無法删除限制。', + 'Your custom project role has been created successfully.' => '自定義項目角色建立成功。', + 'Unable to create custom project role.' => '無法删除自定義專案角色。', + 'Your custom project role has been updated successfully.' => '自定義项目角色更新成功。', + 'Unable to update custom project role.' => '無法更新自定義專案角色。', + 'Custom project role removed successfully.' => '删除自定義專案角色成功。', + 'Unable to remove this project role.' => '無法删除專案角色。', + 'The project restriction has been created successfully.' => '建立專案限制成功。', + 'Unable to create this project restriction.' => '無法建立專案限制。', + 'Project restriction removed successfully.' => '删除專案限制成功。', + 'You cannot create tasks in this column.' => '你不能在此欄位下建立任務。', + 'Task creation is permitted for this column' => '目前欄位下允許建立任務。', + 'Closing or opening a task is permitted for this column' => '目前欄位下允許開關任務。', + 'Task creation is blocked for this column' => '目前欄位下禁止建立任務。', + 'Closing or opening a task is blocked for this column' => '禁止在此欄位下開關任務', + 'Task creation is not permitted' => '不能建立任務', + 'Closing or opening a task is not permitted' => '禁止開關任務', + 'New drag and drop restriction for the role "%s"' => '為角色"%s"新建拖動限制', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => '此角色下的用户只能在原欄位和目標欄位間移動任務。', + 'Remove a column restriction' => '移除欄位限制', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => '你真的要删除欄位限制:"%s"到"%s"?', + 'New column restriction for the role "%s"' => '為角色"%s"增加欄位限制?', + 'Rule' => '規則', + 'Do you really want to remove this column restriction?' => '你真的要移除欄位限制?', + 'Custom roles' => '自定義角色', + 'New custom project role' => '新建專案角色', + 'Edit custom project role' => '編輯專案角色', + 'Remove a custom role' => '删除自定義角色', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => '你真的要删除這個自定義角色:"%s"?目前角色下的成員將轉成普通專案成員。', + 'There is no custom role for this project.' => '目前專案没有自定義角色', + 'New project restriction for the role "%s"' => '给角色"%s"新建專案限制', + 'Restriction' => '限制', + 'Remove a project restriction' => '移除專案限制', + 'Do you really want to remove this project restriction: "%s"?' => '你真的要删除專案限制:"%s"?', + 'Duplicate to multiple projects' => '複製到多個專案', + 'This field is required' => '此欄位必填', + 'Moving a task is not permitted' => '禁止移動任務', + 'This value must be in the range %d to %d' => '輸入值必須在%d到%d之間', + 'You are not allowed to move this task.' => '你不能移動此任務', + 'API User Access' => 'API access', + 'Preview' => '預覽', + 'Write' => '寫入', + 'Write your text in Markdown' => '使用markdown格式', + 'No personal API access token registered.' => '没有API access token 註冊。', + 'Your personal API access token is "%s"' => '你的API access token 是 “%s”', + 'Remove your token' => '移除token', + 'Generate a new token' => '產生新的token', + 'Showing %d-%d of %d' => '本頁顯示 %d-%d 筆,共有: %d 筆', + 'Outgoing Emails' => '目標郵件', + 'Add or change currency rate' => '增加/更改匯率', + 'Reference currency: %s' => '参考匯率:%s', + 'Add custom filters' => '添加自定義過濾', + 'Export' => '匯出', + 'Add link label' => '增加連結標籤', + 'Incompatible Plugins' => '不相容插件', + 'Compatibility' => '相容性', + 'Permissions and ownership' => '權限和擁有者', + 'Priorities' => '屬性', + 'Close this window' => '關閉視窗', + 'Unable to upload this file.' => '無法上傳此文件。', + 'Import tasks' => '匯入任務', + 'Choose a project' => '選擇專案', + 'Profile' => '個人資料', + 'Application role' => '應用角色', + '%d invitations were sent.' => '%d個邀請已發送。', + '%d invitation was sent.' => '%d個邀請已發送。', + 'Unable to create this user.' => '無法建立用戶。', + 'Kanboard Invitation' => '看板邀請', + 'Visible on dashboard' => '儀表板可見', + 'Created at:' => '建立於:', + 'Updated at:' => '更新於:', + 'There is no custom filter.' => '目前没有自定義過濾。', + 'New User' => '新增使用者', + 'Authentication' => '認證', + 'If checked, this user will use a third-party system for authentication.' => '選取時使用者將使用第三方系统認證。', + 'The password is necessary only for local users.' => '只有本機使用者才需要設定密碼。', + 'You have been invited to register on Kanboard.' => '你被邀請註冊看板。', + 'Click here to join your team' => '點擊這裡加入你的團隊', + 'Invite people' => '邀請新使用者', + 'Emails' => '郵件', + 'Enter one email address by line.' => '輸入郵件位址,每行一個。', + 'Add these people to this project' => '增加使用者到專案', + 'Add this person to this project' => '增加使用者到專案', + 'Sign-up' => '註冊', + 'Credentials' => '認證訊息', + 'New user' => '新使用者', + 'This username is already taken' => '帳號名稱已被使用', + 'Your profile must have a valid email address.' => '個人資料必須有一個有效email位址。', + 'TRL - Turkish Lira' => '土耳其里拉', + 'The project email is optional and could be used by several plugins.' => '專案郵件位址可選,使用於插件。', + 'The project email must be unique across all projects' => '專案郵件地址必須全域唯一', + 'The email configuration has been disabled by the administrator.' => '郵件設定被管理員禁用。', + 'Close this project' => '關閉專案', + 'Open this project' => '打開專案', + 'Close a project' => '關閉專案', + 'Do you really want to close this project: "%s"?' => '你真的要關閉專案:"%s"?', + 'Reopen a project' => '重開專案', + 'Do you really want to reopen this project: "%s"?' => '你真的要重開專案:"%s"?', + 'This project is open' => '專案已打開', + 'This project is closed' => '專案已關閉', + 'Unable to upload files, check the permissions of your data folder.' => '無法上傳文件,請檢查你的data目錄權限。', + 'Another category with the same name exists in this project' => '此项目中存在另一个同名类别', + 'Comment sent by email successfully.' => '評論已成功通過電子郵件發送。', + 'Sent by email to "%s" (%s)' => '通過電子郵件發送給 "%s" (%s)', + 'Unable to read uploaded file.' => '無法讀取上傳的檔案。', + 'Database uploaded successfully.' => '資料庫成功上傳。', + 'Task sent by email successfully.' => '任務已成功通過電子郵件發送。', + 'There is no category in this project.' => '目前專案没有分類。', + 'Send by email' => '發送郵件', + 'Create and send a comment by email' => '通過電子郵件建立和發送評論', + 'Subject' => '主旨', + 'Upload the database' => '上傳資料庫', + 'You could upload the previously downloaded Sqlite database (Gzip format).' => '您可以上傳之前下載的 Sqlite 資料庫 (Gzip 格式)。', + 'Database file' => '資料庫檔案', + 'Upload' => '上傳', + 'Your project must have at least one active swimlane.' => '您的專案必須至少有一個作用中的泳道。', + 'Project: %s' => '專案:%s', + 'Automatic action not found: "%s"' => '找不到自動動作: "%s"', + '%d projects' => '%d 專案', + '%d project' => '%d 專案', + 'There is no project.' => '目前没有專案。', + 'Sort' => '排序', + 'Project ID' => '專案編號', + 'Project name' => '專案名稱', + 'Public' => '公開', + 'Personal' => '私人', + '%d tasks' => '%d 任務', + '%d task' => '%d 任務', + 'Task ID' => '任務編號', + 'Assign automatically a color when due date is expired' => '當截止日期過期時自動分配顏色', + 'Total score in this column across all swimlanes' => '此列所有泳道的總分數', + 'HRK - Kuna' => 'HRK - 克羅埃西亞庫納', + 'ARS - Argentine Peso' => 'ARS - 阿根廷比索', + 'COP - Colombian Peso' => 'COP - 哥倫比亞比索', + '%d groups' => '%d 群組', + '%d group' => '%d 群組', + 'Group ID' => '群組編號', + 'External ID' => '外部 ID', + '%d users' => '%d 使用者', + '%d user' => '%d 使用者', + 'Hide subtasks' => '顯示子任務', + 'Show subtasks' => '隱藏子任務', + 'Authentication Parameters' => '認證參數', + 'API Access' => 'API 存取', + 'No users found.' => '找不到使用者。', + 'User ID' => '使用者編號', + 'Notifications are activated' => '通知已啟用', + 'Notifications are disabled' => '通知已停用', + 'User disabled' => '帳號已禁用', + '%d notifications' => '%d 通知', + '%d notification' => '%d 通知', + 'There is no external integration installed.' => '未安裝外部整合。', + 'You are not allowed to update tasks assigned to someone else.' => '您不允許更新分配給其他人的任務。', + 'You are not allowed to change the assignee.' => '您不允許更改被分配人。', + 'Task suppression is not permitted' => '不允許壓制任務', + 'Changing assignee is not permitted' => '不允許更改被分配人', + 'Update only assigned tasks is permitted' => '只允許更新分配的任務', + 'Only for tasks assigned to the current user' => '僅限於分配給當前使用者的任務', + 'My projects' => '我的專案', + 'You are not a member of any project.' => '你不是任何專案的成員。', + 'My subtasks' => '我的子任務', + '%d subtasks' => '%d 子任務', + '%d subtask' => '%d 子任務', + 'Only moving task between those columns is permitted for tasks assigned to the current user' => '僅允許分配給當前使用者的任務在這些列之間移動', + '[DUPLICATE]' => '[重複]', + 'DKK - Danish Krona' => 'DKK - 丹麥克朗', + 'Remove user from group' => '從群組中移除使用者', + 'Assign the task to its creator' => '將任務分配给建立者', + 'This task was sent by email to "%s" with subject "%s".' => '此任務已通過電子郵件發送給 "%s",主旨為 "%s"。', + 'Predefined Email Subjects' => '預定義的郵件主旨', + 'Write one subject by line.' => '每行寫一個主旨。', + 'Create another link' => '建立另一個連結', + 'BRL - Brazilian Real' => 'BRL - 巴西雷亞爾', + 'Add a new Kanboard task' => '新增 Kanboard 任務', + 'Subtask not started' => '子任務未開始', + 'Subtask currently in progress' => '子任務目前正在進行中', + 'Subtask completed' => '子任務已完成', + 'Subtask added successfully.' => '子任務已成功新增。', + '%d subtasks added successfully.' => '已成功新增 %d 個子任務。', + 'Enter one subtask by line.' => '每行輸入一個子任務。', + 'Predefined Contents' => '預定義內容', + 'Predefined contents' => '預定義內容', + 'Predefined Task Description' => '預定義任務描述', + 'Do you really want to remove this template? "%s"' => '您確定要移除此範本嗎? "%s"', + 'Add predefined task description' => '新增預定義任務描述', + 'Predefined Task Descriptions' => '預定義任務描述', + 'Template created successfully.' => '範本已成功建立。', + 'Unable to create this template.' => '無法建立此範本。', + 'Template updated successfully.' => '範本已成功更新。', + 'Unable to update this template.' => '無法更新此範本。', + 'Template removed successfully.' => '範本已成功移除。', + 'Unable to remove this template.' => '無法移除此範本。', + 'Template for the task description' => '任務描述範本', + 'The start date is greater than the end date' => '開始日期晚於結束日期', + 'Tags must be separated by a comma' => '標籤必須用逗號分隔', + 'Only the task title is required' => '僅任務標題為必填', + 'Creator Username' => '建立者使用者名稱', + 'Color Name' => '顏色名稱', + 'Column Name' => '欄位名稱', + 'Swimlane Name' => '泳道名稱', + 'Time Estimated' => '預計時間', + 'Time Spent' => '花費時間', + 'External Link' => '外部連結', + 'This feature enables the iCal feed, RSS feed and the public board view.' => '此功能啟用 iCal feed、RSS feed 和公開看板檢視。', + 'Stop the timer of all subtasks when moving a task to another column' => '將任務移動到另一列時停止所有子任務的計時器', + 'Subtask Title' => '子任務標題', + 'Add a subtask and activate the timer when moving a task to another column' => '新增子任務並在將任務移動到另一列時啟用計時器', + 'days' => '天', + 'minutes' => '分鐘', + 'seconds' => '秒', + 'Assign automatically a color when preset start date is reached' => '達到預設開始日期時自動分配顏色', + 'Move the task to another column once a predefined start date is reached' => '達到預定義開始日期後將任務移動到另一列', + 'This task is now linked to the task %s with the relation "%s"' => '此任務現已使用關係 "%s" 連結到任務 %s', + 'The link with the relation "%s" to the task %s has been removed' => '與任務 %s 的關係 "%s" 的連結已移除', + 'Custom Filter:' => '自訂篩選器:', + 'Unable to find this group.' => '無法找到此群組。', + '%s moved the task #%d to the column "%s"' => '%s 將任務 #%d 移動到欄 "%s"', + '%s moved the task #%d to the position %d in the column "%s"' => '%s 將任務 #%d 移動到欄 "%s" 的位置 %d', + '%s moved the task #%d to the swimlane "%s"' => '%s 將任務 #%d 移動到泳道 "%s"', + '%sh spent' => '已花費 %sh', + '%sh estimated' => '預計 %sh', + 'Select All' => '全選', + 'Unselect All' => '取消全選', + 'Apply action' => '應用動作', + 'Move selected tasks to another column or swimlane' => '將選定的任務移動到另一列或泳道', + 'Edit tasks in bulk' => '批量編輯任務', + 'Choose the properties that you would like to change for the selected tasks.' => '選擇要變更所選任務的屬性。', + 'Configure this project' => '設定此專案', + 'Start now' => '立即開始', + '%s removed a file from the task #%d' => '%s 從任務 #%d 移除了檔案', + 'Attachment removed from task #%d: %s' => '從任務 #%d 移除的附件:%s', + 'No color' => '無顏色', + 'Attachment removed "%s"' => '附件 "%s" 已移除', + '%s removed a file from the task %s' => '%s 從任務 %s 移除了檔案', + 'Move the task to another swimlane when assigned to a user' => '將任務分配給使用者時移動到另一個泳道', + 'Destination swimlane' => '目標泳道', + 'Assign a category when the task is moved to a specific swimlane' => '當任務移動到特定泳道時分配類別', + 'Move the task to another swimlane when the category is changed' => '當類別變更時移動任務到另一個泳道', + 'Reorder this column by priority (ASC)' => '按優先級重排此列 (升序)', + 'Reorder this column by priority (DESC)' => '按優先級重排此列 (降序)', + 'Reorder this column by assignee and priority (ASC)' => '按被分配者和優先級重排此列 (升序)', + 'Reorder this column by assignee and priority (DESC)' => '按被分配者和優先級重排此列 (降序)', + 'Reorder this column by assignee (A-Z)' => '按被分配者 (A-Z) 重排此列', + 'Reorder this column by assignee (Z-A)' => '按被分配者 (Z-A) 重排此列', + 'Reorder this column by due date (ASC)' => '按截止日期重排此列 (升序)', + 'Reorder this column by due date (DESC)' => '按截止日期重排此列 (降序)', + 'Reorder this column by id (ASC)' => '按 ID 重排此列 (升序)', + 'Reorder this column by id (DESC)' => '按 ID 重排此列 (降序)', + '%s moved the task #%d "%s" to the project "%s"' => '%s 將任務 #%d "%s" 移動到專案 "%s"', + 'Task #%d "%s" has been moved to the project "%s"' => '任務 #%d "%s" 已移至專案 "%s"', + 'Move the task to another column when the due date is less than a certain number of days' => '當截止日期少於特定天數時將任務移動到另一列', + 'Automatically update the start date when the task is moved away from a specific column' => '當任務從特定欄位移開時,自動更新開始日期', + 'HTTP Client:' => 'HTTP 客戶端:', + 'Assigned' => '已分配', + 'Task limits apply to each swimlane individually' => '任務限制個別應用於每個泳道', + 'Column task limits apply to each swimlane individually' => '欄位任務限制個別應用於每個泳道', + 'Column task limits are applied to each swimlane individually' => '欄位任務限制個別應用於每個泳道', + 'Column task limits are applied across swimlanes' => '欄位任務限制應用於所有泳道', + 'Task limit: ' => '任務限制:', + 'Change to global tag' => '變更為全域標籤', + 'Do you really want to make the tag "%s" global?' => '您確定要將標籤 "%s" 設為全域嗎?', + 'Enable global tags for this project' => '啟用此專案的全域標籤', + 'Group membership(s):' => '群組成員:', + '%s is a member of the following group(s): %s' => '%s 是以下群組的成員:%s', + '%d/%d group(s) shown' => '顯示 %d/%d 個群組', + 'Subtask creation or modification' => '子任務建立或修改', + 'Assign the task to a specific user when the task is moved to a specific swimlane' => '將任務移動到特定泳道時,將任務分配給特定使用者', + 'Comment' => '評論', + 'Collapse vertically' => '垂直收合', + 'Expand vertically' => '垂直展開', + 'MXN - Mexican Peso' => 'MXN - 墨西哥比索', + 'Estimated vs actual time per column' => '每個欄位的預估時間與實際時間', + 'HUF - Hungarian Forint' => 'HUF - 匈牙利福林', + 'XBT - Bitcoin' => 'XBT - 比特幣', + 'You must select a file to upload as your avatar!' => '您必須選擇要上傳的檔案作為您的頭像!', + 'The file you uploaded is not a valid image! (Only *.gif, *.jpg, *.jpeg and *.png are allowed!)' => '您上傳的檔案不是有效的圖片! (僅允許 *.gif, *.jpg, *.jpeg 和 *.png)', + 'Automatically set the due date when the task is moved away from a specific column' => '當任務從特定欄位移開時,自動設定截止日期', + 'No other projects found.' => '找不到其他專案。', + 'Tasks copied successfully.' => '任務已成功複製。', + 'Unable to copy tasks.' => '無法複製任務。', + 'Theme' => '主題', + 'Theme:' => '主題:', + 'Light theme' => '淺色主題', + 'Dark theme' => '深色主題', + 'Automatic theme - Sync with system' => '自動主題 - 與系統同步', + 'Application managers or more' => '應用程式管理員或更高權限', + 'Administrators' => '管理員', + 'Visibility:' => '可見性:', + 'Standard users' => '標準使用者', + 'Visibility is required' => '可見性是必需的', + 'The visibility should be an app role' => '可見性應該是應用程式角色', + 'Reply' => '回覆', + '%s wrote: ' => '%s 寫道:', + 'Number of visible tasks in this column and swimlane' => '此欄位和泳道中可見的任務數量', + 'Number of tasks in this swimlane' => '此泳道中的任務數量', + 'Unable to find another subtask in progress, you can close this window.' => '找不到其他正在進行的子任務,您可以關閉此視窗。', + 'This theme is invalid' => '此主題無效', + 'This role is invalid' => '此角色無效', + 'This timezone is invalid' => '此時區無效', + 'This language is invalid' => '此語言無效', + 'This URL is invalid' => '此 URL 無效', + 'Date format invalid' => '日期格式無效', + 'Time format invalid' => '時間格式無效', + 'Invalid Mail transport' => '無效的郵件傳輸', + 'Color invalid' => '顏色無效', + 'This value must be greater or equal to %d' => '此值必須大於或等於 %d', + 'Add a BOM at the beginning of the file (required for Microsoft Excel)' => '在檔案開頭加入 BOM(Microsoft Excel 需要)', + 'Just add these tag(s)' => '只需加入這些標籤', + 'Remove internal link(s)' => '移除內部連結', + 'Import tasks from another project' => '從其他專案匯入任務', + 'Select the project to copy tasks from' => '選擇要複製任務的專案', + 'The total maximum allowed attachments size is %sB.' => '允許的附件總大小上限為 %sB。', + 'Add attachments' => '加入附件', + 'Task #%d "%s" is overdue' => '任務 #%d "%s" 已逾期', + 'Enable notifications by default for all new users' => '預設啟用所有新使用者的通知', + 'Assign the task to its creator for specific columns if no assignee is set manually' => '若未手動指定負責人,則在指定欄位中將任務指派給建立者', + 'Assign a task to the logged user on column change to specified column if no user is assigned' => '若未指派使用者,則在變更欄位移動到指定欄位時將任務指派給已登入使用者', +]; diff --git a/app/Middleware/ApplicationAuthorizationMiddleware.php b/app/Middleware/ApplicationAuthorizationMiddleware.php new file mode 100644 index 0000000..faca2d6 --- /dev/null +++ b/app/Middleware/ApplicationAuthorizationMiddleware.php @@ -0,0 +1,27 @@ +<?php + +namespace Kanboard\Middleware; + +use Kanboard\Core\Controller\AccessForbiddenException; +use Kanboard\Core\Controller\BaseMiddleware; + +/** + * Class ApplicationAuthorizationMiddleware + * + * @package Kanboard\Middleware + * @author Frederic Guillot + */ +class ApplicationAuthorizationMiddleware extends BaseMiddleware +{ + /** + * Execute middleware + */ + public function execute() + { + if (! $this->helper->user->hasAccess($this->router->getController(), $this->router->getAction())) { + throw new AccessForbiddenException(); + } + + $this->next(); + } +} diff --git a/app/Middleware/AuthenticationMiddleware.php b/app/Middleware/AuthenticationMiddleware.php new file mode 100644 index 0000000..18d0ec2 --- /dev/null +++ b/app/Middleware/AuthenticationMiddleware.php @@ -0,0 +1,60 @@ +<?php + +namespace Kanboard\Middleware; + +use Kanboard\Core\Controller\AccessForbiddenException; +use Kanboard\Core\Controller\BaseMiddleware; +use Kanboard\Core\Security\Role; + +/** + * Class AuthenticationMiddleware + * + * @package Kanboard\Middleware + * @author Frederic Guillot + */ +class AuthenticationMiddleware extends BaseMiddleware +{ + /** + * Execute middleware + */ + public function execute() + { + if (! $this->authenticationManager->checkCurrentSession()) { + $this->response->redirect($this->helper->url->to('AuthController', 'login')); + return; + } + + if (! $this->isPublicAccess()) { + $this->handleAuthentication(); + } + + $this->next(); + } + + protected function handleAuthentication() + { + if (! $this->userSession->isLogged() && ! $this->authenticationManager->preAuthentication()) { + $this->nextMiddleware = null; + + if ($this->request->isAjax()) { + $this->response->text('Not Authorized', 401); + } else { + $redirectURI = $this->request->getUri(); + if ($this->request->isSafeRedirectUri($redirectURI)) { + session_set('redirectAfterLogin', $redirectURI); + } + $this->response->redirect($this->helper->url->to('AuthController', 'login')); + } + } + } + + protected function isPublicAccess() + { + if ($this->applicationAuthorization->isAllowed($this->router->getController(), $this->router->getAction(), Role::APP_PUBLIC)) { + $this->nextMiddleware = null; + return true; + } + + return false; + } +} diff --git a/app/Middleware/BootstrapMiddleware.php b/app/Middleware/BootstrapMiddleware.php new file mode 100644 index 0000000..447cc8a --- /dev/null +++ b/app/Middleware/BootstrapMiddleware.php @@ -0,0 +1,46 @@ +<?php + +namespace Kanboard\Middleware; + +use Kanboard\Core\Controller\BaseMiddleware; +use Symfony\Contracts\EventDispatcher\Event; + +/** + * Class BootstrapMiddleware + * + * @package Kanboard\Middleware + * @author Frederic Guillot + */ +class BootstrapMiddleware extends BaseMiddleware +{ + /** + * Execute middleware + */ + public function execute() + { + $this->sessionManager->open(); + $this->dispatcher->dispatch(new Event, 'app.bootstrap'); + $this->sendHeaders(); + $this->next(); + } + + /** + * Send HTTP headers + * + * @access private + */ + private function sendHeaders() + { + $this->response->withContentSecurityPolicy($this->container['cspRules']); + $this->response->withSecurityHeaders(); + $this->response->withP3P(); + + if (ENABLE_XFRAME) { + $this->response->withXframe(); + } + + if (ENABLE_HSTS) { + $this->response->withStrictTransportSecurity(); + } + } +} diff --git a/app/Middleware/PostAuthenticationMiddleware.php b/app/Middleware/PostAuthenticationMiddleware.php new file mode 100644 index 0000000..8ad1f1a --- /dev/null +++ b/app/Middleware/PostAuthenticationMiddleware.php @@ -0,0 +1,36 @@ +<?php + +namespace Kanboard\Middleware; + +use Kanboard\Core\Controller\BaseMiddleware; + +/** + * Class PostAuthenticationMiddleware + * + * @package Kanboard\Middleware + * @author Frederic Guillot + */ +class PostAuthenticationMiddleware extends BaseMiddleware +{ + /** + * Execute middleware + */ + public function execute() + { + $controller = strtolower($this->router->getController()); + $action = strtolower($this->router->getAction()); + $ignore = ($controller === 'twofactorcontroller' && in_array($action, array('code', 'check'))) || ($controller === 'authcontroller' && $action === 'logout'); + + if ($ignore === false && $this->userSession->hasPostAuthentication() && ! $this->userSession->isPostAuthenticationValidated()) { + $this->nextMiddleware = null; + + if ($this->request->isAjax()) { + $this->response->text('Not Authorized', 401); + } else { + $this->response->redirect($this->helper->url->to('TwoFactorController', 'code')); + } + } + + $this->next(); + } +} diff --git a/app/Middleware/ProjectAuthorizationMiddleware.php b/app/Middleware/ProjectAuthorizationMiddleware.php new file mode 100644 index 0000000..704491b --- /dev/null +++ b/app/Middleware/ProjectAuthorizationMiddleware.php @@ -0,0 +1,34 @@ +<?php + +namespace Kanboard\Middleware; + +use Kanboard\Core\Controller\AccessForbiddenException; +use Kanboard\Core\Controller\BaseMiddleware; + +/** + * Class ProjectAuthorizationMiddleware + * + * @package Kanboard\Middleware + * @author Frederic Guillot + */ +class ProjectAuthorizationMiddleware extends BaseMiddleware +{ + /** + * Execute middleware + */ + public function execute() + { + $project_id = $this->request->getIntegerParam('project_id'); + $task_id = $this->request->getIntegerParam('task_id'); + + if ($task_id > 0 && $project_id === 0) { + $project_id = $this->taskFinderModel->getProjectId($task_id); + } + + if ($project_id > 0 && ! $this->helper->user->hasProjectAccess($this->router->getController(), $this->router->getAction(), $project_id)) { + throw new AccessForbiddenException(); + } + + $this->next(); + } +} diff --git a/app/Model/ActionModel.php b/app/Model/ActionModel.php new file mode 100644 index 0000000..b5d2bd0 --- /dev/null +++ b/app/Model/ActionModel.php @@ -0,0 +1,202 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Action Model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class ActionModel extends Base +{ + /** + * SQL table name for actions + * + * @var string + */ + const TABLE = 'actions'; + + /** + * Return actions and parameters for a given user + * + * @access public + * @param integer $user_id + * @return array + */ + public function getAllByUser($user_id) + { + $project_ids = $this->projectPermissionModel->getActiveProjectIds($user_id); + $actions = array(); + + if (! empty($project_ids)) { + $actions = $this->db->table(self::TABLE)->in('project_id', $project_ids)->findAll(); + $params = $this->actionParameterModel->getAllByActions(array_column($actions, 'id')); + $this->attachParamsToActions($actions, $params); + } + + return $actions; + } + + /** + * Return actions and parameters for a given project + * + * @access public + * @param integer $project_id + * @return array + */ + public function getAllByProject($project_id) + { + $actions = $this->db->table(self::TABLE)->eq('project_id', $project_id)->findAll(); + $params = $this->actionParameterModel->getAllByActions(array_column($actions, 'id')); + return $this->attachParamsToActions($actions, $params); + } + + /** + * Return all actions and parameters + * + * @access public + * @return array + */ + public function getAll() + { + $actions = $this->db->table(self::TABLE)->findAll(); + $params = $this->actionParameterModel->getAll(); + return $this->attachParamsToActions($actions, $params); + } + + /** + * Fetch an action + * + * @access public + * @param integer $action_id + * @return array + */ + public function getById($action_id) + { + $action = $this->db->table(self::TABLE)->eq('id', $action_id)->findOne(); + + if (! empty($action)) { + $action['params'] = $this->actionParameterModel->getAllByAction($action_id); + } + + return $action; + } + + /** + * Get the projectId by the actionId + * + * @access public + * @param integer $action_id + * @return integer + */ + public function getProjectId($action_id) + { + return $this->db->table(self::TABLE)->eq('id', $action_id)->findOneColumn('project_id') ?: 0; + } + + /** + * Attach parameters to actions + * + * @access private + * @param array &$actions + * @param array &$params + * @return array + */ + private function attachParamsToActions(array &$actions, array &$params) + { + foreach ($actions as &$action) { + $action['params'] = isset($params[$action['id']]) ? $params[$action['id']] : array(); + } + + return $actions; + } + + /** + * Remove an action + * + * @access public + * @param integer $action_id + * @return bool + */ + public function remove($action_id) + { + return $this->db->table(self::TABLE)->eq('id', $action_id)->remove(); + } + + /** + * Create an action + * + * @access public + * @param array $values Required parameters to save an action + * @return boolean|integer + */ + public function create(array $values) + { + $this->db->startTransaction(); + + $action = array( + 'project_id' => $values['project_id'], + 'event_name' => $values['event_name'], + 'action_name' => $values['action_name'], + ); + + if (! $this->db->table(self::TABLE)->insert($action)) { + $this->db->cancelTransaction(); + return false; + } + + $action_id = $this->db->getLastId(); + + if (! $this->actionParameterModel->create($action_id, $values)) { + $this->db->cancelTransaction(); + return false; + } + + $this->db->closeTransaction(); + + return $action_id; + } + + /** + * Copy actions from a project to another one (skip actions that cannot resolve parameters) + * + * @author Antonio Rabelo + * @param integer $src_project_id Source project id + * @param integer $dst_project_id Destination project id + * @return boolean + */ + public function duplicate($src_project_id, $dst_project_id) + { + $actions = $this->actionModel->getAllByProject($src_project_id); + + foreach ($actions as $action) { + $this->db->startTransaction(); + + $values = array( + 'project_id' => $dst_project_id, + 'event_name' => $action['event_name'], + 'action_name' => $action['action_name'], + ); + + if (! $this->db->table(self::TABLE)->insert($values)) { + $this->db->cancelTransaction(); + continue; + } + + $action_id = $this->db->getLastId(); + + if (! $this->actionParameterModel->duplicateParameters($dst_project_id, $action_id, $action['params'])) { + $this->logger->error('Action::duplicate => skip action '.$action['action_name'].' '.$action['id']); + $this->db->cancelTransaction(); + continue; + } + + $this->db->closeTransaction(); + } + + return true; + } +} diff --git a/app/Model/ActionParameterModel.php b/app/Model/ActionParameterModel.php new file mode 100644 index 0000000..8df76a5 --- /dev/null +++ b/app/Model/ActionParameterModel.php @@ -0,0 +1,173 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Action Parameter Model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class ActionParameterModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'action_has_params'; + + /** + * Get all action params + * + * @access public + * @return array + */ + public function getAll() + { + $params = $this->db->table(self::TABLE)->findAll(); + return $this->toDictionary($params); + } + + /** + * Get all params for a list of actions + * + * @access public + * @param array $action_ids + * @return array + */ + public function getAllByActions(array $action_ids) + { + $params = $this->db->table(self::TABLE)->in('action_id', $action_ids)->findAll(); + return $this->toDictionary($params); + } + + /** + * Build params dictionary + * + * @access private + * @param array $params + * @return array + */ + private function toDictionary(array $params) + { + $result = array(); + + foreach ($params as $param) { + $result[$param['action_id']][$param['name']] = $param['value']; + } + + return $result; + } + + /** + * Get all action params for a given action + * + * @access public + * @param integer $action_id + * @return array + */ + public function getAllByAction($action_id) + { + return $this->db->hashtable(self::TABLE)->eq('action_id', $action_id)->getAll('name', 'value'); + } + + /** + * Insert new parameters for an action + * + * @access public + * @param integer $action_id + * @param array $values + * @return boolean + */ + public function create($action_id, array $values) + { + foreach ($values['params'] as $name => $value) { + $param = array( + 'action_id' => $action_id, + 'name' => $name, + 'value' => $value, + ); + + if (! $this->db->table(self::TABLE)->save($param)) { + return false; + } + } + + return true; + } + + /** + * Duplicate action parameters + * + * @access public + * @param integer $project_id + * @param integer $action_id + * @param array $params + * @return boolean + */ + public function duplicateParameters($project_id, $action_id, array $params) + { + foreach ($params as $name => $value) { + $value = $this->resolveParameter($project_id, $name, $value); + + if ($value === false) { + $this->logger->error('ActionParameter::duplicateParameters => unable to resolve '.$name.'='.$value); + return false; + } + + $values = array( + 'action_id' => $action_id, + 'name' => $name, + 'value' => $value, + ); + + if (! $this->db->table(self::TABLE)->insert($values)) { + return false; + } + } + + return true; + } + + /** + * Resolve action parameter values according to another project + * + * @access private + * @param integer $project_id + * @param string $name + * @param string $value + * @return mixed + */ + private function resolveParameter($project_id, $name, $value) + { + switch ($name) { + case 'project_id': + return $value != $project_id ? $value : false; + case 'category_id': + if ($value == 0) { + return 0; + } + return $this->categoryModel->getIdByName($project_id, $this->categoryModel->getNameById($value)) ?: false; + case 'src_column_id': + case 'dest_column_id': + case 'dst_column_id': + case 'column_id': + $column = $this->columnModel->getById($value); + return empty($column) ? false : ($this->columnModel->getColumnIdByTitle($project_id, $column['title']) ?: false); + case 'user_id': + case 'owner_id': + if ($value == 0) { + return 0; + } + return $this->projectPermissionModel->isAssignable($project_id, $value) ? $value : false; + case 'swimlane_id': + $column = $this->swimlaneModel->getById($value); + return empty($column) ? false : ($this->swimlaneModel->getIdByName($project_id, $column['name']) ?: false); + default: + return $value; + } + } +} diff --git a/app/Model/AvatarFileModel.php b/app/Model/AvatarFileModel.php new file mode 100644 index 0000000..923e639 --- /dev/null +++ b/app/Model/AvatarFileModel.php @@ -0,0 +1,159 @@ +<?php + +namespace Kanboard\Model; + +use Exception; +use Kanboard\Core\Base; + +/** + * Avatar File + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class AvatarFileModel extends Base +{ + /** + * Path prefix + * + * @var string + */ + const PATH_PREFIX = 'avatars'; + + /** + * Get image filename + * + * @access public + * @param integer $user_id + * @return string + */ + public function getFilename($user_id) + { + return $this->db->table(UserModel::TABLE)->eq('id', $user_id)->findOneColumn('avatar_path'); + } + + /** + * Add avatar in the user profile + * + * @access public + * @param integer $user_id Foreign key + * @param string $path Path on the disk + * @return bool + */ + public function create($user_id, $path) + { + $result = $this->db->table(UserModel::TABLE)->eq('id', $user_id)->update(array( + 'avatar_path' => $path, + )); + + $this->userSession->refresh($user_id); + + return $result; + } + + /** + * Remove avatar from the user profile + * + * @access public + * @param integer $user_id Foreign key + * @return bool + */ + public function remove($user_id) + { + try { + $filename = $this->getFilename($user_id); + + if (! empty($filename)) { + $this->objectStorage->remove($filename); + return $this->db->table(UserModel::TABLE)->eq('id', $user_id)->update(array('avatar_path' => '')); + } + } catch (Exception $e) { + $this->logger->error($e->getMessage()); + return false; + } + + return true; + } + + /** + * Upload avatar image file + * + * @access public + * @param integer $user_id + * @param array $file + * @return boolean + */ + public function uploadImageFile($user_id, array $file) + { + try { + if ($file['error'] == UPLOAD_ERR_OK && $file['size'] > 0) { + $destinationFilename = $this->generatePath($user_id, $file['name']); + $this->objectStorage->moveUploadedFile($file['tmp_name'], $destinationFilename); + $this->create($user_id, $destinationFilename); + } else { + throw new Exception('File not uploaded: '.var_export($file['error'], true)); + } + + } catch (Exception $e) { + $this->logger->error($e->getMessage()); + return false; + } + + return true; + } + + /** + * Upload avatar image content + * + * @access public + * @param integer $user_id + * @param string $blob + * @return boolean + */ + public function uploadImageContent($user_id, &$blob) + { + try { + $destinationFilename = $this->generatePath($user_id, 'imageContent'); + $this->objectStorage->put($destinationFilename, $blob); + $this->create($user_id, $destinationFilename); + } catch (Exception $e) { + $this->logger->error($e->getMessage()); + return false; + } + + return true; + } + + /** + * Generate the path for a new filename + * + * @access public + * @param integer $user_id + * @param string $filename + * @return string + */ + public function generatePath($user_id, $filename) + { + return implode(DIRECTORY_SEPARATOR, array(self::PATH_PREFIX, $user_id, hash('sha1', $filename.time()))); + } + + /** + * Check if a filename is an image (file types that can be shown as avatar) + * + * @access public + * @param string $filename Filename + * @return bool + */ + public function isAvatarImage($filename) + { + switch (get_file_extension($filename)) { + case 'jpeg': + case 'jpg': + case 'png': + case 'gif': + return true; + } + + return false; + } +} diff --git a/app/Model/BoardModel.php b/app/Model/BoardModel.php new file mode 100644 index 0000000..ad590ff --- /dev/null +++ b/app/Model/BoardModel.php @@ -0,0 +1,97 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Board model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class BoardModel extends Base +{ + /** + * Get Kanboard default columns + * + * @access public + * @return string[] + */ + public function getDefaultColumns() + { + return array(t('Backlog'), t('Ready'), t('Work in progress'), t('Done')); + } + + /** + * Get user default columns + * + * @access public + * @return array + */ + public function getUserColumns() + { + $column_names = array_unique(explode_csv_field($this->configModel->get('board_columns', implode(',', $this->getDefaultColumns())))); + $columns = array(); + + foreach ($column_names as $column_name) { + $columns[] = array( + 'title' => $column_name, + 'task_limit' => 0, + 'description' => '', + 'hide_in_dashboard' => 0, + ); + } + + return $columns; + } + + /** + * Create a board with default columns, must be executed inside a transaction + * + * @access public + * @param integer $project_id Project id + * @param array $columns Column parameters [ 'title' => 'boo', 'task_limit' => 2 ... ] + * @return boolean + */ + public function create($project_id, array $columns) + { + $position = 0; + + foreach ($columns as $column) { + $values = array( + 'title' => $column['title'], + 'position' => ++$position, + 'project_id' => $project_id, + 'task_limit' => $column['task_limit'], + 'description' => $column['description'], + 'hide_in_dashboard' => $column['hide_in_dashboard'] ?: 0, // Avoid SQL error with Postgres + ); + + if (! $this->db->table(ColumnModel::TABLE)->save($values)) { + return false; + } + } + + return true; + } + + /** + * Copy board columns from a project to another one + * + * @author Antonio Rabelo + * @param integer $project_from Project Template + * @param integer $project_to Project that receives the copy + * @return boolean + */ + public function duplicate($project_from, $project_to) + { + $columns = $this->db->table(ColumnModel::TABLE) + ->columns('title', 'task_limit', 'description', 'hide_in_dashboard') + ->eq('project_id', $project_from) + ->asc('position') + ->findAll(); + + return $this->boardModel->create($project_to, $columns); + } +} diff --git a/app/Model/CaptchaModel.php b/app/Model/CaptchaModel.php new file mode 100644 index 0000000..b9e39f1 --- /dev/null +++ b/app/Model/CaptchaModel.php @@ -0,0 +1,63 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Captcha model + * + * @package Kanboard\Model + * + * {"<IP_ADDRESS>": {"failed_login": <COUNT>, "expiration_date": <TIMESTAMP>}} + */ +class CaptchaModel extends Base +{ + public function incrementFailedLogin($ipAddress) + { + $data = $this->getCaptchaData(); + if (!isset($data[$ipAddress])) { + $data[$ipAddress] = ['failed_login' => 0, 'expiration_date' => 0]; + } + + $data[$ipAddress]['failed_login']++; + if ($data[$ipAddress]['failed_login'] >= BRUTEFORCE_CAPTCHA) { + $data[$ipAddress]['lock_expiration_date'] = time() + BRUTEFORCE_LOCKDOWN_DURATION; + } + + $this->setCaptchaData($data); + } + + public function resetFailedLogin($ipAddress) + { + $data = $this->getCaptchaData(); + if (isset($data[$ipAddress])) { + unset($data[$ipAddress]); + $this->setCaptchaData($data); + } + } + + public function isLocked($ipAddress) + { + $data = $this->getCaptchaData(); + if (isset($data[$ipAddress]) && isset($data[$ipAddress]['lock_expiration_date'])) { + return $data[$ipAddress]['lock_expiration_date'] > time(); + } + return false; + } + + protected function getCaptchaData() + { + $rawData = $this->configModel->getOption('captcha_data', '{}'); + $data = json_decode($rawData, true); + if (!is_array($data)) { + $data = []; + } + return $data; + } + + protected function setCaptchaData(array $data) + { + $this->configModel->save(['captcha_data' => json_encode($data)]); + } +} diff --git a/app/Model/CategoryModel.php b/app/Model/CategoryModel.php new file mode 100644 index 0000000..798c8a9 --- /dev/null +++ b/app/Model/CategoryModel.php @@ -0,0 +1,228 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Category model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class CategoryModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'project_has_categories'; + + /** + * Return true if a category exists for a given project + * + * @access public + * @param integer $category_id Category id + * @return boolean + */ + public function exists($category_id) + { + return $this->db->table(self::TABLE)->eq('id', $category_id)->exists(); + } + + /** + * Get a category by the id + * + * @access public + * @param integer $category_id Category id + * @return array + */ + public function getById($category_id) + { + return $this->db->table(self::TABLE)->eq('id', $category_id)->findOne(); + } + + /** + * Get the category name by the id + * + * @access public + * @param integer $category_id Category id + * @return string + */ + public function getNameById($category_id) + { + return $this->db->table(self::TABLE)->eq('id', $category_id)->findOneColumn('name') ?: ''; + } + + /** + * Get the projectId by the category id + * + * @access public + * @param integer $category_id Category id + * @return integer + */ + public function getProjectId($category_id) + { + return $this->db->table(self::TABLE)->eq('id', $category_id)->findOneColumn('project_id') ?: 0; + } + + /** + * Get a category id by the category name and project id + * + * @access public + * @param integer $project_id Project id + * @param string $category_name Category name + * @return integer + */ + public function getIdByName($project_id, $category_name) + { + return (int) $this->db->table(self::TABLE) + ->eq('project_id', $project_id) + ->eq('name', $category_name) + ->findOneColumn('id'); + } + + /** + * Return the list of all categories + * + * @access public + * @param integer $project_id Project id + * @param bool $prepend_none If true, prepend to the list the value 'None' + * @param bool $prepend_all If true, prepend to the list the value 'All' + * @return array + */ + public function getList($project_id, $prepend_none = true, $prepend_all = false) + { + $listing = $this->db->hashtable(self::TABLE) + ->eq('project_id', $project_id) + ->asc('name') + ->getAll('id', 'name'); + + $prepend = array(); + + if ($prepend_all) { + $prepend[-1] = t('All categories'); + } + + if ($prepend_none) { + $prepend[0] = t('No category'); + } + + return $prepend + $listing; + } + + /** + * Return all categories for a given project + * + * @access public + * @param integer $project_id Project id + * @return array + */ + public function getAll($project_id) + { + return $this->db->table(self::TABLE) + ->eq('project_id', $project_id) + ->asc('name') + ->findAll(); + } + + /** + * Create default categories during project creation (transaction already started in Project::create()) + * + * @access public + * @param integer $project_id + * @return boolean + */ + public function createDefaultCategories($project_id) + { + $results = array(); + $categories = array_unique(explode_csv_field($this->configModel->get('project_categories'))); + + foreach ($categories as $category) { + $results[] = $this->db->table(self::TABLE)->insert(array( + 'project_id' => $project_id, + 'name' => $category, + )); + } + + return in_array(false, $results, true); + } + + /** + * Create a category (run inside a transaction) + * + * @access public + * @param array $values Form values + * @return bool|integer + */ + public function create(array $values) + { + return $this->db->table(self::TABLE)->persist($values); + } + + /** + * Update a category + * + * @access public + * @param array $values Form values + * @return bool + */ + public function update(array $values) + { + $updates = $values; + unset($updates['id']); + return $this->db->table(self::TABLE)->eq('id', $values['id'])->save($updates); + } + + /** + * Remove a category + * + * @access public + * @param integer $category_id Category id + * @return bool + */ + public function remove($category_id) + { + $this->db->startTransaction(); + + $this->db->table(TaskModel::TABLE)->eq('category_id', $category_id)->update(array('category_id' => 0)); + + if (! $this->db->table(self::TABLE)->eq('id', $category_id)->remove()) { + $this->db->cancelTransaction(); + return false; + } + + $this->db->closeTransaction(); + + return true; + } + + /** + * Duplicate categories from a project to another one, must be executed inside a transaction + * + * @author Antonio Rabelo + * @param integer $src_project_id Source project id + * @param integer $dst_project_id Destination project id + * @return boolean + */ + public function duplicate($src_project_id, $dst_project_id) + { + $categories = $this->db + ->table(self::TABLE) + ->columns('name', 'description', 'color_id') + ->eq('project_id', $src_project_id) + ->asc('name') + ->findAll(); + + foreach ($categories as $category) { + $category['project_id'] = $dst_project_id; + + if (! $this->db->table(self::TABLE)->save($category)) { + return false; + } + } + + return true; + } +} diff --git a/app/Model/ColorModel.php b/app/Model/ColorModel.php new file mode 100644 index 0000000..13566fb --- /dev/null +++ b/app/Model/ColorModel.php @@ -0,0 +1,231 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Color model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class ColorModel extends Base +{ + /** + * Default colors + * + * @access protected + * @var array + */ + protected $default_colors = array( + 'yellow' => array( + 'name' => 'Yellow', + 'background' => 'rgb(245, 247, 196)', + 'border' => 'rgb(223, 227, 45)', + ), + 'blue' => array( + 'name' => 'Blue', + 'background' => 'rgb(219, 235, 255)', + 'border' => 'rgb(168, 207, 255)', + ), + 'green' => array( + 'name' => 'Green', + 'background' => 'rgb(189, 244, 203)', + 'border' => 'rgb(74, 227, 113)', + ), + 'purple' => array( + 'name' => 'Purple', + 'background' => 'rgb(223, 176, 255)', + 'border' => 'rgb(205, 133, 254)', + ), + 'red' => array( + 'name' => 'Red', + 'background' => 'rgb(255, 187, 187)', + 'border' => 'rgb(255, 151, 151)', + ), + 'orange' => array( + 'name' => 'Orange', + 'background' => 'rgb(255, 215, 179)', + 'border' => 'rgb(255, 172, 98)', + ), + 'grey' => array( + 'name' => 'Grey', + 'background' => 'rgb(238, 238, 238)', + 'border' => 'rgb(204, 204, 204)', + ), + 'brown' => array( + 'name' => 'Brown', + 'background' => '#d7ccc8', + 'border' => '#4e342e', + ), + 'deep_orange' => array( + 'name' => 'Deep Orange', + 'background' => '#ffab91', + 'border' => '#e64a19', + ), + 'dark_grey' => array( + 'name' => 'Dark Grey', + 'background' => '#cfd8dc', + 'border' => '#455a64', + ), + 'pink' => array( + 'name' => 'Pink', + 'background' => '#f48fb1', + 'border' => '#d81b60', + ), + 'teal' => array( + 'name' => 'Teal', + 'background' => '#80cbc4', + 'border' => '#00695c', + ), + 'cyan' => array( + 'name' => 'Cyan', + 'background' => '#b2ebf2', + 'border' => '#00bcd4', + ), + 'lime' => array( + 'name' => 'Lime', + 'background' => '#e6ee9c', + 'border' => '#afb42b', + ), + 'light_green' => array( + 'name' => 'Light Green', + 'background' => '#dcedc8', + 'border' => '#689f38', + ), + 'amber' => array( + 'name' => 'Amber', + 'background' => '#ffe082', + 'border' => '#ffa000', + ), + ); + + /** + * Find a color id from the name or the id + * + * @access public + * @param string $color + * @return string + */ + public function find($color) + { + $color = strtolower($color); + + foreach ($this->default_colors as $color_id => $params) { + if ($color_id === $color) { + return $color_id; + } elseif ($color === strtolower($params['name'])) { + return $color_id; + } + } + + return ''; + } + + /** + * Get color properties + * + * @access public + * @param string $color_id + * @return array + */ + public function getColorProperties($color_id) + { + if (isset($this->default_colors[$color_id])) { + return $this->default_colors[$color_id]; + } + + return $this->default_colors[$this->getDefaultColor()]; + } + + /** + * Get available colors + * + * @access public + * @param bool $prepend + * @return array + */ + public function getList($prepend = false) + { + $listing = $prepend ? array('' => t('All colors')) : array(); + + foreach ($this->default_colors as $color_id => $color) { + $listing[$color_id] = t($color['name']); + } + + $this->hook->reference('model:color:get-list', $listing); + + return $listing; + } + + /** + * Get the default color + * + * @access public + * @return string + */ + public function getDefaultColor() + { + return $this->configModel->get('default_color', 'yellow'); + } + + /** + * Get the default colors + * + * @access public + * @return array + */ + public function getDefaultColors() + { + return $this->default_colors; + } + + /** + * Get border color from string + * + * @access public + * @param string $color_id Color id + * @return string + */ + public function getBorderColor($color_id) + { + $color = $this->getColorProperties($color_id); + return $color['border']; + } + + /** + * Get background color from the color_id + * + * @access public + * @param string $color_id Color id + * @return string + */ + public function getBackgroundColor($color_id) + { + $color = $this->getColorProperties($color_id); + return $color['background']; + } + + /** + * Get CSS stylesheet of all colors + * + * @access public + * @return string + */ + public function getCss() + { + $buffer = ''; + + foreach ($this->default_colors as $color => $values) { + $buffer .= '.task-board.color-'.$color.', .task-summary-container.color-'.$color.', .color-picker-square.color-'.$color.', .task-board-category.color-'.$color.', .table-list-category.color-'.$color.', .task-tag.color-'.$color.' {'; + $buffer .= 'background-color: '.$values['background'].';'; + $buffer .= 'border-color: '.$values['border']; + $buffer .= '}'; + $buffer .= 'td.color-'.$color.' { background-color: '.$values['background'].'}'; + $buffer .= '.table-list-row.color-'.$color.' {border-left: 5px solid '.$values['border'].'}'; + } + + return $buffer; + } +} diff --git a/app/Model/ColumnModel.php b/app/Model/ColumnModel.php new file mode 100644 index 0000000..36bff5e --- /dev/null +++ b/app/Model/ColumnModel.php @@ -0,0 +1,262 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Column Model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class ColumnModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'columns'; + + /** + * Get a column by the id + * + * @access public + * @param integer $column_id Column id + * @return array + */ + public function getById($column_id) + { + return $this->db->table(self::TABLE)->eq('id', $column_id)->findOne(); + } + + /** + * Get projectId by the columnId + * + * @access public + * @param integer $column_id Column id + * @return integer + */ + public function getProjectId($column_id) + { + return $this->db->table(self::TABLE)->eq('id', $column_id)->findOneColumn('project_id'); + } + + /** + * Get the first column id for a given project + * + * @access public + * @param integer $project_id Project id + * @return integer + */ + public function getFirstColumnId($project_id) + { + return $this->db->table(self::TABLE)->eq('project_id', $project_id)->asc('position')->findOneColumn('id'); + } + + /** + * Get the last column id for a given project + * + * @access public + * @param integer $project_id Project id + * @return integer + */ + public function getLastColumnId($project_id) + { + return $this->db->table(self::TABLE)->eq('project_id', $project_id)->desc('position')->findOneColumn('id'); + } + + /** + * Get the position of the last column for a given project + * + * @access public + * @param integer $project_id Project id + * @return integer + */ + public function getLastColumnPosition($project_id) + { + return (int) $this->db + ->table(self::TABLE) + ->eq('project_id', $project_id) + ->desc('position') + ->findOneColumn('position'); + } + + /** + * Get a column id by the name + * + * @access public + * @param integer $project_id + * @param string $title + * @return integer + */ + public function getColumnIdByTitle($project_id, $title) + { + return (int) $this->db->table(self::TABLE)->eq('project_id', $project_id)->eq('title', $title)->findOneColumn('id'); + } + + /** + * Get a column title by the id + * + * @access public + * @param integer $column_id + * @return integer + */ + public function getColumnTitleById($column_id) + { + return $this->db->table(self::TABLE)->eq('id', $column_id)->findOneColumn('title'); + } + + /** + * Get all columns sorted by position for a given project + * + * @access public + * @param integer $project_id Project id + * @return array + */ + public function getAll($project_id) + { + return $this->db->table(self::TABLE)->eq('project_id', $project_id)->asc('position')->findAll(); + } + + /** + * Get all columns with opened task count only + * + * @access public + * @param integer $project_id Project id + * @return array + */ + public function getAllWithOpenedTaskCount($project_id) + { + return $this->db->table(self::TABLE) + ->columns('id', 'title', 'position', 'task_limit', 'description', 'hide_in_dashboard', 'project_id') + ->subquery("SELECT COUNT(*) FROM ".TaskModel::TABLE." WHERE column_id=".self::TABLE.".id AND is_active='1'", 'nb_open_tasks') + ->eq('project_id', $project_id) + ->asc('position') + ->findAll(); + } + + /** + * Get all columns with task count + * + * @access public + * @param integer $project_id Project id + * @return array + */ + public function getAllWithTaskCount($project_id) + { + return $this->db->table(self::TABLE) + ->columns('id', 'title', 'position', 'task_limit', 'description', 'hide_in_dashboard', 'project_id') + ->subquery("SELECT COUNT(*) FROM ".TaskModel::TABLE." WHERE column_id=".self::TABLE.".id AND is_active='1'", 'nb_open_tasks') + ->subquery("SELECT COUNT(*) FROM ".TaskModel::TABLE." WHERE column_id=".self::TABLE.".id AND is_active='0'", 'nb_closed_tasks') + ->eq('project_id', $project_id) + ->asc('position') + ->findAll(); + } + + /** + * Get the list of columns sorted by position [ column_id => title ] + * + * @access public + * @param integer $project_id Project id + * @param boolean $prepend Prepend a default value + * @return array + */ + public function getList($project_id, $prepend = false) + { + $listing = $this->db->hashtable(self::TABLE)->eq('project_id', $project_id)->asc('position')->getAll('id', 'title'); + return $prepend ? array(-1 => t('All columns')) + $listing : $listing; + } + + /** + * Add a new column to the board + * + * @access public + * @param integer $project_id Project id + * @param string $title Column title + * @param integer $task_limit Task limit + * @param string $description Column description + * @param integer $hide_in_dashboard + * @return bool|int + */ + public function create($project_id, $title, $task_limit = 0, $description = '', $hide_in_dashboard = 0) + { + $values = array( + 'project_id' => $project_id, + 'title' => $title, + 'task_limit' => intval($task_limit), + 'position' => $this->getLastColumnPosition($project_id) + 1, + 'hide_in_dashboard' => $hide_in_dashboard, + 'description' => $description, + ); + + return $this->db->table(self::TABLE)->persist($values); + } + + /** + * Update a column + * + * @access public + * @param integer $column_id Column id + * @param string $title Column title + * @param integer $task_limit Task limit + * @param string $description Optional description + * @param integer $hide_in_dashboard + * @return boolean + */ + public function update($column_id, $title, $task_limit = 0, $description = '', $hide_in_dashboard = 0) + { + return $this->db->table(self::TABLE)->eq('id', $column_id)->update(array( + 'title' => $title, + 'task_limit' => intval($task_limit), + 'hide_in_dashboard' => $hide_in_dashboard, + 'description' => $description, + )); + } + + /** + * Remove a column and all tasks associated to this column + * + * @access public + * @param integer $column_id Column id + * @return boolean + */ + public function remove($column_id) + { + return $this->db->table(self::TABLE)->eq('id', $column_id)->remove(); + } + + /** + * Change column position + * + * @access public + * @param integer $project_id + * @param integer $column_id + * @param integer $position + * @return boolean + */ + public function changePosition($project_id, $column_id, $position) + { + if ($position < 1 || $position > $this->db->table(self::TABLE)->eq('project_id', $project_id)->count()) { + return false; + } + + $column_ids = $this->db->table(self::TABLE)->eq('project_id', $project_id)->neq('id', $column_id)->asc('position')->findAllByColumn('id'); + $offset = 1; + $results = array(); + + foreach ($column_ids as $current_column_id) { + if ($offset == $position) { + $offset++; + } + + $results[] = $this->db->table(self::TABLE)->eq('id', $current_column_id)->update(array('position' => $offset)); + $offset++; + } + + $results[] = $this->db->table(self::TABLE)->eq('id', $column_id)->eq('project_id', $project_id)->update(array('position' => $position)); + + return !in_array(false, $results, true); + } +} diff --git a/app/Model/ColumnMoveRestrictionModel.php b/app/Model/ColumnMoveRestrictionModel.php new file mode 100644 index 0000000..473b2a7 --- /dev/null +++ b/app/Model/ColumnMoveRestrictionModel.php @@ -0,0 +1,174 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Class ColumnMoveRestrictionModel + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class ColumnMoveRestrictionModel extends Base +{ + const TABLE = 'column_has_move_restrictions'; + + /** + * Fetch one restriction + * + * @param int $project_id + * @param int $restriction_id + * @return array|null + */ + public function getById($project_id, $restriction_id) + { + return $this->db + ->table(self::TABLE) + ->columns( + self::TABLE.'.restriction_id', + self::TABLE.'.project_id', + self::TABLE.'.role_id', + self::TABLE.'.src_column_id', + self::TABLE.'.dst_column_id', + self::TABLE.'.only_assigned', + 'pr.role', + 'sc.title as src_column_title', + 'dc.title as dst_column_title' + ) + ->left(ColumnModel::TABLE, 'sc', 'id', self::TABLE, 'src_column_id') + ->left(ColumnModel::TABLE, 'dc', 'id', self::TABLE, 'dst_column_id') + ->left(ProjectRoleModel::TABLE, 'pr', 'role_id', self::TABLE, 'role_id') + ->eq(self::TABLE.'.project_id', $project_id) + ->eq(self::TABLE.'.restriction_id', $restriction_id) + ->findOne(); + } + + /** + * Get all project column restrictions + * + * @param int $project_id + * @return array + */ + public function getAll($project_id) + { + return $this->db + ->table(self::TABLE) + ->columns( + self::TABLE.'.restriction_id', + self::TABLE.'.project_id', + self::TABLE.'.role_id', + self::TABLE.'.src_column_id', + self::TABLE.'.dst_column_id', + self::TABLE.'.only_assigned', + 'pr.role', + 'sc.title as src_column_title', + 'dc.title as dst_column_title' + ) + ->left(ColumnModel::TABLE, 'sc', 'id', self::TABLE, 'src_column_id') + ->left(ColumnModel::TABLE, 'dc', 'id', self::TABLE, 'dst_column_id') + ->left(ProjectRoleModel::TABLE, 'pr', 'role_id', self::TABLE, 'role_id') + ->eq(self::TABLE.'.project_id', $project_id) + ->findAll(); + } + + /** + * Get all sortable column Ids + * + * @param int $project_id + * @param string $role + * @return array + */ + public function getSortableColumns($project_id, $role) + { + return $this->db + ->table(self::TABLE) + ->columns(self::TABLE.'.src_column_id', self::TABLE.'.dst_column_id', self::TABLE.'.only_assigned') + ->left(ProjectRoleModel::TABLE, 'pr', 'role_id', self::TABLE, 'role_id') + ->eq(self::TABLE.'.project_id', $project_id) + ->eq('pr.role', $role) + ->findAll(); + } + + /** + * Create a new column restriction + * + * @param int $project_id + * @param int $role_id + * @param int $src_column_id + * @param int $dst_column_id + * @param bool $only_assigned + * @return bool|int + */ + public function create($project_id, $role_id, $src_column_id, $dst_column_id, $only_assigned = false) + { + return $this->db + ->table(self::TABLE) + ->persist(array( + 'project_id' => $project_id, + 'role_id' => $role_id, + 'src_column_id' => $src_column_id, + 'dst_column_id' => $dst_column_id, + 'only_assigned' => (int) $only_assigned, + )); + } + + /** + * Remove a permission + * + * @param int $restriction_id + * @return bool + */ + public function remove($restriction_id) + { + return $this->db->table(self::TABLE)->eq('restriction_id', $restriction_id)->remove(); + } + + /** + * Copy column_move_restriction models from a custome_role in the src project to the dst custom_role of the dst project + * + * @param integer $project_src_id + * @param integer $project_dst_id + * @param integer $role_src_id + * @param integer $role_dst_id + * @return boolean + */ + public function duplicate($project_src_id, $project_dst_id, $role_src_id, $role_dst_id) + { + $rows = $this->db->table(self::TABLE) + ->eq('project_id', $project_src_id) + ->eq('role_id', $role_src_id) + ->findAll(); + + foreach ($rows as $row) { + $src_column_title = $this->columnModel->getColumnTitleById($row['src_column_id']); + $dst_column_title = $this->columnModel->getColumnTitleById($row['dst_column_id']); + $src_column_id = $this->columnModel->getColumnIdByTitle($project_dst_id, $src_column_title); + $dst_column_id = $this->columnModel->getColumnIdByTitle($project_dst_id, $dst_column_title); + + if (! $dst_column_id) { + $this->logger->error("The column $dst_column_title is not present in project $project_dst_id"); + return false; + } + + if (! $src_column_id) { + $this->logger->error("The column $src_column_title is not present in project $project_dst_id"); + return false; + } + + $result = $this->db->table(self::TABLE)->persist(array( + 'project_id' => $project_dst_id, + 'role_id' => $role_dst_id, + 'src_column_id' => $src_column_id, + 'dst_column_id' => $dst_column_id, + 'only_assigned' => (int) $row['only_assigned'], + )); + + if (! $result) { + return false; + } + } + + return true; + } +} diff --git a/app/Model/ColumnRestrictionModel.php b/app/Model/ColumnRestrictionModel.php new file mode 100644 index 0000000..4023b48 --- /dev/null +++ b/app/Model/ColumnRestrictionModel.php @@ -0,0 +1,192 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Class ColumnRestrictionModel + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class ColumnRestrictionModel extends Base +{ + const TABLE = 'column_has_restrictions'; + + const RULE_ALLOW_TASK_CREATION = 'allow.task_creation'; + const RULE_ALLOW_TASK_OPEN_CLOSE = 'allow.task_open_close'; + const RULE_BLOCK_TASK_CREATION = 'block.task_creation'; + const RULE_BLOCK_TASK_OPEN_CLOSE = 'block.task_open_close'; + + /** + * Get rules + * + * @return array + */ + public function getRules() + { + return array( + self::RULE_ALLOW_TASK_CREATION => t('Task creation is permitted for this column'), + self::RULE_ALLOW_TASK_OPEN_CLOSE => t('Closing or opening a task is permitted for this column'), + self::RULE_BLOCK_TASK_CREATION => t('Task creation is blocked for this column'), + self::RULE_BLOCK_TASK_OPEN_CLOSE => t('Closing or opening a task is blocked for this column'), + ); + } + + /** + * Fetch one restriction + * + * @param int $project_id + * @param int $restriction_id + * @return array|null + */ + public function getById($project_id, $restriction_id) + { + return $this->db + ->table(self::TABLE) + ->columns( + 'restriction_id', + 'project_id', + 'role_id', + 'column_id', + 'rule', + 'pr.role', + 'c.title as column_title' + ) + ->left(ColumnModel::TABLE, 'c', 'id', self::TABLE, 'column_id') + ->left(ProjectRoleModel::TABLE, 'pr', 'role_id', self::TABLE, 'role_id') + ->eq(self::TABLE.'.project_id', $project_id) + ->eq(self::TABLE.'.restriction_id', $restriction_id) + ->findOne(); + } + + /** + * Get all project column restrictions + * + * @param int $project_id + * @return array + */ + public function getAll($project_id) + { + $rules = $this->getRules(); + $restrictions = $this->db + ->table(self::TABLE) + ->columns( + 'restriction_id', + 'project_id', + 'role_id', + 'column_id', + 'rule', + 'pr.role', + 'c.title as column_title' + ) + ->left(ColumnModel::TABLE, 'c', 'id', self::TABLE, 'column_id') + ->left(ProjectRoleModel::TABLE, 'pr', 'role_id', self::TABLE, 'role_id') + ->eq(self::TABLE.'.project_id', $project_id) + ->findAll(); + + foreach ($restrictions as &$restriction) { + $restriction['title'] = $rules[$restriction['rule']]; + } + + return $restrictions; + } + + /** + * Get restrictions + * + * @param int $project_id + * @param string $role + * @return array + */ + public function getAllByRole($project_id, $role) + { + return $this->db + ->table(self::TABLE) + ->columns( + 'restriction_id', + 'project_id', + 'role_id', + 'column_id', + 'rule', + 'pr.role' + ) + ->eq(self::TABLE.'.project_id', $project_id) + ->eq('pr.role', $role) + ->left(ProjectRoleModel::TABLE, 'pr', 'role_id', self::TABLE, 'role_id') + ->findAll(); + } + + /** + * Create a new column restriction + * + * @param int $project_id + * @param int $role_id + * @param int $column_id + * @param int $rule + * @return bool|int + */ + public function create($project_id, $role_id, $column_id, $rule) + { + return $this->db + ->table(self::TABLE) + ->persist(array( + 'project_id' => $project_id, + 'role_id' => $role_id, + 'column_id' => $column_id, + 'rule' => $rule, + )); + } + + /** + * Remove a permission + * + * @param int $restriction_id + * @return bool + */ + public function remove($restriction_id) + { + return $this->db->table(self::TABLE)->eq('restriction_id', $restriction_id)->remove(); + } + + /** + * Copy column_restriction models from a custome_role in the src project to the dst custom_role of the dst project + * + * @param integer $project_src_id + * @param integer $project_dst_id + * @param integer $role_src_id + * @param integer $role_dst_id + * @return boolean + */ + public function duplicate($project_src_id, $project_dst_id, $role_src_id, $role_dst_id) + { + $rows = $this->db->table(self::TABLE) + ->eq('project_id', $project_src_id) + ->eq('role_id', $role_src_id) + ->findAll(); + + foreach ($rows as $row) { + $column_title = $this->columnModel->getColumnTitleById($row['column_id']); + $dst_column_id = $this->columnModel->getColumnIdByTitle($project_dst_id, $column_title); + + if (! $dst_column_id) { + $this->logger->error("The column $column_title is not present in project $project_dst_id"); + return false; + } + + $result = $this->db->table(self::TABLE)->persist(array( + 'project_id' => $project_dst_id, + 'role_id' => $role_dst_id, + 'column_id' => $dst_column_id, + 'rule' => $row['rule'], + )); + + if (! $result) { + return false; + } + } + + return true; + } +} diff --git a/app/Model/CommentModel.php b/app/Model/CommentModel.php new file mode 100644 index 0000000..299ad77 --- /dev/null +++ b/app/Model/CommentModel.php @@ -0,0 +1,222 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; +use Kanboard\Core\Security\Role; + +/** + * Comment model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class CommentModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'comments'; + + /** + * Events + * + * @var string + */ + const EVENT_UPDATE = 'comment.update'; + const EVENT_CREATE = 'comment.create'; + const EVENT_DELETE = 'comment.delete'; + const EVENT_USER_MENTION = 'comment.user.mention'; + + /** + * Get projectId from commentId + * + * @access public + * @param integer $comment_id + * @return integer + */ + public function getProjectId($comment_id) + { + return $this->db + ->table(self::TABLE) + ->eq(self::TABLE.'.id', $comment_id) + ->join(TaskModel::TABLE, 'id', 'task_id') + ->findOneColumn(TaskModel::TABLE . '.project_id') ?: 0; + } + + /** + * Get visibility from commentId + * + * @access public + * @param integer $comment_id + * @return string + */ + public function getVisibility($comment_id) + { + return $this->db + ->table(self::TABLE) + ->eq(self::TABLE.'.id', $comment_id) + ->findOneColumn(self::TABLE . '.visibility') ?: Role::APP_USER; + } + + /** + * Get all comments for a given task + * + * @access public + * @param integer $task_id Task id + * @param string $sorting ASC/DESC + * @return array + */ + public function getAll($task_id, $sorting = 'ASC') + { + return $this->db + ->table(self::TABLE) + ->columns( + self::TABLE.'.id', + self::TABLE.'.date_creation', + self::TABLE.'.date_modification', + self::TABLE.'.task_id', + self::TABLE.'.user_id', + self::TABLE.'.comment', + self::TABLE.'.visibility', + UserModel::TABLE.'.username', + UserModel::TABLE.'.name', + UserModel::TABLE.'.email', + UserModel::TABLE.'.avatar_path' + ) + ->join(UserModel::TABLE, 'id', 'user_id') + ->orderBy(self::TABLE.'.date_creation', $sorting) + ->orderBy(self::TABLE.'.id', $sorting) + ->eq(self::TABLE.'.task_id', $task_id) + ->findAll(); + } + + /** + * Get a comment + * + * @access public + * @param integer $comment_id Comment id + * @return array + */ + public function getById($comment_id) + { + return $this->db + ->table(self::TABLE) + ->columns( + self::TABLE.'.id', + self::TABLE.'.task_id', + self::TABLE.'.user_id', + self::TABLE.'.date_creation', + self::TABLE.'.date_modification', + self::TABLE.'.comment', + self::TABLE.'.reference', + self::TABLE.'.visibility', + UserModel::TABLE.'.username', + UserModel::TABLE.'.name', + UserModel::TABLE.'.email', + UserModel::TABLE.'.avatar_path' + ) + ->join(UserModel::TABLE, 'id', 'user_id') + ->eq(self::TABLE.'.id', $comment_id) + ->findOne(); + } + + /** + * Get the number of comments for a given task + * + * @access public + * @param integer $task_id Task id + * @return integer + */ + public function count($task_id) + { + return $this->db + ->table(self::TABLE) + ->eq(self::TABLE.'.task_id', $task_id) + ->count(); + } + + /** + * Create a new comment + * + * @access public + * @param array $values Form values + * @return boolean|integer + */ + public function create(array $values) + { + $values = $this->clampVisibility($values); + $values['date_creation'] = time(); + $values['date_modification'] = time(); + $comment_id = $this->db->table(self::TABLE)->persist($values); + + if ($comment_id !== false) { + $this->queueManager->push($this->commentEventJob->withParams($comment_id, self::EVENT_CREATE)); + } + + return $comment_id; + } + + /** + * Update a comment in the database + * + * @access public + * @param array $values Form values + * @return boolean + */ + public function update(array $values) + { + $result = $this->db + ->table(self::TABLE) + ->eq('id', $values['id']) + ->update(array('comment' => $values['comment'], 'date_modification' => time())); + + if ($result) { + $this->queueManager->push($this->commentEventJob->withParams($values['id'], self::EVENT_UPDATE)); + } + + return $result; + } + + /** + * Clamp the visibility field so it never exceeds the current user's role + * + * @access protected + * @param array $values + * @return array + */ + protected function clampVisibility(array $values) + { + if (! $this->userSession->isLogged()) { + return $values; + } + + $visibility = isset($values['visibility']) ? $values['visibility'] : Role::APP_USER; + $userRole = $this->userSession->getRole(); + + if ($userRole === Role::APP_MANAGER && $visibility === Role::APP_ADMIN) { + $values['visibility'] = Role::APP_MANAGER; + } + + if ($userRole === Role::APP_USER && $visibility !== Role::APP_USER) { + $values['visibility'] = Role::APP_USER; + } + + return $values; + } + + /** + * Remove a comment + * + * @access public + * @param integer $comment_id Comment id + * @return boolean + */ + public function remove($comment_id) + { + $this->commentEventJob->execute($comment_id, self::EVENT_DELETE); + return $this->db->table(self::TABLE)->eq('id', $comment_id)->remove(); + } +} diff --git a/app/Model/ConfigModel.php b/app/Model/ConfigModel.php new file mode 100644 index 0000000..232932b --- /dev/null +++ b/app/Model/ConfigModel.php @@ -0,0 +1,122 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Security\Token; + +/** + * Config model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class ConfigModel extends SettingModel +{ + /** + * Get a config variable with in-memory caching + * + * @access public + * @param string $name Parameter name + * @param string $default_value Default value of the parameter + * @return string + */ + public function get($name, $default_value = '') + { + $options = $this->memoryCache->proxy($this, 'getAll'); + return isset($options[$name]) && $options[$name] !== '' ? $options[$name] : $default_value; + } + + /** + * Optimize the Sqlite database + * + * @access public + * @return boolean + */ + public function optimizeDatabase() + { + return $this->db->getConnection()->exec('VACUUM'); + } + + /** + * Compress the Sqlite database + * + * @access public + * @return string + */ + public function downloadDatabase() + { + return gzencode(file_get_contents(DB_FILENAME)); + } + + /** + * Replace database file with uploaded one + * + * @access public + * @param string $file + * @return bool + */ + public function uploadDatabase($file) + { + $this->db->closeConnection(); + return file_put_contents(DB_FILENAME, gzdecode(file_get_contents($file))) !== false; + } + + /** + * Get the Sqlite database size in bytes + * + * @access public + * @return integer + */ + public function getDatabaseSize() + { + return DB_DRIVER === 'sqlite' ? filesize(DB_FILENAME) : 0; + } + + /** + * Get database extra options + * + * @access public + * @return array + */ + public function getDatabaseOptions() + { + if (DB_DRIVER === 'sqlite') { + return [ + 'journal_mode' => $this->db->getConnection()->query('PRAGMA journal_mode')->fetchColumn(), + 'wal_autocheckpoint' => $this->db->getConnection()->query('PRAGMA wal_autocheckpoint')->fetchColumn(), + 'synchronous' => $this->db->getConnection()->query('PRAGMA synchronous')->fetchColumn(), + 'busy_timeout' => $this->db->getConnection()->query('PRAGMA busy_timeout')->fetchColumn(), + ]; + } + + return []; + } + + /** + * Regenerate a token + * + * @access public + * @param string $option Parameter name + * @return boolean + */ + public function regenerateToken($option) + { + return $this->save(array($option => Token::getToken())); + } + + /** + * Prepare data before save + * + * @access public + * @param array $values + * @return array + */ + public function prepare(array $values) + { + if (! empty($values['application_url']) && substr($values['application_url'], -1) !== '/') { + $values['application_url'] = $values['application_url'].'/'; + } + + return $values; + } +} diff --git a/app/Model/CurrencyModel.php b/app/Model/CurrencyModel.php new file mode 100644 index 0000000..be1d3f6 --- /dev/null +++ b/app/Model/CurrencyModel.php @@ -0,0 +1,123 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Currency + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class CurrencyModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'currencies'; + + /** + * Get available application currencies + * + * @access public + * @return array + */ + public function getCurrencies() + { + return array( + 'ARS' => t('ARS - Argentine Peso'), + 'AUD' => t('AUD - Australian Dollar'), + 'BAM' => t('BAM - Konvertible Mark'), + 'BRL' => t('BRL - Brazilian Real'), + 'CAD' => t('CAD - Canadian Dollar'), + 'CHF' => t('CHF - Swiss Francs'), + 'CNY' => t('CNY - Chinese Yuan'), + 'COP' => t('COP - Colombian Peso'), + 'DKK' => t('DKK - Danish Krona'), + 'EUR' => t('EUR - Euro'), + 'GBP' => t('GBP - British Pound'), + 'HRK' => t('HRK - Kuna'), + 'HUF' => t('HUF - Hungarian Forint'), + 'INR' => t('INR - Indian Rupee'), + 'JPY' => t('JPY - Japanese Yen'), + 'MXN' => t('MXN - Mexican Peso'), + 'NOK' => t('NOK - Norwegian Krone'), + 'NZD' => t('NZD - New Zealand Dollar'), + 'PEN' => t('PEN - Peruvian Sol'), + 'RSD' => t('RSD - Serbian dinar'), + 'RUB' => t('RUB - Russian Ruble'), + 'SEK' => t('SEK - Swedish Krona'), + 'TRL' => t('TRL - Turkish Lira'), + 'USD' => t('USD - US Dollar'), + 'VBL' => t('VES - Venezuelan Bolívar'), + 'XBT' => t('XBT - Bitcoin'), + ); + } + + /** + * Get all currency rates + * + * @access public + * @return array + */ + public function getAll() + { + return $this->db->table(self::TABLE)->findAll(); + } + + /** + * Calculate the price for the reference currency + * + * @access public + * @param string $currency + * @param double $price + * @return double + */ + public function getPrice($currency, $price) + { + static $rates = null; + $reference = $this->configModel->get('application_currency', 'USD'); + + if ($reference !== $currency) { + $rates = $rates === null ? $this->db->hashtable(self::TABLE)->getAll('currency', 'rate') : $rates; + $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)->exists()) { + return $this->update($currency, $rate); + } + + return $this->db->table(self::TABLE)->insert(array('currency' => $currency, 'rate' => $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)); + } +} diff --git a/app/Model/CustomFilterModel.php b/app/Model/CustomFilterModel.php new file mode 100644 index 0000000..a9c5aae --- /dev/null +++ b/app/Model/CustomFilterModel.php @@ -0,0 +1,141 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Custom Filter model + * + * @package Kanboard\Model + * @author Timo Litzbarski + */ +class CustomFilterModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'custom_filters'; + + /** + * Return the list of all allowed custom filters for a user and project + * + * @access public + * @param integer $project_id Project id + * @param integer $user_id User id + * @return array + */ + public function getAll($project_id, $user_id) + { + return $this->db + ->table(self::TABLE) + ->columns( + UserModel::TABLE.'.name as owner_name', + UserModel::TABLE.'.username as owner_username', + self::TABLE.'.id', + self::TABLE.'.user_id', + self::TABLE.'.project_id', + self::TABLE.'.filter', + self::TABLE.'.name', + self::TABLE.'.is_shared', + self::TABLE.'.append' + ) + ->asc(self::TABLE.'.name') + ->join(UserModel::TABLE, 'id', 'user_id') + ->beginOr() + ->eq('is_shared', 1) + ->eq('user_id', $user_id) + ->closeOr() + ->eq('project_id', $project_id) + ->findAll(); + } + + /** + * Get custom filter by id + * + * @access private + * @param integer $filter_id + * @return array + */ + public function getById($filter_id) + { + return $this->db->table(self::TABLE)->eq('id', $filter_id)->findOne(); + } + + /** + * Create a custom filter + * + * @access public + * @param array $values Form values + * @return bool|integer + */ + public function create(array $values) + { + return $this->db->table(self::TABLE)->persist($values); + } + + /** + * Update a custom filter + * + * @access public + * @param array $values Form values + * @return bool + */ + public function update(array $values) + { + $updates = $values; + unset($updates['id']); + return $this->db->table(self::TABLE) + ->eq('id', $values['id']) + ->update($updates); + } + + /** + * Remove a custom filter + * + * @access public + * @param integer $filter_id + * @return bool + */ + public function remove($filter_id) + { + return $this->db->table(self::TABLE)->eq('id', $filter_id)->remove(); + } + + /** + * Duplicate custom filters from a project to another one, must be executed inside a transaction + * + * @param integer $src_project_id Source project id + * @param integer $dst_project_id Destination project id + * @return boolean + */ + public function duplicate($src_project_id, $dst_project_id) + { + $filters = $this->db + ->table(self::TABLE) + ->columns( + self::TABLE.'.user_id', + self::TABLE.'.filter', + self::TABLE.'.name', + self::TABLE.'.is_shared', + self::TABLE.'.append' + ) + ->eq('project_id', $src_project_id) + ->findAll(); + + foreach ($filters as $filter) { + $filter['project_id'] = $dst_project_id; + // Avoid SQL error with Postgres + $filter['is_shared'] = $filter['is_shared'] ?: 0; + $filter['append'] = $filter['append'] ?: 0; + + if (! $this->db->table(self::TABLE)->save($filter)) { + return false; + } + } + + return true; + } +} diff --git a/app/Model/FileModel.php b/app/Model/FileModel.php new file mode 100644 index 0000000..122728c --- /dev/null +++ b/app/Model/FileModel.php @@ -0,0 +1,416 @@ +<?php + +namespace Kanboard\Model; + +use Exception; +use Kanboard\Core\Base; +use Kanboard\Core\Thumbnail; +use Kanboard\Core\ObjectStorage\ObjectStorageException; + +/** + * Base File Model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +abstract class FileModel extends Base +{ + /** + * Get the table + * + * @abstract + * @access protected + * @return string + */ + abstract protected function getTable(); + + /** + * Define the foreign key + * + * @abstract + * @access protected + * @return string + */ + abstract protected function getForeignKey(); + + /** + * Get the path prefix + * + * @abstract + * @access protected + * @return string + */ + abstract protected function getPathPrefix(); + + /** + * Fire file creation event + * + * @abstract + * @access protected + * @param integer $file_id + */ + abstract protected function fireCreationEvent($file_id); + + /** + * Fire file destruction event + * + * @abstract + * @access protected + * @param integer $file_id + */ + abstract protected function fireDestructionEvent($file_id); + + /** + * Get PicoDb query to get all files + * + * @access protected + * @return \PicoDb\Table + */ + protected function getQuery() + { + return $this->db + ->table($this->getTable()) + ->columns( + $this->getTable().'.id', + $this->getTable().'.name', + $this->getTable().'.path', + $this->getTable().'.is_image', + $this->getTable().'.'.$this->getForeignKey(), + $this->getTable().'.date', + $this->getTable().'.user_id', + $this->getTable().'.size', + UserModel::TABLE.'.username', + UserModel::TABLE.'.name as user_name' + ) + ->join(UserModel::TABLE, 'id', 'user_id') + ->asc($this->getTable().'.name'); + } + + /** + * Get a file by the id + * + * @access public + * @param integer $file_id File id + * @return array + */ + public function getById($file_id) + { + $file = $this->db->table($this->getTable())->eq('id', $file_id)->findOne(); + if ($file) { + $file['etag'] = md5($file['path']); + } + return $file; + } + + /** + * Get all files + * + * @access public + * @param integer $id + * @return array + */ + public function getAll($id) + { + $files = $this->getQuery()->eq($this->getForeignKey(), $id)->findAll(); + foreach ($files as &$file) { + $file['etag'] = md5($file['path']); + } + return $files; + } + + /** + * Get all images + * + * @access public + * @param integer $id + * @return array + */ + public function getAllImages($id) + { + $images = $this->getQuery()->eq($this->getForeignKey(), $id)->eq('is_image', 1)->findAll(); + foreach ($images as &$image) { + $image['etag'] = md5($image['path']); + } + return $images; + } + + /** + * Get all files without images + * + * @access public + * @param integer $id + * @return array + */ + public function getAllDocuments($id) + { + $files = $this->getQuery()->eq($this->getForeignKey(), $id)->eq('is_image', 0)->findAll(); + foreach ($files as &$file) { + $file['etag'] = md5($file['path']); + } + return $files; + } + + /** + * Create a file entry in the database + * + * @access public + * @param integer $foreign_key_id Foreign key + * @param string $name Filename + * @param string $path Path on the disk + * @param integer $size File size + * @return bool|integer + */ + public function create($foreign_key_id, $name, $path, $size) + { + $values = array( + $this->getForeignKey() => $foreign_key_id, + 'name' => substr($name, 0, 255), + 'path' => $path, + 'is_image' => $this->isImage($name) ? 1 : 0, + 'size' => $size, + 'user_id' => $this->userSession->getId() ?: 0, + 'date' => time(), + ); + + $result = $this->db->table($this->getTable())->insert($values); + + if ($result) { + $file_id = (int) $this->db->getLastId(); + $this->fireCreationEvent($file_id); + return $file_id; + } + + return false; + } + + /** + * Remove all files + * + * @access public + * @param integer $id + * @return bool + */ + public function removeAll($id) + { + $file_ids = $this->db->table($this->getTable())->eq($this->getForeignKey(), $id)->asc('id')->findAllByColumn('id'); + $results = array(); + + foreach ($file_ids as $file_id) { + $results[] = $this->remove($file_id); + } + + return ! in_array(false, $results, true); + } + + /** + * Remove a file + * + * @access public + * @param integer $file_id File id + * @return bool + */ + public function remove($file_id) + { + try { + $this->fireDestructionEvent($file_id); + + $file = $this->getById($file_id); + + // Only remove files from disk attached to a single task. + $multiple_tasks_count = $this->db->table($this->getTable())->eq('path', $file['path'])->count(); + if ($multiple_tasks_count === 1) { + $this->objectStorage->remove($file['path']); + + if ($file['is_image'] == 1) { + $this->objectStorage->remove($this->getThumbnailPath($file['path'])); + } + } + + return $this->db->table($this->getTable())->eq('id', $file['id'])->remove(); + } catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); + return false; + } + } + + /** + * Check if a filename is an image (file types that can be shown as thumbnail) + * + * @access public + * @param string $filename Filename + * @return bool + */ + public function isImage($filename) + { + switch (get_file_extension($filename)) { + case 'jpeg': + case 'jpg': + case 'png': + case 'gif': + return true; + } + + return false; + } + + /** + * Generate the path for a thumbnails + * + * @access public + * @param string $key Storage key + * @return string + */ + public function getThumbnailPath($key) + { + return 'thumbnails'.DIRECTORY_SEPARATOR.$key; + } + + /** + * Generate the path for a new filename + * + * @access public + * @param integer $id Foreign key + * @param string $filename Filename + * @return string + */ + public function generatePath($id, $filename) + { + if (is_string($id)) { + $id = (int) $id; + } + if (! is_int($id) || $id <= 0) { + throw new Exception('Invalid ID provided for file path generation'); + } + return $this->getPathPrefix().DIRECTORY_SEPARATOR.$id.DIRECTORY_SEPARATOR.hash('sha1', $filename.time()); + } + + /** + * Upload multiple files + * + * @access public + * @param integer $id + * @param array $files + * @return bool + */ + public function uploadFiles($id, array $files) + { + try { + if (empty($files)) { + return false; + } + + foreach (array_keys($files['error']) as $key) { + $file = array( + 'name' => $files['name'][$key], + 'tmp_name' => $files['tmp_name'][$key], + 'size' => $files['size'][$key], + 'error' => $files['error'][$key], + ); + + $this->uploadFile($id, $file); + } + + return true; + } catch (Exception $e) { + $this->logger->error($e->getMessage()); + return false; + } + } + + /** + * Upload a file + * + * @access public + * @param integer $id + * @param array $file + * @throws Exception + */ + public function uploadFile($id, array $file) + { + if ($file['error'] == UPLOAD_ERR_OK && $file['size'] > 0) { + $destination_filename = $this->generatePath($id, $file['name']); + + if ($this->isImage($file['name'])) { + $this->generateThumbnailFromFile($file['tmp_name'], $destination_filename); + } + + $this->objectStorage->moveUploadedFile($file['tmp_name'], $destination_filename); + $this->create($id, $file['name'], $destination_filename, $file['size']); + } else { + throw new Exception('File not uploaded: '.var_export($file['error'], true)); + } + } + + /** + * Handle file upload (base64 encoded content) + * + * @access public + * @param integer $id + * @param string $originalFilename + * @param string $data + * @param bool $isEncoded + * @return bool|int + */ + public function uploadContent($id, $originalFilename, $data, $isEncoded = true) + { + try { + if ($isEncoded) { + $data = base64_decode($data); + } + + if (empty($data)) { + $this->logger->error(__METHOD__.': Content upload with no data'); + return false; + } + + $destinationFilename = $this->generatePath($id, $originalFilename); + $this->objectStorage->put($destinationFilename, $data); + + if ($this->isImage($originalFilename)) { + $this->generateThumbnailFromData($destinationFilename, $data); + } + + return $this->create( + $id, + $originalFilename, + $destinationFilename, + strlen($data) + ); + } catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); + return false; + } + } + + /** + * Generate thumbnail from a blob + * + * @access public + * @param string $destination_filename + * @param string $data + */ + public function generateThumbnailFromData($destination_filename, &$data) + { + $blob = Thumbnail::createFromString($data) + ->resize() + ->toString(); + + $this->objectStorage->put($this->getThumbnailPath($destination_filename), $blob); + } + + /** + * Generate thumbnail from a local file + * + * @access public + * @param string $uploaded_filename + * @param string $destination_filename + */ + public function generateThumbnailFromFile($uploaded_filename, $destination_filename) + { + $blob = Thumbnail::createFromFile($uploaded_filename) + ->resize() + ->toString(); + + $this->objectStorage->put($this->getThumbnailPath($destination_filename), $blob); + } +} diff --git a/app/Model/GroupMemberModel.php b/app/Model/GroupMemberModel.php new file mode 100644 index 0000000..ff24777 --- /dev/null +++ b/app/Model/GroupMemberModel.php @@ -0,0 +1,131 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Group Member Model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class GroupMemberModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'group_has_users'; + + /** + * Get query to fetch all users + * + * @access public + * @param integer $group_id + * @return \PicoDb\Table + */ + public function getQuery($group_id) + { + return $this->db->table(self::TABLE) + ->join(UserModel::TABLE, 'id', 'user_id') + ->eq('group_id', $group_id); + } + + /** + * Get all users + * + * @access public + * @param integer $group_id + * @return array + */ + public function getMembers($group_id) + { + return $this->getQuery($group_id)->findAll(); + } + + /** + * Get all not members + * + * @access public + * @param integer $group_id + * @return array + */ + public function getNotMembers($group_id) + { + $subquery = $this->db->table(self::TABLE) + ->columns('user_id') + ->eq('group_id', $group_id); + + return $this->db->table(UserModel::TABLE) + ->notInSubquery('id', $subquery) + ->eq('is_active', 1) + ->findAll(); + } + + /** + * Add user to a group + * + * @access public + * @param integer $group_id + * @param integer $user_id + * @return boolean + */ + public function addUser($group_id, $user_id) + { + return $this->db->table(self::TABLE)->insert(array( + 'group_id' => $group_id, + 'user_id' => $user_id, + )); + } + + /** + * Remove user from a group + * + * @access public + * @param integer $group_id + * @param integer $user_id + * @return boolean + */ + public function removeUser($group_id, $user_id) + { + return $this->db->table(self::TABLE) + ->eq('group_id', $group_id) + ->eq('user_id', $user_id) + ->remove(); + } + + /** + * Check if a user is member + * + * @access public + * @param integer $group_id + * @param integer $user_id + * @return boolean + */ + public function isMember($group_id, $user_id) + { + return $this->db->table(self::TABLE) + ->eq('group_id', $group_id) + ->eq('user_id', $user_id) + ->exists(); + } + + /** + * Get all groups for a given user + * + * @access public + * @param integer $user_id + * @return array + */ + public function getGroups($user_id) + { + return $this->db->table(self::TABLE) + ->columns(GroupModel::TABLE.'.id', GroupModel::TABLE.'.external_id', GroupModel::TABLE.'.name') + ->join(GroupModel::TABLE, 'id', 'group_id') + ->eq(self::TABLE.'.user_id', $user_id) + ->asc(GroupModel::TABLE.'.name') + ->findAll(); + } +} diff --git a/app/Model/GroupModel.php b/app/Model/GroupModel.php new file mode 100644 index 0000000..6fd9c53 --- /dev/null +++ b/app/Model/GroupModel.php @@ -0,0 +1,158 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Group Model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class GroupModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'groups'; + + /** + * Get query to fetch all groups + * + * @access public + * @return \PicoDb\Table + */ + public function getQuery() + { + return $this->db->table(self::TABLE) + ->columns('id', 'name', 'external_id') + ->subquery('SELECT COUNT(*) FROM '.GroupMemberModel::TABLE.' WHERE group_id='.self::TABLE.'.id', 'nb_users'); + } + + /** + * Get a specific group by id + * + * @access public + * @param integer $group_id + * @return array + */ + public function getById($group_id) + { + return $this->db->table(self::TABLE)->eq('id', $group_id)->findOne(); + } + + /** + * Get a specific group by externalID + * + * @access public + * @param string $external_id + * @return array + */ + public function getByExternalId($external_id) + { + return $this->db->table(self::TABLE)->eq('external_id', $external_id)->findOne(); + } + + /** + * Get specific groups by externalIDs + * + * @access public + * @param string[] $external_ids + * @return array + */ + public function getByExternalIds(array $external_ids) + { + if (empty($external_ids)) { + return []; + } + + return $this->db->table(self::TABLE)->in('external_id', $external_ids)->findAll(); + } + + /** + * Get all groups + * + * @access public + * @return array + */ + public function getAll() + { + return $this->getQuery()->asc('name')->findAll(); + } + + /** + * Search groups by name + * + * @access public + * @param string $input + * @return array + */ + public function search($input) + { + return $this->db->table(self::TABLE)->ilike('name', '%'.$input.'%')->asc('name')->findAll(); + } + + /** + * Remove a group + * + * @access public + * @param integer $group_id + * @return boolean + */ + public function remove($group_id) + { + return $this->db->table(self::TABLE)->eq('id', $group_id)->remove(); + } + + /** + * Create a new group + * + * @access public + * @param string $name + * @param string $external_id + * @return integer|boolean + */ + public function create($name, $external_id = '') + { + return $this->db->table(self::TABLE)->persist(array( + 'name' => $name, + 'external_id' => $external_id, + )); + } + + /** + * Update existing group + * + * @access public + * @param array $values + * @return boolean + */ + public function update(array $values) + { + $updates = $values; + unset($updates['id']); + return $this->db->table(self::TABLE)->eq('id', $values['id'])->update($updates); + } + + /** + * Get groupId from externalGroupId and create the group if not found + * + * @access public + * @param string $name + * @param string $external_id + * @return bool|integer + */ + public function getOrCreateExternalGroupId($name, $external_id) + { + $group_id = $this->db->table(self::TABLE)->eq('external_id', $external_id)->findOneColumn('id'); + + if (empty($group_id)) { + $group_id = $this->create($name, $external_id); + } + + return $group_id; + } +} diff --git a/app/Model/InviteModel.php b/app/Model/InviteModel.php new file mode 100644 index 0000000..13d75f6 --- /dev/null +++ b/app/Model/InviteModel.php @@ -0,0 +1,73 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; +use Kanboard\Core\Security\Token; + +/** + * Class InviteModel + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class InviteModel extends Base +{ + const TABLE = 'invites'; + + public function createInvites(array $emails, $projectId) + { + $emails = array_unique($emails); + $nb = 0; + + foreach ($emails as $email) { + $email = trim($email); + + if (! empty($email) && $this->createInvite($email, $projectId)) { + $nb++; + } + } + + return $nb; + } + + protected function createInvite($email, $projectId) + { + $values = array( + 'email' => $email, + 'project_id' => $projectId, + 'token' => Token::getToken(), + ); + + if ($this->db->table(self::TABLE)->insert($values)) { + $this->sendInvite($values); + return true; + } + + return false; + } + + protected function sendInvite(array $values) + { + $this->emailClient->send( + $values['email'], + $values['email'], + e('Kanboard Invitation'), + $this->template->render('user_invite/email', array('token' => $values['token'])) + ); + } + + public function getByToken($token) + { + return $this->db->table(self::TABLE) + ->eq('token', $token) + ->findOne(); + } + + public function remove($email) + { + return $this->db->table(self::TABLE) + ->eq('email', $email) + ->remove(); + } +} diff --git a/app/Model/LanguageModel.php b/app/Model/LanguageModel.php new file mode 100644 index 0000000..74c900b --- /dev/null +++ b/app/Model/LanguageModel.php @@ -0,0 +1,237 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; +use Kanboard\Core\Translator; + +/** + * Class Language + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class LanguageModel extends Base +{ + /** + * Get all language codes + * + * @static + * @access public + * @return string[] + */ + public static function getCodes() + { + return array( + 'id_ID', + 'bg_BG', + 'bs_BA', + 'ca_ES', + 'cs_CZ', + 'da_DK', + 'de_DE', + 'de_DE_du', + 'en_GB', + 'en_US', + 'es_ES', + 'es_VE', + 'fr_FR', + 'el_GR', + 'it_IT', + 'hr_HR', + 'hu_HU', + 'mk_MK', + 'my_MY', + 'nl_NL', + 'nb_NO', + 'pl_PL', + 'pt_PT', + 'pt_BR', + 'ro_RO', + 'ru_RU', + 'sr_Latn_RS', + 'fi_FI', + 'sk_SK', + 'sv_SE', + 'tr_TR', + 'uk_UA', + 'ko_KR', + 'zh_CN', + 'zh_TW', + 'ja_JP', + 'th_TH', + 'vi_VN', + 'fa_IR', + 'ar_SY', + ); + } + + /** + * Find language code + * + * @static + * @access public + * @param string $code + * @return string + */ + public static function findCode($code) + { + $code = str_replace('-', '_', $code); + return in_array($code, self::getCodes()) ? $code : ''; + } + + /** + * Get available languages + * + * @access public + * @param boolean $prepend Prepend a default value + * @return array + */ + public function getLanguages($prepend = false) + { + // Sorted by value + $languages = array( + 'id_ID' => 'Bahasa Indonesia', + 'bg_BG' => 'Български', + 'bs_BA' => 'Bosanski', + 'ca_ES' => 'Català', + 'cs_CZ' => 'Čeština', + 'da_DK' => 'Dansk', + 'de_DE' => 'Deutsch (Sie)', + 'de_DE_du' => 'Deutsch (du)', + 'en_GB' => 'English (GB)', + 'en_US' => 'English (US)', + 'es_ES' => 'Español (España)', + 'es_VE' => 'Español (Venezuela)', + 'fr_FR' => 'Français', + 'el_GR' => 'Greek (Ελληνικά)', + 'hr_HR' => 'Hrvatski', + 'it_IT' => 'Italiano', + 'hu_HU' => 'Magyar', + 'mk_MK' => 'Македонски', + 'my_MY' => 'Melayu', + 'nl_NL' => 'Nederlands', + 'nb_NO' => 'Norsk', + 'pl_PL' => 'Polski', + 'pt_PT' => 'Português', + 'pt_BR' => 'Português (Brasil)', + 'ro_RO' => 'Română', + 'ru_RU' => 'Русский', + 'sr_Latn_RS' => 'Srpski', + 'fi_FI' => 'Suomi', + 'sk_SK' => 'Slovenčina', + 'sv_SE' => 'Svenska', + 'tr_TR' => 'Türkçe', + 'uk_UA' => 'Українська', + 'ko_KR' => '한국어', + 'zh_CN' => '中文(简体)', + 'zh_TW' => '中文(繁體)', + 'ja_JP' => '日本語', + 'th_TH' => 'ไทย', + 'vi_VN' => 'Tiếng Việt', + 'fa_IR' => 'فارسی', + 'ar_SY' => 'عربي', + ); + + if ($prepend) { + return array('' => t('Application default')) + $languages; + } + + return $languages; + } + + /** + * Get javascript language code + * + * @access public + * @return string + */ + public function getJsLanguageCode() + { + $languages = array( + 'bg_BG' => 'bg', + 'cs_CZ' => 'cs', + 'ca_ES' => 'ca', + 'da_DK' => 'da', + 'de_DE' => 'de', + 'de_DE_du' => 'de', + 'en_GB' => 'en-GB', + 'en_US' => 'en', + 'es_ES' => 'es', + 'es_VE' => 'es', + 'fr_FR' => 'fr', + 'it_IT' => 'it', + 'hr_HR' => 'hr', + 'hu_HU' => 'hu', + 'nl_NL' => 'nl', + 'nb_NO' => 'no', + 'pl_PL' => 'pl', + 'pt_PT' => 'pt', + 'pt_BR' => 'pt-BR', + 'ro_RO' => 'ro', + 'ru_RU' => 'ru', + 'sr_Latn_RS' => 'sr', + 'fi_FI' => 'fi', + 'sk_SK' => 'sk', + 'sv_SE' => 'sv', + 'tr_TR' => 'tr', + 'uk_UA' => 'uk', + 'ko_KR' => 'ko', + 'zh_CN' => 'zh-CN', + 'zh_TW' => 'zh-TW', + 'ja_JP' => 'ja', + 'th_TH' => 'th', + 'id_ID' => 'id', + 'el_GR' => 'el', + 'fa_IR' => 'fa', + 'vi_VN' => 'vi', + 'bs_BA' => 'bs', + 'mk_MK' => 'mk', + 'my_MY' => 'my', + 'ar_SY' => 'ar', + ); + + $lang = $this->getCurrentLanguage(); + + return isset($languages[$lang]) ? $languages[$lang] : 'en'; + } + + /** + * Check if current language requires RTL direction + * + * @access public + * @return bool + */ + public function isRtlLanguage() + { + $rtlJsLanguageCodes = array( + 'ar', + 'fa', + ); + + $lang = $this->getJsLanguageCode(); + + return in_array($lang, $rtlJsLanguageCodes); + } + + /** + * Get current language + * + * @access public + * @return string + */ + public function getCurrentLanguage() + { + return $this->userSession->getLanguage() ?: $this->configModel->get('application_language', 'en_US'); + } + + /** + * Load translations for the current language + * + * @access public + */ + public function loadCurrentLanguage() + { + Translator::load($this->getCurrentLanguage()); + } +} diff --git a/app/Model/LastLoginModel.php b/app/Model/LastLoginModel.php new file mode 100644 index 0000000..caaa4a8 --- /dev/null +++ b/app/Model/LastLoginModel.php @@ -0,0 +1,92 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * LastLogin model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class LastLoginModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'last_logins'; + + /** + * Number of connections to keep for history + * + * @var integer + */ + const NB_LOGINS = 10; + + /** + * Create a new record + * + * @access public + * @param string $auth_type Authentication method + * @param integer $user_id User id + * @param string $ip IP Address + * @param string $user_agent User Agent + * @return boolean + */ + public function create($auth_type, $user_id, $ip, $user_agent) + { + $this->cleanup($user_id); + + return $this->db + ->table(self::TABLE) + ->insert(array( + 'auth_type' => $auth_type, + 'user_id' => $user_id, + 'ip' => $ip, + 'user_agent' => substr($user_agent, 0, 255), + 'date_creation' => time(), + )); + } + + /** + * Cleanup login history + * + * @access public + * @param integer $user_id + */ + public function cleanup($user_id) + { + $connections = $this->db + ->table(self::TABLE) + ->eq('user_id', $user_id) + ->desc('id') + ->findAllByColumn('id'); + + if (count($connections) >= self::NB_LOGINS) { + $this->db->table(self::TABLE) + ->eq('user_id', $user_id) + ->notIn('id', array_slice($connections, 0, self::NB_LOGINS - 1)) + ->remove(); + } + } + + /** + * Get the last connections for a given user + * + * @access public + * @param integer $user_id User id + * @return array + */ + public function getAll($user_id) + { + return $this->db + ->table(self::TABLE) + ->eq('user_id', $user_id) + ->desc('id') + ->columns('id', 'auth_type', 'ip', 'user_agent', 'date_creation') + ->findAll(); + } +} diff --git a/app/Model/LinkModel.php b/app/Model/LinkModel.php new file mode 100644 index 0000000..b72c753 --- /dev/null +++ b/app/Model/LinkModel.php @@ -0,0 +1,178 @@ +<?php + +namespace Kanboard\Model; + +use PDO; +use Kanboard\Core\Base; + +/** + * Link model + * + * @package Kanboard\Model + * @author Olivier Maridat + * @author Frederic Guillot + */ +class LinkModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'links'; + + /** + * Get a link by id + * + * @access public + * @param integer $link_id Link id + * @return array + */ + public function getById($link_id) + { + return $this->db->table(self::TABLE)->eq('id', $link_id)->findOne(); + } + + /** + * Get a link by name + * + * @access public + * @param string $label + * @return array + */ + public function getByLabel($label) + { + return $this->db->table(self::TABLE)->eq('label', $label)->findOne(); + } + + /** + * Get the opposite link id + * + * @access public + * @param integer $link_id Link id + * @return integer + */ + public function getOppositeLinkId($link_id) + { + return $this->db->table(self::TABLE)->eq('id', $link_id)->findOneColumn('opposite_id') ?: $link_id; + } + + /** + * Get all links + * + * @access public + * @return array + */ + public function getAll() + { + return $this->db->table(self::TABLE)->findAll(); + } + + /** + * Get merged links + * + * @access public + * @return array + */ + public function getMergedList() + { + return $this->db + ->execute(' + SELECT + links.id, links.label, opposite.label as opposite_label + FROM links + LEFT JOIN links AS opposite ON opposite.id=links.opposite_id + ') + ->fetchAll(PDO::FETCH_ASSOC); + } + + /** + * Get label list + * + * @access public + * @param integer $exclude_id Exclude this link + * @param boolean $prepend Prepend default value + * @return array + */ + public function getList($exclude_id = 0, $prepend = true) + { + $labels = $this->db->hashtable(self::TABLE)->neq('id', $exclude_id)->asc('id')->getAll('id', 'label'); + + foreach ($labels as &$value) { + $value = t($value); + } + + return $prepend ? array('') + $labels : $labels; + } + + /** + * Create a new link label + * + * @access public + * @param string $label + * @param string $opposite_label + * @return boolean|integer + */ + public function create($label, $opposite_label = '') + { + $this->db->startTransaction(); + + if (! $this->db->table(self::TABLE)->insert(array('label' => $label))) { + $this->db->cancelTransaction(); + return false; + } + + $label_id = $this->db->getLastId(); + + if (! empty($opposite_label)) { + $this->db + ->table(self::TABLE) + ->insert(array( + 'label' => $opposite_label, + 'opposite_id' => $label_id, + )); + + $this->db + ->table(self::TABLE) + ->eq('id', $label_id) + ->update(array( + 'opposite_id' => $this->db->getLastId() + )); + } + + $this->db->closeTransaction(); + + return (int) $label_id; + } + + /** + * Update a link + * + * @access public + * @param array $values + * @return boolean + */ + public function update(array $values) + { + return $this->db + ->table(self::TABLE) + ->eq('id', $values['id']) + ->update(array( + 'label' => $values['label'], + 'opposite_id' => $values['opposite_id'], + )); + } + + /** + * Remove a link a the relation to its opposite + * + * @access public + * @param integer $link_id + * @return boolean + */ + public function remove($link_id) + { + $this->db->table(self::TABLE)->eq('opposite_id', $link_id)->update(array('opposite_id' => 0)); + return $this->db->table(self::TABLE)->eq('id', $link_id)->remove(); + } +} diff --git a/app/Model/MetadataModel.php b/app/Model/MetadataModel.php new file mode 100644 index 0000000..0ab9409 --- /dev/null +++ b/app/Model/MetadataModel.php @@ -0,0 +1,140 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Metadata + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +abstract class MetadataModel extends Base +{ + /** + * Get the table + * + * @abstract + * @access protected + * @return string + */ + abstract protected function getTable(); + + /** + * Define the entity key + * + * @abstract + * @access protected + * @return string + */ + abstract protected function getEntityKey(); + + /** + * Get all metadata for the entity + * + * @access public + * @param integer $entity_id + * @return array + */ + public function getAll($entity_id) + { + return $this->db + ->hashtable($this->getTable()) + ->eq($this->getEntityKey(), $entity_id) + ->asc('name') + ->getAll('name', 'value'); + } + + /** + * Get a metadata for the given entity + * + * @access public + * @param integer $entity_id + * @param string $name + * @param string $default + * @return mixed + */ + public function get($entity_id, $name, $default = '') + { + return $this->db + ->table($this->getTable()) + ->eq($this->getEntityKey(), $entity_id) + ->eq('name', $name) + ->findOneColumn('value') ?: $default; + } + + /** + * Return true if a metadata exists + * + * @access public + * @param integer $entity_id + * @param string $name + * @return boolean + */ + public function exists($entity_id, $name) + { + return $this->db + ->table($this->getTable()) + ->eq($this->getEntityKey(), $entity_id) + ->eq('name', $name) + ->exists(); + } + + /** + * Update or insert new metadata + * + * @access public + * @param integer $entity_id + * @param array $values + * @return boolean + */ + public function save($entity_id, array $values) + { + $results = array(); + $user_id = $this->userSession->getId(); + $timestamp = time(); + + $this->db->startTransaction(); + + foreach ($values as $key => $value) { + if ($this->exists($entity_id, $key)) { + $results[] = $this->db->table($this->getTable()) + ->eq($this->getEntityKey(), $entity_id) + ->eq('name', $key) + ->update(array( + 'value' => $value, + 'changed_on' => $timestamp, + 'changed_by' => $user_id, + )); + } else { + $results[] = $this->db->table($this->getTable())->insert(array( + 'name' => $key, + 'value' => $value, + $this->getEntityKey() => $entity_id, + 'changed_on' => $timestamp, + 'changed_by' => $user_id, + )); + } + } + + $this->db->closeTransaction(); + return ! in_array(false, $results, true); + } + + /** + * Remove a metadata + * + * @access public + * @param integer $entity_id + * @param string $name + * @return bool + */ + public function remove($entity_id, $name) + { + return $this->db->table($this->getTable()) + ->eq($this->getEntityKey(), $entity_id) + ->eq('name', $name) + ->remove(); + } +} diff --git a/app/Model/NotificationModel.php b/app/Model/NotificationModel.php new file mode 100644 index 0000000..803d4f1 --- /dev/null +++ b/app/Model/NotificationModel.php @@ -0,0 +1,100 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; +use Kanboard\EventBuilder\CommentEventBuilder; +use Kanboard\EventBuilder\EventIteratorBuilder; +use Kanboard\EventBuilder\SubtaskEventBuilder; +use Kanboard\EventBuilder\TaskEventBuilder; +use Kanboard\EventBuilder\TaskFileEventBuilder; +use Kanboard\EventBuilder\TaskLinkEventBuilder; + +/** + * Notification Model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class NotificationModel extends Base +{ + /** + * Get the event title with author + * + * @access public + * @param string $eventAuthor + * @param string $eventName + * @param array $eventData + * @return string + */ + public function getTitleWithAuthor($eventAuthor, $eventName, array $eventData) + { + foreach ($this->getIteratorBuilder() as $builder) { + $title = $builder->buildTitleWithAuthor($eventAuthor, $eventName, $eventData); + + if ($title !== '') { + return $title; + } + } + + return e('Notification'); + } + + /** + * Get the event title without author + * + * @access public + * @param string $eventName + * @param array $eventData + * @return string + */ + public function getTitleWithoutAuthor($eventName, array $eventData) + { + foreach ($this->getIteratorBuilder() as $builder) { + $title = $builder->buildTitleWithoutAuthor($eventName, $eventData); + + if ($title !== '') { + return $title; + } + } + + return e('Notification'); + } + + /** + * Get task id from event + * + * @access public + * @param string $eventName + * @param array $eventData + * @return integer + */ + public function getTaskIdFromEvent($eventName, array $eventData) + { + if ($eventName === TaskModel::EVENT_OVERDUE) { + return $eventData['tasks'][0]['id']; + } + + return isset($eventData['task']['id']) ? $eventData['task']['id'] : 0; + } + + /** + * Get iterator builder + * + * @access protected + * @return EventIteratorBuilder + */ + protected function getIteratorBuilder() + { + $iterator = new EventIteratorBuilder(); + $iterator + ->withBuilder(TaskEventBuilder::getInstance($this->container)) + ->withBuilder(CommentEventBuilder::getInstance($this->container)) + ->withBuilder(SubtaskEventBuilder::getInstance($this->container)) + ->withBuilder(TaskFileEventBuilder::getInstance($this->container)) + ->withBuilder(TaskLinkEventBuilder::getInstance($this->container)) + ; + + return $iterator; + } +} diff --git a/app/Model/NotificationTypeModel.php b/app/Model/NotificationTypeModel.php new file mode 100644 index 0000000..432832e --- /dev/null +++ b/app/Model/NotificationTypeModel.php @@ -0,0 +1,128 @@ +<?php + +namespace Kanboard\Model; + +use Pimple\Container; +use Kanboard\Core\Base; + +/** + * Notification Type + * + * @package model + * @author Frederic Guillot + */ +abstract class NotificationTypeModel extends Base +{ + /** + * Container + * + * @access private + * @var \Pimple\Container + */ + private $classes; + + /** + * Notification type labels + * + * @access private + * @var array + */ + private $labels = array(); + + /** + * Hidden notification types + * + * @access private + * @var array + */ + private $hiddens = array(); + + /** + * Constructor + * + * @access public + * @param \Pimple\Container $container + */ + public function __construct(Container $container) + { + parent::__construct($container); + $this->classes = new Container; + } + + /** + * Add a new notification type + * + * @access public + * @param string $type + * @param string $label + * @param string $class + * @param boolean $hidden + * @return NotificationTypeModel + */ + public function setType($type, $label, $class, $hidden = false) + { + $container = $this->container; + + if ($hidden) { + $this->hiddens[] = $type; + } else { + $this->labels[$type] = $label; + } + + $this->classes[$type] = function () use ($class, $container) { + return new $class($container); + }; + + return $this; + } + + /** + * Get mail notification type instance + * + * @access public + * @param string $type + * @return \Kanboard\Core\Notification\NotificationInterface + */ + public function getType($type) + { + return $this->classes[$type]; + } + + /** + * Get all notification types with labels + * + * @access public + * @return array + */ + public function getTypes() + { + return $this->labels; + } + + /** + * Get all hidden notification types + * + * @access public + * @return array + */ + public function getHiddenTypes() + { + return $this->hiddens; + } + + /** + * Keep only loaded notification types + * + * @access public + * @param string[] $types + * @return array + */ + public function filterTypes(array $types) + { + $classes = $this->classes; + + return array_filter($types, function ($type) use ($classes) { + return isset($classes[$type]); + }); + } +} diff --git a/app/Model/PasswordResetModel.php b/app/Model/PasswordResetModel.php new file mode 100644 index 0000000..d7c7496 --- /dev/null +++ b/app/Model/PasswordResetModel.php @@ -0,0 +1,95 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Password Reset Model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class PasswordResetModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'password_reset'; + + /** + * Token duration (30 minutes) + * + * @var integer + */ + const DURATION = 1800; + + /** + * Get all tokens + * + * @access public + * @param integer $user_id + * @return array + */ + public function getAll($user_id) + { + return $this->db->table(self::TABLE)->eq('user_id', $user_id)->desc('date_creation')->limit(100)->findAll(); + } + + /** + * Generate a new reset token for a user + * + * @access public + * @param string $username + * @param integer $expiration + * @return boolean|string + */ + public function create($username, $expiration = 0) + { + $user_id = $this->db->table(UserModel::TABLE)->eq('username', $username)->neq('email', '')->notNull('email')->findOneColumn('id'); + + if (! $user_id) { + return false; + } + + $token = $this->token->getToken(); + + $result = $this->db->table(self::TABLE)->insert(array( + 'token' => $token, + 'user_id' => $user_id, + 'date_expiration' => $expiration ?: time() + self::DURATION, + 'date_creation' => time(), + 'ip' => $this->request->getIpAddress(), + 'user_agent' => $this->request->getUserAgent(), + 'is_active' => 1, + )); + + return $result ? $token : false; + } + + /** + * Get user id from the token + * + * @access public + * @param string $token + * @return integer + */ + public function getUserIdByToken($token) + { + return $this->db->table(self::TABLE)->eq('token', $token)->eq('is_active', 1)->gte('date_expiration', time())->findOneColumn('user_id'); + } + + /** + * Disable all tokens for a user + * + * @access public + * @param integer $user_id + * @return boolean + */ + public function disable($user_id) + { + return $this->db->table(self::TABLE)->eq('user_id', $user_id)->update(array('is_active' => 0)); + } +} diff --git a/app/Model/PredefinedTaskDescriptionModel.php b/app/Model/PredefinedTaskDescriptionModel.php new file mode 100644 index 0000000..aaa4d23 --- /dev/null +++ b/app/Model/PredefinedTaskDescriptionModel.php @@ -0,0 +1,52 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +class PredefinedTaskDescriptionModel extends Base +{ + const TABLE = 'predefined_task_descriptions'; + + public function getAll($projectId) + { + return $this->db->table(self::TABLE)->eq('project_id', $projectId)->findAll(); + } + + public function getList($projectId) + { + return array('' => t('None')) + $this->db->hashtable(self::TABLE)->eq('project_id', $projectId)->getAll('id', 'title'); + } + + public function getById($projectId, $id) + { + return $this->db->table(self::TABLE)->eq('project_id', $projectId)->eq('id', $id)->findOne(); + } + + public function getDescriptionById($projectId, $id) + { + return $this->db->table(self::TABLE)->eq('project_id', $projectId)->eq('id', $id)->findOneColumn('description'); + } + + public function create($projectId, $title, $description) + { + return $this->db->table(self::TABLE)->persist(array( + 'project_id' => $projectId, + 'title' => $title, + 'description' => $description, + )); + } + + public function update($projectId, $id, $title, $description) + { + return $this->db->table(self::TABLE)->eq('project_id', $projectId)->eq('id', $id)->update(array( + 'title' => $title, + 'description' => $description, + )); + } + + public function remove($projectId, $id) + { + return $this->db->table(self::TABLE)->eq('project_id', $projectId)->eq('id', $id)->remove(); + } +} diff --git a/app/Model/ProjectActivityModel.php b/app/Model/ProjectActivityModel.php new file mode 100644 index 0000000..739fd78 --- /dev/null +++ b/app/Model/ProjectActivityModel.php @@ -0,0 +1,79 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; +use PicoDb\Table; + +/** + * Project activity model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class ProjectActivityModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'project_activities'; + + /** + * Add a new event for the project + * + * @access public + * @param integer $project_id Project id + * @param integer $task_id Task id + * @param integer $creator_id User id + * @param string $event_name Event name + * @param array $data Event data (will be serialized) + * @return boolean + */ + public function createEvent($project_id, $task_id, $creator_id, $event_name, array $data) + { + return $this->db->table(self::TABLE)->insert(array( + 'project_id' => $project_id, + 'task_id' => $task_id, + 'creator_id' => $creator_id, + 'event_name' => $event_name, + 'date_creation' => time(), + 'data' => json_encode($data), + )); + } + + /** + * Get query + * + * @access public + * @return Table + */ + public function getQuery() + { + return $this + ->db + ->table(ProjectActivityModel::TABLE) + ->columns( + ProjectActivityModel::TABLE.'.*', + 'uc.username AS author_username', + 'uc.name AS author_name', + 'uc.email', + 'uc.avatar_path' + ) + ->join(TaskModel::TABLE, 'id', 'task_id') + ->join(ProjectModel::TABLE, 'id', 'project_id') + ->left(UserModel::TABLE, 'uc', 'id', ProjectActivityModel::TABLE, 'creator_id'); + } + + /** + * Remove old event entries to avoid large table + * + * @access public + * @param integer $ts Timestamp + */ + public function cleanup($ts) + { + $this->db->table(self::TABLE)->lt('date_creation', $ts)->remove(); + } +} diff --git a/app/Model/ProjectDailyColumnStatsModel.php b/app/Model/ProjectDailyColumnStatsModel.php new file mode 100644 index 0000000..a0f14cf --- /dev/null +++ b/app/Model/ProjectDailyColumnStatsModel.php @@ -0,0 +1,254 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Project Daily Column Stats + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class ProjectDailyColumnStatsModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'project_daily_column_stats'; + + /** + * Update daily totals for the project and for each column + * + * "total" is the number open of tasks in the column + * "score" is the sum of tasks score in the column + * + * @access public + * @param integer $project_id Project id + * @param string $date Record date (YYYY-MM-DD) + * @return boolean + */ + public function updateTotals($project_id, $date) + { + $this->db->startTransaction(); + $this->db->table(self::TABLE)->eq('project_id', $project_id)->eq('day', $date)->remove(); + + foreach ($this->getStatsByColumns($project_id) as $column_id => $column) { + $this->db->table(self::TABLE)->insert(array( + 'day' => $date, + 'project_id' => $project_id, + 'column_id' => $column_id, + 'total' => $column['total'], + 'score' => $column['score'], + )); + } + + $this->db->closeTransaction(); + + return true; + } + + /** + * Count the number of recorded days for the data range + * + * @access public + * @param integer $project_id Project id + * @param string $from Start date (ISO format YYYY-MM-DD) + * @param string $to End date + * @return integer + */ + public function countDays($project_id, $from, $to) + { + return $this->db->table(self::TABLE) + ->eq('project_id', $project_id) + ->gte('day', $from) + ->lte('day', $to) + ->findOneColumn('COUNT(DISTINCT day)'); + } + + /** + * Get aggregated metrics for the project within a data range + * + * [ + * ['Date', 'Column1', 'Column2'], + * ['2014-11-16', 2, 5], + * ['2014-11-17', 20, 15], + * ] + * + * @access public + * @param integer $project_id Project id + * @param string $from Start date (ISO format YYYY-MM-DD) + * @param string $to End date + * @param string $field Column to aggregate + * @return array + */ + public function getAggregatedMetrics($project_id, $from, $to, $field = 'total') + { + $columns = $this->columnModel->getList($project_id); + $metrics = $this->getMetrics($project_id, $from, $to); + return $this->buildAggregate($metrics, $columns, $field); + } + + /** + * Fetch metrics + * + * @access public + * @param integer $project_id Project id + * @param string $from Start date (ISO format YYYY-MM-DD) + * @param string $to End date + * @return array + */ + public function getMetrics($project_id, $from, $to) + { + return $this->db->table(self::TABLE) + ->eq('project_id', $project_id) + ->gte('day', $from) + ->lte('day', $to) + ->asc(self::TABLE.'.day') + ->findAll(); + } + + /** + * Build aggregate + * + * @access private + * @param array $metrics + * @param array $columns + * @param string $field + * @return array + */ + private function buildAggregate(array &$metrics, array &$columns, $field) + { + $column_ids = array_keys($columns); + $days = array_unique(array_column($metrics, 'day')); + $rows = array(array_merge(array(e('Date')), array_values($columns))); + + foreach ($days as $day) { + $rows[] = $this->buildRowAggregate($metrics, $column_ids, $day, $field); + } + + return $rows; + } + + /** + * Build one row of the aggregate + * + * @access private + * @param array $metrics + * @param array $column_ids + * @param string $day + * @param string $field + * @return array + */ + private function buildRowAggregate(array &$metrics, array &$column_ids, $day, $field) + { + $row = array($day); + + foreach ($column_ids as $column_id) { + $row[] = $this->findValueInMetrics($metrics, $day, $column_id, $field); + } + + return $row; + } + + /** + * Find the value in the metrics + * + * @access private + * @param array $metrics + * @param string $day + * @param string $column_id + * @param string $field + * @return integer + */ + private function findValueInMetrics(array &$metrics, $day, $column_id, $field) + { + foreach ($metrics as $metric) { + if ($metric['day'] === $day && $metric['column_id'] == $column_id) { + return (int) $metric[$field]; + } + } + + return 0; + } + + /** + * Get number of tasks and score by columns + * + * @access private + * @param integer $project_id + * @return array + */ + private function getStatsByColumns($project_id) + { + $totals = $this->getTotalByColumns($project_id); + $scores = $this->getScoreByColumns($project_id); + $columns = array(); + + foreach ($totals as $column_id => $total) { + $columns[$column_id] = array('total' => $total, 'score' => 0); + } + + foreach ($scores as $column_id => $score) { + $columns[$column_id]['score'] = (int) $score; + } + + return $columns; + } + + /** + * Get number of tasks and score by columns + * + * @access private + * @param integer $project_id + * @return array + */ + private function getScoreByColumns($project_id) + { + $stats = $this->db->table(TaskModel::TABLE) + ->columns('column_id', 'SUM(score) AS score') + ->eq('project_id', $project_id) + ->eq('is_active', TaskModel::STATUS_OPEN) + ->notNull('score') + ->groupBy('column_id') + ->findAll(); + + return array_column($stats, 'score', 'column_id'); + } + + /** + * Get number of tasks and score by columns + * + * @access private + * @param integer $project_id + * @return array + */ + private function getTotalByColumns($project_id) + { + $stats = $this->db->table(TaskModel::TABLE) + ->columns('column_id', 'COUNT(*) AS total') + ->eq('project_id', $project_id) + ->in('is_active', $this->getTaskStatusConfig()) + ->groupBy('column_id') + ->findAll(); + + return array_column($stats, 'total', 'column_id'); + } + + /** + * Get task status to use for total calculation + * + * @access private + * @return array + */ + private function getTaskStatusConfig() + { + if ($this->configModel->get('cfd_include_closed_tasks') == 1) { + return array(TaskModel::STATUS_OPEN, TaskModel::STATUS_CLOSED); + } + + return array(TaskModel::STATUS_OPEN); + } +} diff --git a/app/Model/ProjectDailyStatsModel.php b/app/Model/ProjectDailyStatsModel.php new file mode 100644 index 0000000..0754d26 --- /dev/null +++ b/app/Model/ProjectDailyStatsModel.php @@ -0,0 +1,76 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Project Daily Stats + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class ProjectDailyStatsModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'project_daily_stats'; + + /** + * Update daily totals for the project + * + * @access public + * @param integer $project_id Project id + * @param string $date Record date (YYYY-MM-DD) + * @return boolean + */ + public function updateTotals($project_id, $date) + { + $this->db->startTransaction(); + + $lead_cycle_time = $this->averageLeadCycleTimeAnalytic->build($project_id); + + $this->db->table(self::TABLE)->eq('day', $date)->eq('project_id', $project_id)->remove(); + + $this->db->table(self::TABLE)->insert(array( + 'day' => $date, + 'project_id' => $project_id, + 'avg_lead_time' => $lead_cycle_time['avg_lead_time'], + 'avg_cycle_time' => $lead_cycle_time['avg_cycle_time'], + )); + + $this->db->closeTransaction(); + + return true; + } + + /** + * Get raw metrics for the project within a data range + * + * @access public + * @param integer $project_id Project id + * @param string $from Start date (ISO format YYYY-MM-DD) + * @param string $to End date + * @return array + */ + public function getRawMetrics($project_id, $from, $to) + { + $metrics = $this->db->table(self::TABLE) + ->columns('day', 'avg_lead_time', 'avg_cycle_time') + ->eq('project_id', $project_id) + ->gte('day', $from) + ->lte('day', $to) + ->asc('day') + ->findAll(); + + foreach ($metrics as &$metric) { + $metric['avg_lead_time'] = (int) $metric['avg_lead_time']; + $metric['avg_cycle_time'] = (int) $metric['avg_cycle_time']; + } + + return $metrics; + } +} diff --git a/app/Model/ProjectDuplicationModel.php b/app/Model/ProjectDuplicationModel.php new file mode 100644 index 0000000..a7de64f --- /dev/null +++ b/app/Model/ProjectDuplicationModel.php @@ -0,0 +1,190 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; +use Kanboard\Core\Security\Role; + +/** + * Project Duplication + * + * @package Kanboard\Model + * @author Frederic Guillot + * @author Antonio Rabelo + */ +class ProjectDuplicationModel extends Base +{ + /** + * Get list of optional models to duplicate + * + * @access public + * @return string[] + */ + public function getOptionalSelection() + { + return array( + 'categoryModel', + 'projectRoleModel', + 'projectPermissionModel', + 'actionModel', + 'tagDuplicationModel', + 'customFilterModel', + 'projectMetadataModel', + 'projectTaskDuplicationModel', + ); + } + + /** + * Get list of all possible models to duplicate + * + * @access public + * @return string[] + */ + public function getPossibleSelection() + { + return array( + 'swimlaneModel', + 'boardModel', + 'categoryModel', + 'projectRoleModel', + 'projectPermissionModel', + 'actionModel', + 'swimlaneModel', + 'tagDuplicationModel', + 'customFilterModel', + 'projectMetadataModel', + 'projectTaskDuplicationModel', + ); + } + + /** + * Get a valid project name for the duplication + * + * @access public + * @param string $name Project name + * @param integer $max_length Max length allowed + * @return string + */ + public function getClonedProjectName($name, $max_length = 50) + { + $suffix = ' ('.t('Clone').')'; + + if (strlen($name.$suffix) > $max_length) { + $name = substr($name, 0, $max_length - strlen($suffix)); + } + + return $name.$suffix; + } + + /** + * Clone a project with all settings + * + * @param integer $src_project_id Project Id + * @param array $selection Selection of optional project parts to duplicate + * @param integer $owner_id Owner of the project + * @param string $name Name of the project + * @param boolean $private Force the project to be private + * @param string $identifier Identifier of the project + * @return integer Cloned Project Id + */ + public function duplicate($src_project_id, $selection = array('projectPermissionModel', 'categoryModel', 'actionModel'), $owner_id = 0, $name = null, $private = null, $identifier = null) + { + $this->db->startTransaction(); + + // Get the cloned project Id + $dst_project_id = $this->copy($src_project_id, $owner_id, $name, $private, $identifier); + + if ($dst_project_id === false) { + $this->db->cancelTransaction(); + return false; + } + + // Clone Swimlanes, Columns, Categories, Permissions and Actions + foreach ($this->getPossibleSelection() as $model) { + + // Skip if optional part has not been selected + if (in_array($model, $this->getOptionalSelection()) && ! in_array($model, $selection)) { + continue; + } + + // Skip permissions for private projects + if ($private && $model === 'projectPermissionModel') { + continue; + } + + if (! $this->$model->duplicate($src_project_id, $dst_project_id)) { + $this->db->cancelTransaction(); + return false; + } + } + + if (! $this->makeOwnerManager($dst_project_id, $owner_id)) { + $this->db->cancelTransaction(); + return false; + } + + $this->db->closeTransaction(); + + return (int) $dst_project_id; + } + + /** + * Create a project from another one + * + * @access private + * @param integer $src_project_id + * @param integer $owner_id + * @param string $name + * @param boolean $private + * @param string $identifier + * @return integer + */ + private function copy($src_project_id, $owner_id = 0, $name = null, $private = null, $identifier = null) + { + $project = $this->projectModel->getById($src_project_id); + $is_private = empty($project['is_private']) ? 0 : 1; + + if (! empty($identifier)) { + $identifier = strtoupper($identifier); + } + + $values = array( + 'name' => $name ?: $this->getClonedProjectName($project['name']), + 'is_active' => 1, + 'last_modified' => time(), + 'token' => '', + 'is_public' => 0, + 'is_private' => $private ? 1 : $is_private, + 'owner_id' => $owner_id, + 'priority_default' => $project['priority_default'], + 'priority_start' => $project['priority_start'], + 'priority_end' => $project['priority_end'], + 'per_swimlane_task_limits' => empty($project['per_swimlane_task_limits']) ? 0 : 1, + 'task_limit' => $project['task_limit'], + 'identifier' => $identifier, + ); + + return $this->db->table(ProjectModel::TABLE)->persist($values); + } + + /** + * Make sure that the creator of the duplicated project is also owner + * + * @access private + * @param integer $dst_project_id + * @param integer $owner_id + * @return boolean + */ + private function makeOwnerManager($dst_project_id, $owner_id) + { + if ($owner_id > 0) { + $this->projectUserRoleModel->removeUser($dst_project_id, $owner_id); + + if (! $this->projectUserRoleModel->addUser($dst_project_id, $owner_id, Role::PROJECT_MANAGER)) { + return false; + } + } + + return true; + } +} diff --git a/app/Model/ProjectFileModel.php b/app/Model/ProjectFileModel.php new file mode 100644 index 0000000..7da5741 --- /dev/null +++ b/app/Model/ProjectFileModel.php @@ -0,0 +1,85 @@ +<?php + +namespace Kanboard\Model; + +/** + * Project File Model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class ProjectFileModel extends FileModel +{ + /** + * Table name + * + * @var string + */ + const TABLE = 'project_has_files'; + + /** + * Events + * + * @var string + */ + const EVENT_CREATE = 'project.file.create'; + const EVENT_DESTROY = 'project.file.destroy'; + + /** + * Get the table + * + * @abstract + * @access protected + * @return string + */ + protected function getTable() + { + return self::TABLE; + } + + /** + * Define the foreign key + * + * @abstract + * @access protected + * @return string + */ + protected function getForeignKey() + { + return 'project_id'; + } + + /** + * Define the path prefix + * + * @abstract + * @access protected + * @return string + */ + protected function getPathPrefix() + { + return 'projects'; + } + + /** + * Fire file creation event + * + * @access protected + * @param integer $file_id + */ + protected function fireCreationEvent($file_id) + { + $this->queueManager->push($this->projectFileEventJob->withParams($file_id, self::EVENT_CREATE)); + } + + /** + * Fire file destruction event + * + * @access protected + * @param integer $file_id + */ + protected function fireDestructionEvent($file_id) + { + $this->queueManager->push($this->projectFileEventJob->withParams($file_id, self::EVENT_DESTROY)); + } +} diff --git a/app/Model/ProjectGroupRoleModel.php b/app/Model/ProjectGroupRoleModel.php new file mode 100644 index 0000000..213fee0 --- /dev/null +++ b/app/Model/ProjectGroupRoleModel.php @@ -0,0 +1,197 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; +use Kanboard\Core\Security\Role; + +/** + * Project Group Role + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class ProjectGroupRoleModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'project_has_groups'; + + /** + * Get the list of project visible by the given user according to groups + * + * @access public + * @param integer $user_id + * @param array $status + * @return array + */ + public function getProjectsByUser($user_id, $status = array(ProjectModel::ACTIVE, ProjectModel::INACTIVE)) + { + return $this->db + ->hashtable(ProjectModel::TABLE) + ->join(self::TABLE, 'project_id', 'id') + ->join(GroupMemberModel::TABLE, 'group_id', 'group_id', self::TABLE) + ->eq(GroupMemberModel::TABLE.'.user_id', $user_id) + ->in(ProjectModel::TABLE.'.is_active', $status) + ->getAll(ProjectModel::TABLE.'.id', ProjectModel::TABLE.'.name'); + } + + /** + * For a given project get the role of the specified user + * + * @access public + * @param integer $project_id + * @param integer $user_id + * @return string + */ + public function getUserRole($project_id, $user_id) + { + $roles = $this->db->table(self::TABLE) + ->join(GroupMemberModel::TABLE, 'group_id', 'group_id', self::TABLE) + ->eq(GroupMemberModel::TABLE.'.user_id', $user_id) + ->eq(self::TABLE.'.project_id', $project_id) + ->findAllByColumn('role'); + + return $this->projectAccessMap->getHighestRole($roles); + } + + /** + * Get all groups associated directly to the project + * + * @access public + * @param integer $project_id + * @return array + */ + public function getGroups($project_id) + { + return $this->db->table(self::TABLE) + ->columns(GroupModel::TABLE.'.id', GroupModel::TABLE.'.name', self::TABLE.'.role') + ->join(GroupModel::TABLE, 'id', 'group_id') + ->eq('project_id', $project_id) + ->asc('name') + ->findAll(); + } + + /** + * From groups get all users associated to the project + * + * @access public + * @param integer $project_id + * @return array + */ + public function getUsers($project_id) + { + return $this->db->table(self::TABLE) + ->columns( + UserModel::TABLE.'.id', + UserModel::TABLE.'.username', + UserModel::TABLE.'.name', + UserModel::TABLE.'.email', + self::TABLE.'.role' + ) + ->join(GroupMemberModel::TABLE, 'group_id', 'group_id', self::TABLE) + ->join(UserModel::TABLE, 'id', 'user_id', GroupMemberModel::TABLE) + ->eq(self::TABLE.'.project_id', $project_id) + ->asc(UserModel::TABLE.'.username') + ->findAll(); + } + + /** + * From groups get all users assignable to tasks + * + * @access public + * @param integer $project_id + * @return array + */ + public function getAssignableUsers($project_id) + { + return $this->db->table(UserModel::TABLE) + ->columns(UserModel::TABLE.'.id', UserModel::TABLE.'.username', UserModel::TABLE.'.name') + ->join(GroupMemberModel::TABLE, 'user_id', 'id', UserModel::TABLE) + ->join(self::TABLE, 'group_id', 'group_id', GroupMemberModel::TABLE) + ->eq(self::TABLE.'.project_id', $project_id) + ->eq(UserModel::TABLE.'.is_active', 1) + ->neq(self::TABLE.'.role', Role::PROJECT_VIEWER) + ->asc(UserModel::TABLE.'.username') + ->findAll(); + } + + /** + * Add a group to the project + * + * @access public + * @param integer $project_id + * @param integer $group_id + * @param string $role + * @return boolean + */ + public function addGroup($project_id, $group_id, $role) + { + return $this->db->table(self::TABLE)->insert(array( + 'group_id' => $group_id, + 'project_id' => $project_id, + 'role' => $role, + )); + } + + /** + * Remove a group from the project + * + * @access public + * @param integer $project_id + * @param integer $group_id + * @return boolean + */ + public function removeGroup($project_id, $group_id) + { + return $this->db->table(self::TABLE)->eq('group_id', $group_id)->eq('project_id', $project_id)->remove(); + } + + /** + * Change a group role for the project + * + * @access public + * @param integer $project_id + * @param integer $group_id + * @param string $role + * @return boolean + */ + public function changeGroupRole($project_id, $group_id, $role) + { + return $this->db->table(self::TABLE) + ->eq('group_id', $group_id) + ->eq('project_id', $project_id) + ->update(array( + 'role' => $role, + )); + } + + /** + * Copy group access from a project to another one + * + * @param integer $project_src_id Project Template + * @param integer $project_dst_id Project that receives the copy + * @return boolean + */ + public function duplicate($project_src_id, $project_dst_id) + { + $rows = $this->db->table(self::TABLE)->eq('project_id', $project_src_id)->findAll(); + + foreach ($rows as $row) { + $result = $this->db->table(self::TABLE)->save(array( + 'project_id' => $project_dst_id, + 'group_id' => $row['group_id'], + 'role' => $row['role'], + )); + + if (! $result) { + return false; + } + } + + return true; + } +} diff --git a/app/Model/ProjectMetadataModel.php b/app/Model/ProjectMetadataModel.php new file mode 100644 index 0000000..760acd7 --- /dev/null +++ b/app/Model/ProjectMetadataModel.php @@ -0,0 +1,54 @@ +<?php + +namespace Kanboard\Model; + +/** + * Project Metadata + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class ProjectMetadataModel extends MetadataModel +{ + /** + * Get the table + * + * @abstract + * @access protected + * @return string + */ + protected function getTable() + { + return 'project_has_metadata'; + } + + /** + * Define the entity key + * + * @access protected + * @return string + */ + protected function getEntityKey() + { + return 'project_id'; + } + + /** + * Helper method to duplicate all metadata to another project + * + * @access public + * @param integer $src_project_id + * @param integer $dst_project_id + * @return boolean + */ + public function duplicate($src_project_id, $dst_project_id) + { + $metadata = $this->getAll($src_project_id); + + if (! $this->save($dst_project_id, $metadata)) { + return false; + } + + return true; + } +} diff --git a/app/Model/ProjectModel.php b/app/Model/ProjectModel.php new file mode 100644 index 0000000..bb99955 --- /dev/null +++ b/app/Model/ProjectModel.php @@ -0,0 +1,611 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; +use Kanboard\Core\Security\Token; +use Kanboard\Core\Security\Role; +use Kanboard\Model\TaskModel; +use Kanboard\Model\TaskFileModel; + +/** + * Project model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class ProjectModel extends Base +{ + /** + * SQL table name for projects + * + * @var string + */ + const TABLE = 'projects'; + + /** + * Value for active project + * + * @var integer + */ + const ACTIVE = 1; + + /** + * Value for inactive project + * + * @var integer + */ + const INACTIVE = 0; + + /** + * Value for private project + * + * @var integer + */ + const TYPE_PRIVATE = 1; + + /** + * Value for team project + * + * @var integer + */ + const TYPE_TEAM = 0; + + /** + * Get a project by the id + * + * @access public + * @param integer $project_id Project id + * @return array + */ + public function getById($project_id) + { + return $this->db->table(self::TABLE)->eq('id', $project_id)->findOne(); + } + + /** + * Get a project by id with owner name + * + * @access public + * @param integer $project_id Project id + * @return array + */ + public function getByIdWithOwner($project_id) + { + return $this->db->table(self::TABLE) + ->columns(self::TABLE.'.*', UserModel::TABLE.'.username AS owner_username', UserModel::TABLE.'.name AS owner_name') + ->eq(self::TABLE.'.id', $project_id) + ->join(UserModel::TABLE, 'id', 'owner_id') + ->findOne(); + } + + /** + * Get a project by id with owner name and task count + * + * @access public + * @param integer $project_id Project id + * @return array + */ + public function getByIdWithOwnerAndTaskCount($project_id) + { + return $this->db->table(self::TABLE) + ->columns( + self::TABLE.'.*', + UserModel::TABLE.'.username AS owner_username', + UserModel::TABLE.'.name AS owner_name', + "(SELECT count(*) FROM tasks WHERE tasks.project_id=projects.id AND tasks.is_active='1') AS nb_active_tasks" + ) + ->eq(self::TABLE.'.id', $project_id) + ->join(UserModel::TABLE, 'id', 'owner_id') + ->join(TaskModel::TABLE, 'project_id', 'id') + ->findOne(); + } + + /** + * Get a project by the name + * + * @access public + * @param string $name Project name + * @return array + */ + public function getByName($name) + { + return $this->db->table(self::TABLE)->eq('name', $name)->findOne(); + } + + /** + * Get a project by the identifier (code) + * + * @access public + * @param string $identifier + * @return array|boolean + */ + public function getByIdentifier($identifier) + { + if (empty($identifier)) { + return false; + } + + return $this->db->table(self::TABLE)->eq('identifier', strtoupper($identifier))->findOne(); + } + + /** + * Get a project by the email address + * + * @access public + * @param string $email + * @return array|boolean + */ + public function getByEmail($email) + { + if (empty($email)) { + return false; + } + + return $this->db->table(self::TABLE)->eq('email', $email)->findOne(); + } + + /** + * Fetch project data by using the token + * + * @access public + * @param string $token Token + * @return array|boolean + */ + public function getByToken($token) + { + if (empty($token)) { + return false; + } + + return $this->db->table(self::TABLE)->eq('token', $token)->eq('is_public', 1)->findOne(); + } + + /** + * Return the first project from the database (no sorting) + * + * @access public + * @return array + */ + public function getFirst() + { + return $this->db->table(self::TABLE)->findOne(); + } + + /** + * Return true if the project is private + * + * @access public + * @param integer $project_id Project id + * @return boolean + */ + public function isPrivate($project_id) + { + return $this->db->table(self::TABLE)->eq('id', $project_id)->eq('is_private', 1)->exists(); + } + + /** + * Get all projects + * + * @access public + * @return array + */ + public function getAll() + { + return $this->db->table(self::TABLE)->asc('name')->findAll(); + } + + /** + * Get all projects with given Ids + * + * @access public + * @param integer[] $project_ids + * @return array + */ + public function getAllByIds(array $project_ids) + { + if (empty($project_ids)) { + return array(); + } + + return $this->db->table(self::TABLE)->in('id', $project_ids)->asc('name')->findAll(); + } + + /** + * Get all project ids + * + * @access public + * @return array + */ + public function getAllIds() + { + return $this->db->table(self::TABLE)->asc('name')->findAllByColumn('id'); + } + + /** + * Return the list of all projects + * + * @access public + * @param bool $prependNone + * @param bool $noPrivateProjects + * @return array + */ + public function getList($prependNone = true, $noPrivateProjects = true) + { + if ($noPrivateProjects) { + $projects = $this->db->hashtable(self::TABLE)->eq('is_private', 0)->asc('name')->getAll('id', 'name'); + } else { + $projects = $this->db->hashtable(self::TABLE)->asc('name')->getAll('id', 'name'); + } + + if ($prependNone) { + return array(t('None')) + $projects; + } + + return $projects; + } + + /** + * Get all projects with all its data for a given status + * + * @access public + * @param integer $status Project status: self::ACTIVE or self:INACTIVE + * @return array + */ + public function getAllByStatus($status) + { + return $this->db + ->table(self::TABLE) + ->asc('name') + ->eq('is_active', $status) + ->findAll(); + } + + /** + * Get a list of project by status + * + * @access public + * @param integer $status Project status: self::ACTIVE or self:INACTIVE + * @return array + */ + public function getListByStatus($status) + { + return $this->db + ->hashtable(self::TABLE) + ->asc('name') + ->eq('is_active', $status) + ->getAll('id', 'name'); + } + + /** + * Return the number of projects by status + * + * @access public + * @param integer $status Status: self::ACTIVE or self:INACTIVE + * @return integer + */ + public function countByStatus($status) + { + return $this->db + ->table(self::TABLE) + ->eq('is_active', $status) + ->count(); + } + + /** + * Get stats for each column of a project + * + * @access public + * @param array $project + * @return array + */ + public function getColumnStats(array &$project) + { + $project['columns'] = $this->columnModel->getAllWithTaskCount($project['id']); + $project['nb_active_tasks'] = 0; + + foreach ($project['columns'] as $column) { + $project['nb_active_tasks'] += $column['nb_open_tasks']; + } + + return $project; + } + + /** + * Apply column stats to a collection of projects (filter callback) + * + * @access public + * @param array $projects + * @return array + */ + public function applyColumnStats(array $projects) + { + foreach ($projects as &$project) { + $this->getColumnStats($project); + } + + return $projects; + } + + /** + * Get project summary for a list of project + * + * @access public + * @param array $project_ids List of project id + * @return \PicoDb\Table + */ + public function getQueryColumnStats(array $project_ids) + { + if (empty($project_ids)) { + return $this->db->table(ProjectModel::TABLE)->eq(ProjectModel::TABLE.'.id', 0); + } + + return $this->db + ->table(ProjectModel::TABLE) + ->columns(self::TABLE.'.*', UserModel::TABLE.'.username AS owner_username', UserModel::TABLE.'.name AS owner_name') + ->join(UserModel::TABLE, 'id', 'owner_id') + ->in(self::TABLE.'.id', $project_ids) + ->callback(array($this, 'applyColumnStats')); + } + + /** + * Get query for list of project without column statistics + * + * @access public + * @param array $projectIds + * @return \PicoDb\Table + */ + public function getQueryByProjectIds(array $projectIds) + { + if (empty($projectIds)) { + return $this->db->table(ProjectModel::TABLE)->eq(ProjectModel::TABLE.'.id', 0); + } + + return $this->db + ->table(ProjectModel::TABLE) + ->columns(self::TABLE.'.*', UserModel::TABLE.'.username AS owner_username', UserModel::TABLE.'.name AS owner_name') + ->join(UserModel::TABLE, 'id', 'owner_id') + ->in(self::TABLE.'.id', $projectIds); + } + + /** + * Create a project + * + * @access public + * @param array $values Form values + * @param integer $userId User who creates the project + * @param bool $addUser Whether to automatically add the user to the project + * @return int Project id + */ + public function create(array $values, $userId = 0, $addUser = false) + { + if (! empty($userId) && ! $this->userModel->exists($userId)) { + return false; + } + + $this->db->startTransaction(); + + $values['token'] = ''; + $values['last_modified'] = time(); + $values['is_private'] = empty($values['is_private']) ? 0 : 1; + $values['owner_id'] = $userId; + + if (! empty($values['identifier'])) { + $values['identifier'] = strtoupper($values['identifier']); + } + + $this->helper->model->convertIntegerFields($values, array('priority_default', 'priority_start', 'priority_end', 'task_limit')); + + if (! $this->db->table(self::TABLE)->save($values)) { + $this->db->cancelTransaction(); + return false; + } + + $project_id = $this->db->getLastId(); + + if (! $this->boardModel->create($project_id, $this->boardModel->getUserColumns())) { + $this->db->cancelTransaction(); + return false; + } + + if (! $this->swimlaneModel->create($project_id, t('Default swimlane'))) { + $this->db->cancelTransaction(); + return false; + } + + if ($addUser && $userId) { + $this->projectUserRoleModel->addUser($project_id, $userId, Role::PROJECT_MANAGER); + } + + $this->categoryModel->createDefaultCategories($project_id); + + $this->db->closeTransaction(); + + return (int) $project_id; + } + + /** + * Check if the project have been modified + * + * @access public + * @param integer $project_id Project id + * @param integer $timestamp Timestamp + * @return bool + */ + public function isModifiedSince($project_id, $timestamp) + { + return (bool) $this->db->table(self::TABLE) + ->eq('id', $project_id) + ->gt('last_modified', $timestamp) + ->count(); + } + + /** + * Update modification date + * + * @access public + * @param integer $project_id Project id + * @return bool + */ + public function updateModificationDate($project_id) + { + return $this->db->table(self::TABLE)->eq('id', $project_id)->update(array( + 'last_modified' => time() + )); + } + + /** + * Update a project + * + * @access public + * @param array $values Form values + * @return bool + */ + public function update(array $values) + { + if (! empty($values['identifier'])) { + $values['identifier'] = strtoupper($values['identifier']); + } + + if (! empty($values['start_date'])) { + $values['start_date'] = $this->dateParser->getIsoDate($values['start_date']); + } + + if (! empty($values['end_date'])) { + $values['end_date'] = $this->dateParser->getIsoDate($values['end_date']); + } + + if (! empty($values['owner_id']) && ! $this->userModel->exists($values['owner_id'])) { + return false; + } + + $values['per_swimlane_task_limits'] = empty($values['per_swimlane_task_limits']) ? 0 : 1; + + $this->helper->model->convertIntegerFields($values, array('priority_default', 'priority_start', 'priority_end', 'task_limit')); + + $updates = $values; + unset($updates['id']); + return $this->exists($values['id']) && + $this->db->table(self::TABLE)->eq('id', $values['id'])->save($updates); + } + + /** + * Remove a project + * + * @access public + * @param integer $project_id Project id + * @return bool + */ + public function remove($project_id) + { + // Remove all project attachments + $this->projectFileModel->removeAll($project_id); + + // Remove all task attachments + $file_ids = $this->db + ->table(TaskFileModel::TABLE) + ->eq(TaskModel::TABLE.'.project_id', $project_id) + ->join(TaskModel::TABLE, 'id', 'task_id', TaskFileModel::TABLE) + ->findAllByColumn(TaskFileModel::TABLE.'.id'); + + foreach ($file_ids as $file_id) { + $this->taskFileModel->remove($file_id); + } + + // Remove project + $this->db->table(TagModel::TABLE)->eq('project_id', $project_id)->remove(); + return $this->db->table(self::TABLE)->eq('id', $project_id)->remove(); + } + + /** + * Return true if the project exists + * + * @access public + * @param integer $project_id Project id + * @return boolean + */ + public function exists($project_id) + { + return $this->db->table(self::TABLE)->eq('id', $project_id)->exists(); + } + + /** + * Enable a project + * + * @access public + * @param integer $project_id Project id + * @return bool + */ + public function enable($project_id) + { + return $this->exists($project_id) && + $this->db + ->table(self::TABLE) + ->eq('id', $project_id) + ->update(array('is_active' => 1)); + } + + /** + * Disable a project + * + * @access public + * @param integer $project_id Project id + * @return bool + */ + public function disable($project_id) + { + return $this->exists($project_id) && + $this->db + ->table(self::TABLE) + ->eq('id', $project_id) + ->update(array('is_active' => 0)); + } + + /** + * Enable public access for a project + * + * @access public + * @param integer $project_id Project id + * @return bool + */ + public function enablePublicAccess($project_id) + { + return $this->exists($project_id) && + $this->db + ->table(self::TABLE) + ->eq('id', $project_id) + ->save(array('is_public' => 1, 'token' => Token::getToken())); + } + + /** + * Disable public access for a project + * + * @access public + * @param integer $project_id Project id + * @return bool + */ + public function disablePublicAccess($project_id) + { + return $this->exists($project_id) && + $this->db + ->table(self::TABLE) + ->eq('id', $project_id) + ->save(array('is_public' => 0, 'token' => '')); + } + + /** + * Change usage of global tags + * + * @param integer $project_id Project id + * @param bool $global_tags New global tag value + * @return bool + */ + public function changeGlobalTagUsage($project_id, $global_tags) + { + return $this->exists($project_id) && + $this->db + ->table(self::TABLE) + ->eq('id', $project_id) + ->save(array('enable_global_tags' => $global_tags)); + } +} diff --git a/app/Model/ProjectNotificationModel.php b/app/Model/ProjectNotificationModel.php new file mode 100644 index 0000000..aeeee4c --- /dev/null +++ b/app/Model/ProjectNotificationModel.php @@ -0,0 +1,67 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Project Notification + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class ProjectNotificationModel extends Base +{ + /** + * Send notifications + * + * @access public + * @param integer $project_id + * @param string $event_name + * @param array $event_data + */ + public function sendNotifications($project_id, $event_name, array $event_data) + { + $project = $this->projectModel->getById($project_id); + + $types = array_merge( + $this->projectNotificationTypeModel->getHiddenTypes(), + $this->projectNotificationTypeModel->getSelectedTypes($project_id) + ); + + foreach ($types as $type) { + $this->projectNotificationTypeModel->getType($type)->notifyProject($project, $event_name, $event_data); + } + } + + /** + * Save settings for the given project + * + * @access public + * @param integer $project_id + * @param array $values + */ + public function saveSettings($project_id, array $values) + { + $this->db->startTransaction(); + + $types = empty($values['notification_types']) ? array() : array_keys($values['notification_types']); + $this->projectNotificationTypeModel->saveSelectedTypes($project_id, $types); + + $this->db->closeTransaction(); + } + + /** + * Read user settings to display the form + * + * @access public + * @param integer $project_id + * @return array + */ + public function readSettings($project_id) + { + return array( + 'notification_types' => $this->projectNotificationTypeModel->getSelectedTypes($project_id), + ); + } +} diff --git a/app/Model/ProjectNotificationTypeModel.php b/app/Model/ProjectNotificationTypeModel.php new file mode 100644 index 0000000..aeec77f --- /dev/null +++ b/app/Model/ProjectNotificationTypeModel.php @@ -0,0 +1,57 @@ +<?php + +namespace Kanboard\Model; + +/** + * Project Notification Type + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class ProjectNotificationTypeModel extends NotificationTypeModel +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'project_has_notification_types'; + + /** + * Get selected notification types for a given project + * + * @access public + * @param integer $project_id + * @return array + */ + public function getSelectedTypes($project_id) + { + $types = $this->db + ->table(self::TABLE) + ->eq('project_id', $project_id) + ->asc('notification_type') + ->findAllByColumn('notification_type'); + + return $this->filterTypes($types); + } + + /** + * Save notification types for a given project + * + * @access public + * @param integer $project_id + * @param string[] $types + * @return boolean + */ + public function saveSelectedTypes($project_id, array $types) + { + $results = array(); + $this->db->table(self::TABLE)->eq('project_id', $project_id)->remove(); + + foreach ($types as $type) { + $results[] = $this->db->table(self::TABLE)->insert(array('project_id' => $project_id, 'notification_type' => $type)); + } + + return ! in_array(false, $results, true); + } +} diff --git a/app/Model/ProjectPermissionModel.php b/app/Model/ProjectPermissionModel.php new file mode 100644 index 0000000..c0dc2f5 --- /dev/null +++ b/app/Model/ProjectPermissionModel.php @@ -0,0 +1,199 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; +use Kanboard\Core\Security\Role; +use Kanboard\Filter\ProjectGroupRoleProjectFilter; +use Kanboard\Filter\ProjectGroupRoleUsernameFilter; +use Kanboard\Filter\ProjectUserRoleProjectFilter; +use Kanboard\Filter\ProjectUserRoleUsernameFilter; + +/** + * Project Permission + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class ProjectPermissionModel extends Base +{ + /** + * Get query for project users overview + * + * @access public + * @param array $project_ids + * @param string $role + * @return \PicoDb\Table + */ + public function getQueryByRole(array $project_ids, $role) + { + if (empty($project_ids)) { + $project_ids = array(-1); + } + + return $this + ->db + ->table(ProjectUserRoleModel::TABLE) + ->join(UserModel::TABLE, 'id', 'user_id') + ->join(ProjectModel::TABLE, 'id', 'project_id') + ->eq(ProjectUserRoleModel::TABLE.'.role', $role) + ->eq(ProjectModel::TABLE.'.is_private', 0) + ->in(ProjectModel::TABLE.'.id', $project_ids) + ->columns( + UserModel::TABLE.'.id', + UserModel::TABLE.'.username', + UserModel::TABLE.'.name', + ProjectModel::TABLE.'.name AS project_name', + ProjectModel::TABLE.'.id' + ); + } + + /** + * Get all usernames (fetch users from groups) + * + * @access public + * @param integer $project_id + * @param string $input + * @return array + */ + public function findUsernames($project_id, $input) + { + $userMembers = $this->projectUserRoleQuery + ->withFilter(new ProjectUserRoleProjectFilter($project_id)) + ->withFilter(new ProjectUserRoleUsernameFilter($input)) + ->getQuery() + ->columns( + UserModel::TABLE.'.id', + UserModel::TABLE.'.username', + UserModel::TABLE.'.name', + UserModel::TABLE.'.email', + UserModel::TABLE.'.avatar_path' + ) + ->findAll(); + + $groupMembers = $this->projectGroupRoleQuery + ->withFilter(new ProjectGroupRoleProjectFilter($project_id)) + ->withFilter(new ProjectGroupRoleUsernameFilter($input)) + ->getQuery() + ->columns( + UserModel::TABLE.'.id', + UserModel::TABLE.'.username', + UserModel::TABLE.'.name', + UserModel::TABLE.'.email', + UserModel::TABLE.'.avatar_path' + ) + ->findAll(); + + $userMembers = array_column_index_unique($userMembers, 'username'); + $groupMembers = array_column_index_unique($groupMembers, 'username'); + $members = array_merge($userMembers, $groupMembers); + + ksort($members); + + return $members; + } + + public function getMembers($project_id) + { + $userMembers = $this->projectUserRoleModel->getUsers($project_id); + $groupMembers = $this->projectGroupRoleModel->getUsers($project_id); + + $userMembers = array_column_index_unique($userMembers, 'username'); + $groupMembers = array_column_index_unique($groupMembers, 'username'); + return array_merge($userMembers, $groupMembers); + } + + public function getMembersWithEmail($project_id) + { + $members = $this->getMembers($project_id); + return array_filter($members, function (array $user) { + return ! empty($user['email']); + }); + } + + /** + * Return true if the user is allowed to access a project + * + * @param integer $project_id + * @param integer $user_id + * @return boolean + */ + public function isUserAllowed($project_id, $user_id) + { + if ($this->userSession->isAdmin()) { + return true; + } + + return $this->userModel->isActive($user_id) && + $this->isMember($project_id, $user_id); + } + + /** + * Return true if the user is assignable + * + * @access public + * @param integer $project_id + * @param integer $user_id + * @return boolean + */ + public function isAssignable($project_id, $user_id) + { + if ($this->userModel->isActive($user_id)) { + $role = $this->projectUserRoleModel->getUserRole($project_id, $user_id); + + return ! empty($role) && $role !== Role::PROJECT_VIEWER; + } + + return false; + } + + /** + * Return true if the user is member + * + * @access public + * @param integer $project_id + * @param integer $user_id + * @return boolean + */ + public function isMember($project_id, $user_id) + { + return ! empty($this->projectUserRoleModel->getUserRole($project_id, $user_id)); + } + + /** + * Get active project ids by user + * + * @access public + * @param integer $user_id + * @return array + */ + public function getActiveProjectIds($user_id) + { + return array_keys($this->projectUserRoleModel->getActiveProjectsByUser($user_id)); + } + + /** + * Get all project ids by user + * + * @access public + * @param integer $user_id + * @return array + */ + public function getProjectIds($user_id) + { + return array_keys($this->projectUserRoleModel->getProjectsByUser($user_id)); + } + + /** + * Copy permissions to another project + * + * @param integer $project_src_id Project Template + * @param integer $project_dst_id Project that receives the copy + * @return boolean + */ + public function duplicate($project_src_id, $project_dst_id) + { + return $this->projectUserRoleModel->duplicate($project_src_id, $project_dst_id) && + $this->projectGroupRoleModel->duplicate($project_src_id, $project_dst_id); + } +} diff --git a/app/Model/ProjectRoleModel.php b/app/Model/ProjectRoleModel.php new file mode 100644 index 0000000..c5cd7b0 --- /dev/null +++ b/app/Model/ProjectRoleModel.php @@ -0,0 +1,234 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; +use Kanboard\Core\Security\Role; + +/** + * Class ProjectRoleModel + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class ProjectRoleModel extends Base +{ + const TABLE = 'project_has_roles'; + + /** + * Get list of project roles + * + * @param int $project_id + * @return array + */ + public function getList($project_id) + { + $defaultRoles = $this->role->getProjectRoles(); + $customRoles = $this->db + ->hashtable(self::TABLE) + ->eq('project_id', $project_id) + ->getAll('role', 'role'); + + return $defaultRoles + $customRoles; + } + + /** + * Get a role + * + * @param int $project_id + * @param int $role_id + * @return array|null + */ + public function getById($project_id, $role_id) + { + return $this->db->table(self::TABLE) + ->eq('project_id', $project_id) + ->eq('role_id', $role_id) + ->findOne(); + } + + /** + * Get all project roles + * + * @param int $project_id + * @return array + */ + public function getAll($project_id) + { + return $this->db->table(self::TABLE) + ->eq('project_id', $project_id) + ->asc('role') + ->findAll(); + } + + /** + * Get all project roles with restrictions + * + * @param int $project_id + * @return array + */ + public function getAllWithRestrictions($project_id) + { + $roles = $this->getAll($project_id); + + $column_restrictions = $this->columnRestrictionModel->getAll($project_id); + $column_restrictions = array_column_index($column_restrictions, 'role_id'); + array_merge_relation($roles, $column_restrictions, 'column_restrictions', 'role_id'); + + $column_move_restrictions = $this->columnMoveRestrictionModel->getAll($project_id); + $column_move_restrictions = array_column_index($column_move_restrictions, 'role_id'); + array_merge_relation($roles, $column_move_restrictions, 'column_move_restrictions', 'role_id'); + + $project_restrictions = $this->projectRoleRestrictionModel->getAll($project_id); + $project_restrictions = array_column_index($project_restrictions, 'role_id'); + array_merge_relation($roles, $project_restrictions, 'project_restrictions', 'role_id'); + + return $roles; + } + + /** + * Create a new project role + * + * @param int $project_id + * @param string $role + * @return bool|int + */ + public function create($project_id, $role) + { + return $this->db + ->table(self::TABLE) + ->persist(array( + 'project_id' => $project_id, + 'role' => $role, + )); + } + + /** + * Update a project role + * + * @param int $role_id + * @param int $project_id + * @param string $role + * @return bool + */ + public function update($role_id, $project_id, $role) + { + $this->db->startTransaction(); + + $previousRole = $this->getById($project_id, $role_id); + + $r1 = $this->db + ->table(ProjectUserRoleModel::TABLE) + ->eq('project_id', $project_id) + ->eq('role', $previousRole['role']) + ->update(array( + 'role' => $role + )); + + $r2 = $this->db + ->table(ProjectGroupRoleModel::TABLE) + ->eq('project_id', $project_id) + ->eq('role', $previousRole['role']) + ->update(array( + 'role' => $role + )); + + $r3 = $this->db + ->table(self::TABLE) + ->eq('role_id', $role_id) + ->eq('project_id', $project_id) + ->update(array( + 'role' => $role, + )); + + if ($r1 && $r2 && $r3) { + $this->db->closeTransaction(); + return true; + } + + $this->db->cancelTransaction(); + return false; + } + + /** + * Remove a project role + * + * @param int $project_id + * @param int $role_id + * @return bool + */ + public function remove($project_id, $role_id) + { + $this->db->startTransaction(); + + $role = $this->getById($project_id, $role_id); + + $r1 = $this->db + ->table(ProjectUserRoleModel::TABLE) + ->eq('project_id', $project_id) + ->eq('role', $role['role']) + ->update(array( + 'role' => Role::PROJECT_MEMBER + )); + + $r2 = $this->db + ->table(ProjectGroupRoleModel::TABLE) + ->eq('project_id', $project_id) + ->eq('role', $role['role']) + ->update(array( + 'role' => Role::PROJECT_MEMBER + )); + + $r3 = $this->db + ->table(self::TABLE) + ->eq('project_id', $project_id) + ->eq('role_id', $role_id) + ->remove(); + + if ($r1 && $r2 && $r3) { + $this->db->closeTransaction(); + return true; + } + + $this->db->cancelTransaction(); + return false; + } + + /** + * Copy project custom_roles from a project to another one + * + * @param integer $project_src_id + * @param integer $project_dst_id + * @return boolean + */ + public function duplicate($project_src_id, $project_dst_id) + { + $rows = $this->db->table(self::TABLE)->eq('project_id', $project_src_id)->findAll(); + + foreach ($rows as $row) { + $role_src_id = $row['role_id']; + $role_dst_id = $this->db->table(self::TABLE)->persist(array( + 'project_id' => $project_dst_id, + 'role' => $row['role'], + )); + + if (! $role_dst_id) { + return false; + } + + if (! $this->columnRestrictionModel->duplicate($project_src_id, $project_dst_id, $role_src_id, $role_dst_id)) { + return false; + } + + if (! $this->columnMoveRestrictionModel->duplicate($project_src_id, $project_dst_id, $role_src_id, $role_dst_id)) { + return false; + } + + if (! $this->projectRoleRestrictionModel->duplicate($project_src_id, $project_dst_id, $role_src_id, $role_dst_id)) { + return false; + } + } + + return true; + } +} diff --git a/app/Model/ProjectRoleRestrictionModel.php b/app/Model/ProjectRoleRestrictionModel.php new file mode 100644 index 0000000..ee6e032 --- /dev/null +++ b/app/Model/ProjectRoleRestrictionModel.php @@ -0,0 +1,167 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Class ProjectRoleRestrictionModel + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class ProjectRoleRestrictionModel extends Base +{ + const TABLE = 'project_role_has_restrictions'; + + const RULE_TASK_CREATION = 'task_creation'; + const RULE_TASK_SUPPRESSION = 'task_remove'; + const RULE_TASK_OPEN_CLOSE = 'task_open_close'; + const RULE_TASK_MOVE = 'task_move'; + const RULE_TASK_CHANGE_ASSIGNEE = 'task_change_assignee'; + const RULE_TASK_UPDATE_ASSIGNED = 'task_update_assigned'; + + /** + * Get rules + * + * @return array + */ + public function getRules() + { + return array( + self::RULE_TASK_CREATION => t('Task creation is not permitted'), + self::RULE_TASK_SUPPRESSION => t('Task suppression is not permitted'), + self::RULE_TASK_OPEN_CLOSE => t('Closing or opening a task is not permitted'), + self::RULE_TASK_MOVE => t('Moving a task is not permitted'), + self::RULE_TASK_CHANGE_ASSIGNEE => t('Changing assignee is not permitted'), + self::RULE_TASK_UPDATE_ASSIGNED => t('Update only assigned tasks is permitted'), + ); + } + + /** + * Get a single restriction + * + * @param integer $project_id + * @param integer $restriction_id + * @return array|null + */ + public function getById($project_id, $restriction_id) + { + return $this->db + ->table(self::TABLE) + ->eq('project_id', $project_id) + ->eq('restriction_id', $restriction_id) + ->findOne(); + } + + /** + * Get restrictions + * + * @param int $project_id + * @return array + */ + public function getAll($project_id) + { + $rules = $this->getRules(); + $restrictions = $this->db + ->table(self::TABLE) + ->columns( + 'restriction_id', + 'project_id', + 'role_id', + 'rule' + ) + ->eq(self::TABLE.'.project_id', $project_id) + ->findAll(); + + foreach ($restrictions as &$restriction) { + $restriction['title'] = $rules[$restriction['rule']]; + } + + return $restrictions; + } + + /** + * Get restrictions + * + * @param int $project_id + * @param string $role + * @return array + */ + public function getAllByRole($project_id, $role) + { + return $this->db + ->table(self::TABLE) + ->columns( + 'restriction_id', + 'project_id', + 'role_id', + 'rule', + 'pr.role' + ) + ->eq(self::TABLE.'.project_id', $project_id) + ->eq('role', $role) + ->left(ProjectRoleModel::TABLE, 'pr', 'role_id', self::TABLE, 'role_id') + ->findAll(); + } + + /** + * Create a new restriction + * + * @param int $project_id + * @param int $role_id + * @param string $rule + * @return bool|int + */ + public function create($project_id, $role_id, $rule) + { + return $this->db->table(self::TABLE) + ->persist(array( + 'project_id' => $project_id, + 'role_id' => $role_id, + 'rule' => $rule, + )); + } + + /** + * Remove a restriction + * + * @param integer $restriction_id + * @return bool + */ + public function remove($restriction_id) + { + return $this->db->table(self::TABLE)->eq('restriction_id', $restriction_id)->remove(); + } + + /** + * Copy role restriction models from a custome_role in the src project to the dst custom_role of the dst project + * + * @param integer $project_src_id + * @param integer $project_dst_id + * @param integer $role_src_id + * @param integer $role_dst_id + * @return boolean + */ + public function duplicate($project_src_id, $project_dst_id, $role_src_id, $role_dst_id) + { + $rows = $this->db->table(self::TABLE) + ->eq('project_id', $project_src_id) + ->eq('role_id', $role_src_id) + ->findAll(); + + foreach ($rows as $row) { + $result = $this->db->table(self::TABLE)->persist(array( + 'project_id' => $project_dst_id, + 'role_id' => $role_dst_id, + 'rule' => $row['rule'], + )); + + if (! $result) { + return false; + } + } + + return true; + } +} diff --git a/app/Model/ProjectTaskDuplicationModel.php b/app/Model/ProjectTaskDuplicationModel.php new file mode 100644 index 0000000..5d2e132 --- /dev/null +++ b/app/Model/ProjectTaskDuplicationModel.php @@ -0,0 +1,35 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Project Task Duplication Model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class ProjectTaskDuplicationModel extends Base +{ + /** + * Duplicate all tasks to another project + * + * @access public + * @param integer $src_project_id + * @param integer $dst_project_id + * @return boolean + */ + public function duplicate($src_project_id, $dst_project_id) + { + $task_ids = $this->taskFinderModel->getAllIds($src_project_id, array(TaskModel::STATUS_OPEN, TaskModel::STATUS_CLOSED)); + + foreach ($task_ids as $task_id) { + if (! $this->taskProjectDuplicationModel->duplicateToProject($task_id, $dst_project_id)) { + return false; + } + } + + return true; + } +} diff --git a/app/Model/ProjectTaskPriorityModel.php b/app/Model/ProjectTaskPriorityModel.php new file mode 100644 index 0000000..c1a0257 --- /dev/null +++ b/app/Model/ProjectTaskPriorityModel.php @@ -0,0 +1,74 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Project Task Priority Model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class ProjectTaskPriorityModel extends Base +{ + /** + * Get Priority range from a project + * + * @access public + * @param array $project + * @return array + */ + public function getPriorities(array $project) + { + $range = range($project['priority_start'], $project['priority_end']); + return array_combine($range, $range); + } + + /** + * Get task priority settings + * + * @access public + * @param int $project_id + * @return array|null + */ + public function getPrioritySettings($project_id) + { + return $this->db + ->table(ProjectModel::TABLE) + ->columns('priority_default', 'priority_start', 'priority_end') + ->eq('id', $project_id) + ->findOne(); + } + + /** + * Get default task priority + * + * @access public + * @param int $project_id + * @return int + */ + public function getDefaultPriority($project_id) + { + return $this->db->table(ProjectModel::TABLE)->eq('id', $project_id)->findOneColumn('priority_default') ?: 0; + } + + /** + * Get priority for a destination project + * + * @access public + * @param integer $dst_project_id + * @param integer $priority + * @return integer + */ + public function getPriorityForProject($dst_project_id, $priority) + { + $settings = $this->getPrioritySettings($dst_project_id); + + if ($priority >= $settings['priority_start'] && $priority <= $settings['priority_end']) { + return $priority; + } + + return $settings['priority_default']; + } +} diff --git a/app/Model/ProjectUserRoleModel.php b/app/Model/ProjectUserRoleModel.php new file mode 100644 index 0000000..cf4e0de --- /dev/null +++ b/app/Model/ProjectUserRoleModel.php @@ -0,0 +1,275 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; +use Kanboard\Core\Security\Role; + +/** + * Project User Role + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class ProjectUserRoleModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'project_has_users'; + + /** + * Get the list of active project for the given user + * + * @access public + * @param integer $user_id + * @return array + */ + public function getActiveProjectsByUser($user_id) + { + return $this->getProjectsByUser($user_id, array(ProjectModel::ACTIVE)); + } + + /** + * Get the list of project visible for the given user + * + * @access public + * @param integer $user_id + * @param array $status + * @return array + */ + public function getProjectsByUser($user_id, $status = array(ProjectModel::ACTIVE, ProjectModel::INACTIVE)) + { + $userProjects = $this->db + ->hashtable(ProjectModel::TABLE) + ->eq(self::TABLE.'.user_id', $user_id) + ->in(ProjectModel::TABLE.'.is_active', $status) + ->join(self::TABLE, 'project_id', 'id') + ->getAll(ProjectModel::TABLE.'.id', ProjectModel::TABLE.'.name'); + + $groupProjects = $this->projectGroupRoleModel->getProjectsByUser($user_id, $status); + $projects = $userProjects + $groupProjects; + + asort($projects); + + return $projects; + } + + /** + * For a given project get the role of the specified user + * + * @access public + * @param integer $project_id + * @param integer $user_id + * @return string + */ + public function getUserRole($project_id, $user_id) + { + $role = $this->db->table(self::TABLE)->eq('user_id', $user_id)->eq('project_id', $project_id)->findOneColumn('role'); + + if (empty($role)) { + $role = $this->projectGroupRoleModel->getUserRole($project_id, $user_id); + if (empty($role)) { + $role = ""; // force use of the cache + } + } + + return $role; + } + + /** + * Get all users associated directly to the project + * + * @access public + * @param integer $project_id + * @return array + */ + public function getUsers($project_id) + { + return $this->db->table(self::TABLE) + ->columns( + UserModel::TABLE.'.id', + UserModel::TABLE.'.username', + UserModel::TABLE.'.name', + UserModel::TABLE.'.email', + self::TABLE.'.role' + ) + ->join(UserModel::TABLE, 'id', 'user_id') + ->eq('project_id', $project_id) + ->asc(UserModel::TABLE.'.username') + ->asc(UserModel::TABLE.'.name') + ->findAll(); + } + + /** + * Get all users (fetch users from groups) + * + * @access public + * @param integer $project_id + * @return array + */ + public function getAllUsers($project_id) + { + $userMembers = $this->getUsers($project_id); + $groupMembers = $this->projectGroupRoleModel->getUsers($project_id); + $members = array_merge($userMembers, $groupMembers); + + return $this->userModel->prepareList($members); + } + + /** + * Get users grouped by role + * + * @access public + * @param integer $project_id Project id + * @return array + */ + public function getAllUsersGroupedByRole($project_id) + { + $users = array(); + + $userMembers = $this->getUsers($project_id); + $groupMembers = $this->projectGroupRoleModel->getUsers($project_id); + $members = array_merge($userMembers, $groupMembers); + + foreach ($members as $user) { + if (! isset($users[$user['role']])) { + $users[$user['role']] = array(); + } + + $users[$user['role']][$user['id']] = $user['name'] ?: $user['username']; + } + + return $users; + } + + /** + * Get list of users that can be assigned to a task (only Manager and Member) + * + * @access public + * @param integer $project_id + * @return array + */ + public function getAssignableUsers($project_id) + { + $userMembers = $this->db->table(self::TABLE) + ->columns(UserModel::TABLE.'.id', UserModel::TABLE.'.username', UserModel::TABLE.'.name') + ->join(UserModel::TABLE, 'id', 'user_id') + ->eq(UserModel::TABLE.'.is_active', 1) + ->eq(self::TABLE.'.project_id', $project_id) + ->neq(self::TABLE.'.role', Role::PROJECT_VIEWER) + ->findAll(); + + $groupMembers = $this->projectGroupRoleModel->getAssignableUsers($project_id); + $members = array_merge($userMembers, $groupMembers); + + return $this->userModel->prepareList($members); + } + + /** + * Get list of users that can be assigned to a task (only Manager and Member) + * + * @access public + * @param integer $project_id Project id + * @param bool $unassigned Prepend the 'Unassigned' value + * @param bool $everybody Prepend the 'Everbody' value + * @param bool $singleUser If there is only one user return only this user + * @return array + */ + public function getAssignableUsersList($project_id, $unassigned = true, $everybody = false, $singleUser = false) + { + $users = $this->getAssignableUsers($project_id); + + if ($singleUser && count($users) === 1) { + return $users; + } + + if ($unassigned) { + $users = array(t('Unassigned')) + $users; + } + + if ($everybody) { + $users = array(UserModel::EVERYBODY_ID => t('Everybody')) + $users; + } + + return $users; + } + + /** + * Add a user to the project + * + * @access public + * @param integer $project_id + * @param integer $user_id + * @param string $role + * @return boolean + */ + public function addUser($project_id, $user_id, $role) + { + return $this->db->table(self::TABLE)->insert(array( + 'user_id' => $user_id, + 'project_id' => $project_id, + 'role' => $role, + )); + } + + /** + * Remove a user from the project + * + * @access public + * @param integer $project_id + * @param integer $user_id + * @return boolean + */ + public function removeUser($project_id, $user_id) + { + return $this->db->table(self::TABLE)->eq('user_id', $user_id)->eq('project_id', $project_id)->remove(); + } + + /** + * Change a user role for the project + * + * @access public + * @param integer $project_id + * @param integer $user_id + * @param string $role + * @return boolean + */ + public function changeUserRole($project_id, $user_id, $role) + { + return $this->db->table(self::TABLE) + ->eq('user_id', $user_id) + ->eq('project_id', $project_id) + ->update(array( + 'role' => $role, + )); + } + + /** + * Copy user access from a project to another one + * + * @param integer $project_src_id + * @param integer $project_dst_id + * @return boolean + */ + public function duplicate($project_src_id, $project_dst_id) + { + $rows = $this->db->table(self::TABLE)->eq('project_id', $project_src_id)->findAll(); + + foreach ($rows as $row) { + $result = $this->db->table(self::TABLE)->save(array( + 'project_id' => $project_dst_id, + 'user_id' => $row['user_id'], + 'role' => $row['role'], + )); + + if (! $result) { + return false; + } + } + + return true; + } +} diff --git a/app/Model/RememberMeSessionModel.php b/app/Model/RememberMeSessionModel.php new file mode 100644 index 0000000..75f14b4 --- /dev/null +++ b/app/Model/RememberMeSessionModel.php @@ -0,0 +1,152 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; +use Kanboard\Core\Security\Token; + +/** + * Remember Me Model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class RememberMeSessionModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'remember_me'; + + /** + * Expiration (60 days) + * + * @var integer + */ + const EXPIRATION = 5184000; + + /** + * Get a remember me record + * + * @access public + * @param $token + * @param $sequence + * @return mixed + */ + public function find($token, $sequence) + { + return $this->db + ->table(self::TABLE) + ->eq('token', $token) + ->eq('sequence', $sequence) + ->gt('expiration', time()) + ->findOne(); + } + + /** + * Get all sessions for a given user + * + * @access public + * @param integer $user_id User id + * @return array + */ + public function getAll($user_id) + { + return $this->db + ->table(self::TABLE) + ->eq('user_id', $user_id) + ->desc('date_creation') + ->columns('id', 'ip', 'user_agent', 'date_creation', 'expiration') + ->findAll(); + } + + /** + * Create a new RememberMe session + * + * @access public + * @param integer $user_id User id + * @param string $ip IP Address + * @param string $user_agent User Agent + * @return array + */ + public function create($user_id, $ip, $user_agent) + { + $token = hash('sha256', $user_id.$user_agent.$ip.Token::getToken()); + $sequence = Token::getToken(); + $expiration = time() + self::EXPIRATION; + + $this->cleanup($user_id); + + $this + ->db + ->table(self::TABLE) + ->insert(array( + 'user_id' => $user_id, + 'ip' => $ip, + 'user_agent' => substr($user_agent, 0, 255), + 'token' => $token, + 'sequence' => $sequence, + 'expiration' => $expiration, + 'date_creation' => time(), + )); + + return array( + 'token' => $token, + 'sequence' => $sequence, + 'expiration' => $expiration, + ); + } + + /** + * Remove a session record + * + * @access public + * @param integer $session_id Session id + * @return mixed + */ + public function remove($session_id) + { + return $this->db + ->table(self::TABLE) + ->eq('id', $session_id) + ->remove(); + } + + /** + * Remove old sessions for a given user + * + * @access public + * @param integer $user_id User id + * @return bool + */ + public function cleanup($user_id) + { + return $this->db + ->table(self::TABLE) + ->eq('user_id', $user_id) + ->lt('expiration', time()) + ->remove(); + } + + /** + * Return a new sequence token and update the database + * + * @access public + * @param string $token Session token + * @return string + */ + public function updateSequence($token) + { + $sequence = Token::getToken(); + + $this + ->db + ->table(self::TABLE) + ->eq('token', $token) + ->update(array('sequence' => $sequence)); + + return $sequence; + } +} diff --git a/app/Model/SettingModel.php b/app/Model/SettingModel.php new file mode 100644 index 0000000..e3cd4f9 --- /dev/null +++ b/app/Model/SettingModel.php @@ -0,0 +1,127 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Application Settings + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +abstract class SettingModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'settings'; + + /** + * Prepare data before save + * + * @abstract + * @access public + * @param array $values + * @return array + */ + abstract public function prepare(array $values); + + /** + * Get all settings + * + * @access public + * @return array + */ + public function getAll() + { + return $this->db->hashtable(self::TABLE)->getAll('option', 'value'); + } + + /** + * Get a setting value + * + * @access public + * @param string $name + * @param string $default + * @return mixed + */ + public function getOption($name, $default = '') + { + $value = $this->db + ->table(self::TABLE) + ->eq('option', $name) + ->findOneColumn('value'); + + return $value === null || $value === false || $value === '' ? $default : $value; + } + + /** + * Return true if a setting exists + * + * @access public + * @param string $name + * @return boolean + */ + public function exists($name) + { + return $this->db + ->table(self::TABLE) + ->eq('option', $name) + ->exists(); + } + + /** + * Update or insert new settings + * + * @access public + * @param array $values + * @return boolean + */ + public function save(array $values) + { + $results = array(); + $values = $this->prepare($values); + $user_id = $this->userSession->getId(); + $timestamp = time(); + + $this->db->startTransaction(); + + foreach ($values as $option => $value) { + if ($this->exists($option)) { + $results[] = $this->db->table(self::TABLE)->eq('option', $option)->update(array( + 'value' => $value, + 'changed_on' => $timestamp, + 'changed_by' => $user_id, + )); + } else { + $results[] = $this->db->table(self::TABLE)->insert(array( + 'option' => $option, + 'value' => $value, + 'changed_on' => $timestamp, + 'changed_by' => $user_id, + )); + } + } + + $this->db->closeTransaction(); + + return ! in_array(false, $results, true); + } + + /** + * Remove a setting + * + * @access public + * @param string $option + * @return bool + */ + public function remove($option) + { + return $this->db->table(self::TABLE) + ->eq('option', $option) + ->remove(); + } +} diff --git a/app/Model/SubtaskModel.php b/app/Model/SubtaskModel.php new file mode 100644 index 0000000..ee86e93 --- /dev/null +++ b/app/Model/SubtaskModel.php @@ -0,0 +1,337 @@ +<?php + +namespace Kanboard\Model; + +use PicoDb\Database; +use Kanboard\Core\Base; + +/** + * Subtask Model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class SubtaskModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'subtasks'; + + /** + * Subtask status + * + * @var integer + */ + const STATUS_TODO = 0; + const STATUS_INPROGRESS = 1; + const STATUS_DONE = 2; + + /** + * Events + * + * @var string + */ + const EVENT_UPDATE = 'subtask.update'; + const EVENT_CREATE = 'subtask.create'; + const EVENT_DELETE = 'subtask.delete'; + const EVENT_CREATE_UPDATE = 'subtask.create_update'; + + /** + * Get projectId from subtaskId + * + * @access public + * @param integer $subtaskId + * @return integer + */ + public function getProjectId($subtaskId) + { + return $this->db + ->table(self::TABLE) + ->eq(self::TABLE.'.id', $subtaskId) + ->join(TaskModel::TABLE, 'id', 'task_id') + ->findOneColumn(TaskModel::TABLE . '.project_id') ?: 0; + } + + /** + * Get available status + * + * @access public + * @return string[] + */ + public function getStatusList() + { + return array( + self::STATUS_TODO => 'Todo', + self::STATUS_INPROGRESS => 'In progress', + self::STATUS_DONE => 'Done', + ); + } + + /** + * Get common query + * + * @return \PicoDb\Table + */ + public function getQuery() + { + return $this->db + ->table(self::TABLE) + ->columns( + self::TABLE.'.*', + UserModel::TABLE.'.username', + UserModel::TABLE.'.name' + ) + ->subquery($this->subtaskTimeTrackingModel->getTimerQuery($this->userSession->getId()), 'timer_start_date') + ->join(UserModel::TABLE, 'id', 'user_id') + ->asc(self::TABLE.'.position'); + } + + /** + * Count by assignee and task status. + * + * @param integer $userId + * @return integer + */ + public function countByAssigneeAndTaskStatus($userId) + { + $query = $this->db->table(self::TABLE) + ->eq('user_id', $userId) + ->eq(TaskModel::TABLE.'.is_active', TaskModel::STATUS_OPEN) + ->join(TaskModel::TABLE, 'id', 'task_id'); + + $this->hook->reference('model:subtask:count:query', $query); + + return $query->count(); + } + + /** + * Get all subtasks for a given task + * + * @access public + * @param integer $taskId + * @return array + */ + public function getAll($taskId) + { + return $this->subtaskListFormatter + ->withQuery($this->getQuery()->eq('task_id', $taskId)) + ->format(); + } + + /** + * Get subtasks for a list of tasks + * + * @param array $taskIds + * @return array + */ + public function getAllByTaskIds(array $taskIds) + { + if (empty($taskIds)) { + return array(); + } + + return $this->subtaskListFormatter + ->withQuery($this->getQuery()->in('task_id', $taskIds)) + ->format(); + } + + /** + * Get subtasks for a list of tasks and a given assignee + * + * @param array $taskIds + * @param integer $userId + * @return array + */ + public function getAllByTaskIdsAndAssignee(array $taskIds, $userId) + { + if (empty($taskIds)) { + return array(); + } + + return $this->subtaskListFormatter + ->withQuery($this->getQuery()->in('task_id', $taskIds)->eq(self::TABLE.'.user_id', $userId)) + ->format(); + } + + /** + * Get a subtask by the id + * + * @access public + * @param integer $subtaskId + * @return array + */ + public function getById($subtaskId) + { + return $this->db->table(self::TABLE)->eq('id', $subtaskId)->findOne(); + } + + /** + * Get subtask with additional information + * + * @param integer $subtaskId + * @return array|null + */ + public function getByIdWithDetails($subtaskId) + { + $subtasks = $this->subtaskListFormatter + ->withQuery($this->getQuery()->eq(self::TABLE.'.id', $subtaskId)) + ->format(); + + if (! empty($subtasks)) { + return $subtasks[0]; + } + + return null; + } + + /** + * Get the position of the last column for a given project + * + * @access public + * @param integer $taskId + * @return integer + */ + public function getLastPosition($taskId) + { + return (int) $this->db + ->table(self::TABLE) + ->eq('task_id', $taskId) + ->desc('position') + ->findOneColumn('position'); + } + + /** + * Create a new subtask + * + * @access public + * @param array $values Form values + * @return bool|integer + */ + public function create(array $values) + { + $this->prepareCreation($values); + $subtaskId = $this->db->table(self::TABLE)->persist($values); + + if ($subtaskId !== false) { + $this->subtaskTimeTrackingModel->updateTaskTimeTracking($values['task_id']); + $this->queueManager->push($this->subtaskEventJob->withParams( + $subtaskId, + array(self::EVENT_CREATE_UPDATE, self::EVENT_CREATE) + )); + } + + return $subtaskId; + } + + /** + * Update a subtask + * + * @access public + * @param array $values + * @param bool $fireEvent + * @return bool + */ + public function update(array $values, $fireEvent = true) + { + $this->prepare($values); + $updates = $values; + unset($updates['id']); + $result = $this->db->table(self::TABLE)->eq('id', $values['id'])->save($updates); + + if ($result) { + $subtask = $this->getById($values['id']); + $this->subtaskTimeTrackingModel->updateTaskTimeTracking($subtask['task_id']); + + if ($fireEvent) { + $this->queueManager->push($this->subtaskEventJob->withParams( + $subtask['id'], + array(self::EVENT_CREATE_UPDATE, self::EVENT_UPDATE), + $values + )); + } + } + + return $result; + } + + /** + * Remove + * + * @access public + * @param integer $subtaskId + * @return bool + */ + public function remove($subtaskId) + { + $this->subtaskEventJob->execute($subtaskId, array(self::EVENT_DELETE)); + + $subtask = $this->getById($subtaskId); + $result = $this->db->table(self::TABLE)->eq('id', $subtaskId)->remove(); + + $this->subtaskTimeTrackingModel->updateTaskTimeTracking($subtask['task_id']); + + return $result; + } + + /** + * Duplicate all subtasks to another task + * + * @access public + * @param integer $srcTaskId + * @param integer $dstTaskId + * @return bool + */ + public function duplicate($srcTaskId, $dstTaskId) + { + return $this->db->transaction(function (Database $db) use ($srcTaskId, $dstTaskId) { + + $subtasks = $db->table(SubtaskModel::TABLE) + ->columns('title', 'time_estimated', 'position', 'user_id') + ->eq('task_id', $srcTaskId) + ->asc('position') + ->findAll(); + + foreach ($subtasks as &$subtask) { + $subtask['task_id'] = $dstTaskId; + + if (! $db->table(SubtaskModel::TABLE)->save($subtask)) { + return false; + } + } + }); + } + + /** + * Prepare data before insert/update + * + * @access protected + * @param array $values Form values + */ + protected function prepare(array &$values) + { + $this->helper->model->removeFields($values, array('another_subtask')); + $this->helper->model->resetFields($values, array('time_estimated', 'time_spent')); + $this->hook->reference('model:subtask:modification:prepare', $values); + } + + /** + * Prepare data before insert + * + * @access protected + * @param array $values Form values + */ + protected function prepareCreation(array &$values) + { + $this->prepare($values); + + $values['position'] = $this->getLastPosition($values['task_id']) + 1; + $values['status'] = isset($values['status']) ? $values['status'] : self::STATUS_TODO; + $values['time_estimated'] = isset($values['time_estimated']) ? $values['time_estimated'] : 0; + $values['time_spent'] = isset($values['time_spent']) ? $values['time_spent'] : 0; + $values['user_id'] = isset($values['user_id']) ? $values['user_id'] : 0; + $this->hook->reference('model:subtask:creation:prepare', $values); + } +} diff --git a/app/Model/SubtaskPositionModel.php b/app/Model/SubtaskPositionModel.php new file mode 100644 index 0000000..3c26465 --- /dev/null +++ b/app/Model/SubtaskPositionModel.php @@ -0,0 +1,47 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Class SubtaskPositionModel + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class SubtaskPositionModel extends Base +{ + /** + * Change subtask position + * + * @access public + * @param integer $task_id + * @param integer $subtask_id + * @param integer $position + * @return boolean + */ + public function changePosition($task_id, $subtask_id, $position) + { + if ($position < 1 || $position > $this->db->table(SubtaskModel::TABLE)->eq('task_id', $task_id)->count()) { + return false; + } + + $subtask_ids = $this->db->table(SubtaskModel::TABLE)->eq('task_id', $task_id)->neq('id', $subtask_id)->asc('position')->findAllByColumn('id'); + $offset = 1; + $results = array(); + + foreach ($subtask_ids as $current_subtask_id) { + if ($offset == $position) { + $offset++; + } + + $results[] = $this->db->table(SubtaskModel::TABLE)->eq('id', $current_subtask_id)->update(array('position' => $offset)); + $offset++; + } + + $results[] = $this->db->table(SubtaskModel::TABLE)->eq('id', $subtask_id)->update(array('position' => $position)); + + return !in_array(false, $results, true); + } +} diff --git a/app/Model/SubtaskStatusModel.php b/app/Model/SubtaskStatusModel.php new file mode 100644 index 0000000..c99d605 --- /dev/null +++ b/app/Model/SubtaskStatusModel.php @@ -0,0 +1,88 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Class SubtaskStatusModel + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class SubtaskStatusModel extends Base +{ + /** + * Get the subtask in progress for this user + * + * @access public + * @param integer $user_id + * @return array + */ + public function getSubtaskInProgress($user_id) + { + return $this->db->table(SubtaskModel::TABLE) + ->eq('status', SubtaskModel::STATUS_INPROGRESS) + ->eq('user_id', $user_id) + ->findOne(); + } + + /** + * Return true if the user have a subtask in progress + * + * @access public + * @param integer $user_id + * @return boolean + */ + public function hasSubtaskInProgress($user_id) + { + return $this->configModel->get('subtask_restriction') == 1 && + $this->db->table(SubtaskModel::TABLE) + ->eq('status', SubtaskModel::STATUS_INPROGRESS) + ->eq('user_id', $user_id) + ->exists(); + } + + /** + * Change the status of subtask + * + * @access public + * @param integer $subtask_id + * @return boolean|integer + */ + public function toggleStatus($subtask_id) + { + $subtask = $this->subtaskModel->getById($subtask_id); + $status = ($subtask['status'] + 1) % 3; + + $values = array( + 'id' => $subtask['id'], + 'status' => $status, + 'task_id' => $subtask['task_id'], + ); + + if (empty($subtask['user_id']) && $this->userSession->isLogged()) { + $values['user_id'] = $this->userSession->getId(); + $subtask['user_id'] = $values['user_id']; + } + + $this->subtaskTimeTrackingModel->toggleTimer($subtask_id, $subtask['user_id'], $status); + + return $this->subtaskModel->update($values) ? $status : false; + } + + /** + * Close all subtasks of a task + * + * @access public + * @param integer $task_id + * @return boolean + */ + public function closeAll($task_id) + { + return $this->db + ->table(SubtaskModel::TABLE) + ->eq('task_id', $task_id) + ->update(array('status' => SubtaskModel::STATUS_DONE)); + } +} diff --git a/app/Model/SubtaskTaskConversionModel.php b/app/Model/SubtaskTaskConversionModel.php new file mode 100644 index 0000000..f9e8f3e --- /dev/null +++ b/app/Model/SubtaskTaskConversionModel.php @@ -0,0 +1,53 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Class SubtaskTaskConversionModel + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class SubtaskTaskConversionModel extends Base +{ + /** + * Convert a subtask to a task + * + * @access public + * @param integer $project_id + * @param integer $subtask_id + * @return integer + */ + public function convertToTask($project_id, $subtask_id) + { + $subtask = $this->subtaskModel->getById($subtask_id); + $parent_task = $this->taskFinderModel->getById($subtask['task_id']); + + $task_id = $this->taskCreationModel->create(array( + 'project_id' => $project_id, + 'title' => $subtask['title'], + 'time_estimated' => $subtask['time_estimated'], + 'time_spent' => $subtask['time_spent'], + 'owner_id' => $subtask['user_id'], + 'swimlane_id' => $parent_task['swimlane_id'], + 'priority' => $parent_task['priority'], + 'column_id' => $parent_task['column_id'], + 'category_id' => $parent_task['category_id'], + 'color_id' => $parent_task['color_id'] + )); + + if ($task_id !== false) { + $link = $this->linkModel->getByLabel('is a child of'); + if ($link) { + $this->taskLinkModel->create($task_id, $subtask['task_id'], $link['id']); + } + + $this->tagDuplicationModel->duplicateTaskTags($parent_task['id'], $task_id); + $this->subtaskModel->remove($subtask_id); + } + + return $task_id; + } +} diff --git a/app/Model/SubtaskTimeTrackingModel.php b/app/Model/SubtaskTimeTrackingModel.php new file mode 100644 index 0000000..a48bb43 --- /dev/null +++ b/app/Model/SubtaskTimeTrackingModel.php @@ -0,0 +1,291 @@ +<?php + +namespace Kanboard\Model; + +use DateTime; +use Kanboard\Core\Base; + +/** + * Subtask time tracking + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class SubtaskTimeTrackingModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'subtask_time_tracking'; + + /** + * Get query to check if a timer is started for the given user and subtask + * + * @access public + * @param integer $user_id User id + * @return string + */ + public function getTimerQuery($user_id) + { + $sql = $this->db + ->table(self::TABLE) + ->columns('start') + ->eq($this->db->escapeIdentifier('user_id', self::TABLE), $user_id) + ->eq($this->db->escapeIdentifier('end', self::TABLE), 0) + ->eq($this->db->escapeIdentifier('subtask_id', self::TABLE), SubtaskModel::TABLE.'.id') + ->limit(1) + ->buildSelectQuery(); + // need to interpolate values into the SQL text for use as a subquery + // in SubtaskModel::getQuery() + $sql = substr_replace($sql, $user_id, strpos($sql, '?'), 1); + $sql = substr_replace($sql, 0, strpos($sql, '?'), 1); + $sql = substr_replace($sql, SubtaskModel::TABLE.'.id', strpos($sql, '?'), 1); + return $sql; + } + + /** + * Get query for user timesheet (pagination) + * + * @access public + * @param integer $user_id User id + * @return \PicoDb\Table + */ + public function getUserQuery($user_id) + { + return $this->db + ->table(self::TABLE) + ->columns( + $this->db->escapeIdentifier('id', self::TABLE), + $this->db->escapeIdentifier('subtask_id', self::TABLE), + $this->db->escapeIdentifier('end', self::TABLE), + $this->db->escapeIdentifier('start', self::TABLE), + $this->db->escapeIdentifier('time_spent', self::TABLE), + $this->db->escapeIdentifier('task_id', SubtaskModel::TABLE), + $this->db->escapeIdentifier('title', SubtaskModel::TABLE).' AS subtask_title', + $this->db->escapeIdentifier('title', TaskModel::TABLE).' AS task_title', + $this->db->escapeIdentifier('project_id', TaskModel::TABLE), + $this->db->escapeIdentifier('color_id', TaskModel::TABLE), + ) + ->join(SubtaskModel::TABLE, 'id', 'subtask_id') + ->join(TaskModel::TABLE, 'id', 'task_id', SubtaskModel::TABLE) + ->eq($this->db->escapeIdentifier('user_id', self::TABLE), $user_id); + } + + /** + * Get query for task timesheet (pagination) + * + * @access public + * @param integer $task_id Task id + * @return \PicoDb\Table + */ + public function getTaskQuery($task_id) + { + return $this->db + ->table(self::TABLE) + ->columns( + $this->db->escapeIdentifier('id', self::TABLE), + $this->db->escapeIdentifier('subtask_id', self::TABLE), + $this->db->escapeIdentifier('end', self::TABLE), + $this->db->escapeIdentifier('start', self::TABLE), + $this->db->escapeIdentifier('time_spent', self::TABLE), + $this->db->escapeIdentifier('user_id', self::TABLE), + $this->db->escapeIdentifier('task_id', SubtaskModel::TABLE), + $this->db->escapeIdentifier('title', SubtaskModel::TABLE).' AS subtask_title', + $this->db->escapeIdentifier('project_id', TaskModel::TABLE), + $this->db->escapeIdentifier('username', UserModel::TABLE), + $this->db->escapeIdentifier('name', UserModel::TABLE).' AS user_fullname', + ) + ->join(SubtaskModel::TABLE, 'id', 'subtask_id') + ->join(TaskModel::TABLE, 'id', 'task_id', SubtaskModel::TABLE) + ->join(UserModel::TABLE, 'id', 'user_id', self::TABLE) + ->eq($this->db->escapeIdentifier('id', TaskModel::TABLE), $task_id); + } + + /** + * Get all recorded time slots for a given user + * + * @access public + * @param integer $user_id User id + * @return array + */ + public function getUserTimesheet($user_id) + { + return $this->db + ->table(self::TABLE) + ->eq('user_id', $user_id) + ->findAll(); + } + + /** + * Return true if a timer is started for this use and subtask + * + * @access public + * @param integer $subtask_id + * @param integer $user_id + * @return boolean + */ + public function hasTimer($subtask_id, $user_id) + { + return $this->db->table(self::TABLE)->eq('subtask_id', $subtask_id)->eq('user_id', $user_id)->eq('end', 0)->exists(); + } + + /** + * Start or stop timer according to subtask status + * + * @access public + * @param integer $subtask_id + * @param integer $user_id + * @param integer $status + * @return boolean + */ + public function toggleTimer($subtask_id, $user_id, $status) + { + if ($this->configModel->get('subtask_time_tracking') == 1) { + if ($status == SubtaskModel::STATUS_INPROGRESS) { + return $this->subtaskTimeTrackingModel->logStartTime($subtask_id, $user_id); + } elseif ($status == SubtaskModel::STATUS_DONE) { + return $this->subtaskTimeTrackingModel->logEndTime($subtask_id, $user_id); + } + } + + return false; + } + + /** + * Log start time + * + * @access public + * @param integer $subtask_id + * @param integer $user_id + * @return boolean + */ + public function logStartTime($subtask_id, $user_id) + { + return + ! $this->hasTimer($subtask_id, $user_id) && + $this->db + ->table(self::TABLE) + ->insert(array('subtask_id' => $subtask_id, 'user_id' => $user_id, 'start' => time(), 'end' => 0)); + } + + /** + * Log end time + * + * @access public + * @param integer $subtask_id + * @param integer $user_id + * @return boolean + */ + public function logEndTime($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) + ->eq('subtask_id', $subtask_id) + ->eq('user_id', $user_id) + ->eq('end', 0) + ->update(array( + '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) + { + $hook = 'model:subtask-time-tracking:calculate:time-spent'; + $start_time = $this->db + ->table(self::TABLE) + ->eq('subtask_id', $subtask_id) + ->eq('user_id', $user_id) + ->eq('end', 0) + ->findOneColumn('start'); + + if (empty($start_time)) { + return 0; + } + + $end = new DateTime; + $start = new DateTime; + $start->setTimestamp($start_time); + + if ($this->hook->exists($hook)) { + return $this->hook->first($hook, array( + 'user_id' => $user_id, + 'start' => $start, + 'end' => $end, + )); + } + + return $this->dateParser->getHours($start, $end); + } + + /** + * 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->subtaskModel->getById($subtask_id); + + return $this->subtaskModel->update(array( + 'id' => $subtask['id'], + 'time_spent' => $subtask['time_spent'] + $time_spent, + 'task_id' => $subtask['task_id'], + ), false); + } + + /** + * Update task time tracking based on subtasks time tracking + * + * @access public + * @param integer $task_id Task id + * @return bool + */ + public function updateTaskTimeTracking($task_id) + { + $values = $this->calculateSubtaskTime($task_id); + + return $this->db + ->table(TaskModel::TABLE) + ->eq('id', $task_id) + ->update($values); + } + + /** + * Sum time spent and time estimated for all subtasks + * + * @access public + * @param integer $task_id Task id + * @return array + */ + public function calculateSubtaskTime($task_id) + { + return $this->db + ->table(SubtaskModel::TABLE) + ->eq('task_id', $task_id) + ->columns( + 'SUM(time_spent) AS time_spent', + 'SUM(time_estimated) AS time_estimated' + ) + ->findOne(); + } +} diff --git a/app/Model/SwimlaneModel.php b/app/Model/SwimlaneModel.php new file mode 100644 index 0000000..b70a9a8 --- /dev/null +++ b/app/Model/SwimlaneModel.php @@ -0,0 +1,459 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Swimlanes + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class SwimlaneModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'swimlanes'; + + /** + * Value for active swimlanes + * + * @var integer + */ + const ACTIVE = 1; + + /** + * Value for inactive swimlanes + * + * @var integer + */ + const INACTIVE = 0; + + /** + * Get a swimlane by the id + * + * @access public + * @param integer $swimlaneId + * @return array + */ + public function getById($swimlaneId) + { + return $this->db->table(self::TABLE)->eq('id', $swimlaneId)->findOne(); + } + + /** + * Get the swimlane name by the id + * + * @access public + * @param integer $swimlaneId + * @return string + */ + public function getNameById($swimlaneId) + { + return $this->db->table(self::TABLE) + ->eq('id', $swimlaneId) + ->findOneColumn('name'); + } + + /** + * Get a swimlane id by the project and the name + * + * @access public + * @param integer $projectId Project id + * @param string $name Name + * @return integer + */ + public function getIdByName($projectId, $name) + { + return (int) $this->db->table(self::TABLE) + ->eq('project_id', $projectId) + ->eq('name', $name) + ->findOneColumn('id'); + } + + /** + * Get a swimlane by the project and the name + * + * @access public + * @param integer $projectId Project id + * @param string $name Swimlane name + * @return array + */ + public function getByName($projectId, $name) + { + return $this->db->table(self::TABLE) + ->eq('project_id', $projectId) + ->eq('name', $name) + ->findOne(); + } + + /** + * Get first active swimlane for a project + * + * @access public + * @param integer $projectId + * @return array|null + */ + public function getFirstActiveSwimlane($projectId) + { + return $this->db->table(self::TABLE) + ->eq('project_id', $projectId) + ->eq('is_active', 1) + ->asc('position') + ->findOne(); + } + + /** + * Get first active swimlaneId + * + * @access public + * @param int $projectId + * @return int + */ + public function getFirstActiveSwimlaneId($projectId) + { + return (int) $this->db->table(self::TABLE) + ->eq('project_id', $projectId) + ->eq('is_active', 1) + ->asc('position') + ->findOneColumn('id'); + } + + /** + * Get all swimlanes for a given project + * + * @access public + * @param integer $projectId + * @return array + */ + public function getAll($projectId) + { + return $this->db + ->table(self::TABLE) + ->eq('project_id', $projectId) + ->orderBy('position', 'asc') + ->findAll(); + } + + /** + * Get the list of swimlanes by status + * + * @access public + * @param integer $projectId + * @param integer $status + * @return array + */ + public function getAllByStatus($projectId, $status = self::ACTIVE) + { + $query = $this->db + ->table(self::TABLE) + ->eq('project_id', $projectId) + ->eq('is_active', $status); + + if ($status == self::ACTIVE) { + $query->asc('position'); + } else { + $query->asc('name'); + } + + return $query->findAll(); + } + + /** + * Get all swimlanes with task count + * + * @access public + * @param integer $project_id Project id + * @return array + */ + public function getAllWithTaskCount($project_id) + { + $result = array( + 'active' => array(), + 'inactive' => array(), + ); + + $swimlanes = $this->db->table(self::TABLE) + ->columns('id', 'name', 'description', 'project_id', 'position', 'is_active', 'task_limit') + ->subquery("SELECT COUNT(*) FROM ".TaskModel::TABLE." WHERE swimlane_id=".self::TABLE.".id AND is_active='1'", 'nb_open_tasks') + ->subquery("SELECT COUNT(*) FROM ".TaskModel::TABLE." WHERE swimlane_id=".self::TABLE.".id AND is_active='0'", 'nb_closed_tasks') + ->eq('project_id', $project_id) + ->asc('position') + ->asc('name') + ->findAll(); + + foreach ($swimlanes as $swimlane) { + if ($swimlane['is_active']) { + $result['active'][] = $swimlane; + } else { + $result['inactive'][] = $swimlane; + } + } + + return $result; + } + + /** + * Get list of all swimlanes + * + * @access public + * @param integer $projectId Project id + * @param boolean $prepend Prepend default value + * @param boolean $onlyActive Return only active swimlanes + * @return array + */ + public function getList($projectId, $prepend = false, $onlyActive = false) + { + $swimlanes = array(); + + if ($prepend) { + $swimlanes[-1] = t('All swimlanes'); + } + + return $swimlanes + $this->db + ->hashtable(self::TABLE) + ->eq('project_id', $projectId) + ->in('is_active', $onlyActive ? array(self::ACTIVE) : array(self::ACTIVE, self::INACTIVE)) + ->orderBy('position', 'asc') + ->getAll('id', 'name'); + } + + /** + * Add a new swimlane + * + * @access public + * @param int $projectId + * @param string $name + * @param string $description + * @return bool|int + */ + public function create($projectId, $name, $description = '', $task_limit = 0) + { + if (! $this->projectModel->exists($projectId)) { + return 0; + } + + return $this->db->table(self::TABLE)->persist(array( + 'project_id' => $projectId, + 'name' => $name, + 'description' => $description, + 'position' => $this->getLastPosition($projectId), + 'is_active' => 1, + 'task_limit' => intval($task_limit), + )); + } + + /** + * Update a swimlane + * + * @access public + * @param integer $swimlaneId + * @param array $values + * @return bool + */ + public function update($swimlaneId, array $values) + { + unset($values['id']); + unset($values['project_id']); + + return $this->db + ->table(self::TABLE) + ->eq('id', $swimlaneId) + ->update($values); + } + + /** + * Get the last position of a swimlane + * + * @access public + * @param integer $projectId + * @return integer + */ + public function getLastPosition($projectId) + { + return $this->db + ->table(self::TABLE) + ->eq('project_id', $projectId) + ->eq('is_active', 1) + ->count() + 1; + } + + /** + * Disable a swimlane + * + * @access public + * @param integer $projectId + * @param integer $swimlaneId + * @return bool + */ + public function disable($projectId, $swimlaneId) + { + $result = $this->db + ->table(self::TABLE) + ->eq('id', $swimlaneId) + ->eq('project_id', $projectId) + ->update(array( + 'is_active' => self::INACTIVE, + 'position' => 0, + )); + + if ($result) { + $this->updatePositions($projectId); + } + + return $result; + } + + /** + * Enable a swimlane + * + * @access public + * @param integer $projectId + * @param integer $swimlaneId + * @return bool + */ + public function enable($projectId, $swimlaneId) + { + return $this->db + ->table(self::TABLE) + ->eq('id', $swimlaneId) + ->eq('project_id', $projectId) + ->update(array( + 'is_active' => self::ACTIVE, + 'position' => $this->getLastPosition($projectId), + )); + } + + /** + * Remove a swimlane + * + * @access public + * @param integer $projecId + * @param integer $swimlaneId + * @return bool + */ + public function remove($projecId, $swimlaneId) + { + $this->db->startTransaction(); + + if ($this->db->table(TaskModel::TABLE)->eq('swimlane_id', $swimlaneId)->exists()) { + $this->db->cancelTransaction(); + return false; + } + + if (! $this->db->table(self::TABLE)->eq('id', $swimlaneId)->eq('project_id', $projecId)->remove()) { + $this->db->cancelTransaction(); + return false; + } + + $this->updatePositions($projecId); + $this->db->closeTransaction(); + + return true; + } + + /** + * Update swimlane positions after disabling or removing a swimlane + * + * @access public + * @param integer $projectId + * @return boolean + */ + public function updatePositions($projectId) + { + $position = 0; + $swimlanes = $this->db + ->table(self::TABLE) + ->eq('project_id', $projectId) + ->eq('is_active', 1) + ->asc('position') + ->asc('id') + ->findAllByColumn('id'); + + if (! $swimlanes) { + return false; + } + + foreach ($swimlanes as $swimlane_id) { + $this->db->table(self::TABLE) + ->eq('id', $swimlane_id) + ->update(array('position' => ++$position)); + } + + return true; + } + + /** + * Change swimlane position + * + * @access public + * @param integer $projectId + * @param integer $swimlaneId + * @param integer $position + * @return boolean + */ + public function changePosition($projectId, $swimlaneId, $position) + { + if ($position < 1 || $position > $this->db->table(self::TABLE)->eq('project_id', $projectId)->count()) { + return false; + } + + $swimlaneIds = $this->db->table(self::TABLE) + ->eq('is_active', 1) + ->eq('project_id', $projectId) + ->neq('id', $swimlaneId) + ->asc('position') + ->findAllByColumn('id'); + + $offset = 1; + $results = array(); + + foreach ($swimlaneIds as $currentSwimlaneId) { + if ($offset == $position) { + $offset++; + } + + $results[] = $this->db->table(self::TABLE)->eq('id', $currentSwimlaneId)->update(array('position' => $offset)); + $offset++; + } + + $results[] = $this->db->table(self::TABLE)->eq('id', $swimlaneId)->eq('project_id', $projectId)->update(array('position' => $position)); + + return !in_array(false, $results, true); + } + + /** + * Duplicate Swimlane to project + * + * @access public + * @param integer $projectSrcId + * @param integer $projectDstId + * @return boolean + */ + public function duplicate($projectSrcId, $projectDstId) + { + $swimlanes = $this->getAll($projectSrcId); + + foreach ($swimlanes as $swimlane) { + if (! $this->db->table(self::TABLE)->eq('project_id', $projectDstId)->eq('name', $swimlane['name'])->exists()) { + $values = array( + 'name' => $swimlane['name'], + 'description' => $swimlane['description'], + 'position' => $swimlane['position'], + 'is_active' => $swimlane['is_active'] ? self::ACTIVE : self::INACTIVE, // Avoid SQL error with Postgres + 'project_id' => $projectDstId, + ); + + if (! $this->db->table(self::TABLE)->persist($values)) { + return false; + } + } + } + + return true; + } +} diff --git a/app/Model/TagDuplicationModel.php b/app/Model/TagDuplicationModel.php new file mode 100644 index 0000000..ebca395 --- /dev/null +++ b/app/Model/TagDuplicationModel.php @@ -0,0 +1,96 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Tag Duplication + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class TagDuplicationModel extends Base +{ + /** + * Duplicate project tags to another project + * + * @access public + * @param integer $src_project_id + * @param integer $dst_project_id + * @return bool + */ + public function duplicate($src_project_id, $dst_project_id) + { + $tags = $this->tagModel->getAllByProject($src_project_id); + $results = array(); + + foreach ($tags as $tag) { + $results[] = $this->tagModel->create($dst_project_id, $tag['name'], $tag['color_id']); + } + + return ! in_array(false, $results, true); + } + + /** + * Link tags to the new tasks + * + * @access public + * @param integer $src_task_id + * @param integer $dst_task_id + * @param integer $dst_project_id + */ + public function duplicateTaskTagsToAnotherProject($src_task_id, $dst_task_id, $dst_project_id) + { + $tags = $this->taskTagModel->getTagsByTask($src_task_id); + + foreach ($tags as $tag) { + $tag_id = $this->tagModel->getIdByName($dst_project_id, $tag['name']); + + if (empty($tag_id)) { + $tag_id = $this->tagModel->create($dst_project_id, $tag['name'], $tag['color_id']); + } + + $this->taskTagModel->associateTag($dst_task_id, $tag_id); + } + } + + /** + * Duplicate tags to the new task + * + * @access public + * @param integer $src_task_id + * @param integer $dst_task_id + */ + public function duplicateTaskTags($src_task_id, $dst_task_id) + { + $tags = $this->taskTagModel->getTagsByTask($src_task_id); + + foreach ($tags as $tag) { + $this->taskTagModel->associateTag($dst_task_id, $tag['id']); + } + } + + /** + * Sync tags that are not available in destination project + * + * @access public + * @param integer $task_id + * @param integer $dst_project_id + */ + public function syncTaskTagsToAnotherProject($task_id, $dst_project_id) + { + $tags = $this->taskTagModel->getTagsByTaskNotAvailableInProject($task_id, $dst_project_id); + + foreach ($tags as $tag) { + $tag_id = $this->tagModel->getIdByName($dst_project_id, $tag['name']); + + if (empty($tag_id)) { + $tag_id = $this->tagModel->create($dst_project_id, $tag['name'], $tag['color_id']); + } + + $this->taskTagModel->dissociateTag($task_id, $tag['id']); + $this->taskTagModel->associateTag($task_id, $tag_id); + } + } +} diff --git a/app/Model/TagModel.php b/app/Model/TagModel.php new file mode 100644 index 0000000..b9d0fd2 --- /dev/null +++ b/app/Model/TagModel.php @@ -0,0 +1,217 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Class TagModel + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class TagModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'tags'; + + /** + * Get all tags + * + * @access public + * @return array + */ + public function getAll() + { + return $this->db->table(self::TABLE)->asc('name')->findAll(); + } + + /** + * Get all tags by project + * + * @access public + * @param integer $project_id + * @return array + */ + public function getAllByProject($project_id) + { + return $this->db->table(self::TABLE)->eq('project_id', $project_id)->asc('name')->findAll(); + } + + /** + * Get all global tags and tags for the given project IDs + * + * @access public + * @param array $project_ids + * @return array + */ + public function getAllByProjectIds(array $project_ids) + { + return $this->db->table(self::TABLE) + ->beginOr() + ->eq('project_id', 0) + ->in('project_id', $project_ids) + ->closeOr() + ->asc('name') + ->findAll(); + } + + /** + * Get assignable tags for a project + * + * @param integer $project_id Project Id + * @param bool $include_global_tags Flag to include global tags + * @return array + */ + public function getAssignableList($project_id, $include_global_tags = true) + { + if ($include_global_tags) { + return $this->db->hashtable(self::TABLE) + ->beginOr() + ->eq('project_id', $project_id) + ->eq('project_id', 0) + ->closeOr() + ->asc('name') + ->getAll('id', 'name'); + } else { + return $this->db->hashtable(self::TABLE) + ->beginOr() + ->eq('project_id', $project_id) + ->closeOr() + ->asc('name') + ->getAll('id', 'name'); + } + } + + /** + * Get one tag + * + * @access public + * @param integer $tag_id + * @return array|null + */ + public function getById($tag_id) + { + return $this->db->table(self::TABLE)->eq('id', $tag_id)->findOne(); + } + + /** + * Get tag id from tag name + * + * @access public + * @param int $project_id + * @param string $tag + * @return integer + */ + public function getIdByName($project_id, $tag) + { + return $this->db + ->table(self::TABLE) + ->beginOr() + ->eq('project_id', 0) + ->eq('project_id', $project_id) + ->closeOr() + ->ilike('name', $tag) + ->asc('project_id') + ->findOneColumn('id'); + } + + /** + * Return true if the tag exists + * + * @access public + * @param integer $project_id + * @param string $tag + * @param integer $tag_id + * @return boolean + */ + public function exists($project_id, $tag, $tag_id = 0) + { + return $this->db + ->table(self::TABLE) + ->neq('id', $tag_id) + ->beginOr() + ->eq('project_id', 0) + ->eq('project_id', $project_id) + ->closeOr() + ->ilike('name', $tag) + ->asc('project_id') + ->exists(); + } + + /** + * Return tag id and create a new tag if necessary + * + * @access public + * @param int $project_id + * @param string $tag + * @return bool|int + */ + public function findOrCreateTag($project_id, $tag) + { + $tag_id = $this->getIdByName($project_id, $tag); + + if (empty($tag_id)) { + $tag_id = $this->create($project_id, $tag); + } + + return $tag_id; + } + + /** + * Add a new tag + * + * @access public + * @param int $project_id + * @param string $tag + * @return bool|int + */ + public function create($project_id, $tag, $color_id = null) + { + return $this->db->table(self::TABLE)->persist(array( + 'project_id' => $project_id, + 'name' => $tag, + 'color_id' => $color_id, + )); + } + + /** + * Update a tag + * + * @access public + * @param integer $tag_id + * @param string $tag + * @return bool + */ + public function update($tag_id, $tag, $color_id = null, $project_id = null) + { + if ($project_id !== null) { + return $this->db->table(self::TABLE)->eq('id', $tag_id)->update(array( + 'name' => $tag, + 'color_id' => $color_id, + 'project_id' => $project_id, + )); + } else { + return $this->db->table(self::TABLE)->eq('id', $tag_id)->update(array( + 'name' => $tag, + 'color_id' => $color_id, + )); + } + } + + /** + * Remove a tag + * + * @access public + * @param integer $tag_id + * @return bool + */ + public function remove($tag_id) + { + return $this->db->table(self::TABLE)->eq('id', $tag_id)->remove(); + } +} diff --git a/app/Model/TaskAnalyticModel.php b/app/Model/TaskAnalyticModel.php new file mode 100644 index 0000000..3d6fe8a --- /dev/null +++ b/app/Model/TaskAnalyticModel.php @@ -0,0 +1,72 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Task Analytic + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class TaskAnalyticModel extends Base +{ + /** + * Get the time between date_creation and date_completed or now if empty + * + * @access public + * @param array $task + * @return integer + */ + public function getLeadTime(array $task) + { + return ($task['date_completed'] ?: time()) - $task['date_creation']; + } + + /** + * Get the time between date_started and date_completed or now if empty + * + * @access public + * @param array $task + * @return integer + */ + public function getCycleTime(array $task) + { + if (empty($task['date_started'])) { + return 0; + } + + return ($task['date_completed'] ?: time()) - $task['date_started']; + } + + /** + * Get the average time spent in each column + * + * @access public + * @param array $task + * @return array + */ + public function getTimeSpentByColumn(array $task) + { + $result = array(); + $columns = $this->columnModel->getList($task['project_id']); + $sums = $this->transitionModel->getTimeSpentByTask($task['id']); + + foreach ($columns as $column_id => $column_title) { + $time_spent = isset($sums[$column_id]) ? $sums[$column_id] : 0; + + if ($task['column_id'] == $column_id) { + $time_spent += ($task['date_completed'] ?: time()) - $task['date_moved']; + } + + $result[] = array( + 'id' => $column_id, + 'title' => $column_title, + 'time_spent' => $time_spent, + ); + } + + return $result; + } +} diff --git a/app/Model/TaskCreationModel.php b/app/Model/TaskCreationModel.php new file mode 100644 index 0000000..8f10ea4 --- /dev/null +++ b/app/Model/TaskCreationModel.php @@ -0,0 +1,94 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Task Creation + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class TaskCreationModel extends Base +{ + /** + * Create a task + * + * @access public + * @param array $values Form values + * @return integer + */ + public function create(array $values) + { + $position = empty($values['position']) ? 0 : $values['position']; + $tags = array(); + + if (isset($values['tags'])) { + $tags = $values['tags']; + unset($values['tags']); + } + + $this->prepare($values); + $task_id = $this->db->table(TaskModel::TABLE)->persist($values); + + if ($task_id !== false) { + if ($position > 0 && $values['position'] > 1) { + $this->taskPositionModel->movePosition($values['project_id'], $task_id, $values['column_id'], $position, $values['swimlane_id'], false); + } + + if (! empty($tags)) { + $this->taskTagModel->save($values['project_id'], $task_id, $tags); + } + + $this->queueManager->push($this->taskEventJob->withParams( + $task_id, + array(TaskModel::EVENT_CREATE_UPDATE, TaskModel::EVENT_CREATE) + )); + } + + $this->hook->reference('model:task:creation:aftersave', $task_id); + + return (int) $task_id; + } + + /** + * Prepare data + * + * @access protected + * @param array $values Form values + */ + protected function prepare(array &$values) + { + $values = $this->dateParser->convert($values, array('date_due'), true); + $values = $this->dateParser->convert($values, array('date_started'), true); + + $this->helper->model->removeFields($values, array('another_task', 'duplicate_multiple_projects')); + $this->helper->model->resetFields($values, array('creator_id', 'owner_id', 'date_due', 'date_started', 'score', 'category_id', 'time_estimated', 'time_spent')); + + if (empty($values['column_id'])) { + $values['column_id'] = $this->columnModel->getFirstColumnId($values['project_id']); + } + + if (empty($values['color_id'])) { + $values['color_id'] = $this->colorModel->getDefaultColor(); + } + + if (empty($values['title'])) { + $values['title'] = t('Untitled'); + } + + // Note: Do not override the creator_id if the task is imported + if (empty($values['creator_id']) && $this->userSession->isLogged()) { + $values['creator_id'] = $this->userSession->getId(); + } + + $values['swimlane_id'] = empty($values['swimlane_id']) ? $this->swimlaneModel->getFirstActiveSwimlaneId($values['project_id']) : $values['swimlane_id']; + $values['date_creation'] = time(); + $values['date_modification'] = $values['date_creation']; + $values['date_moved'] = $values['date_creation']; + $values['position'] = $this->taskFinderModel->countByColumnAndSwimlaneId($values['project_id'], $values['column_id'], $values['swimlane_id']) + 1; + + $this->hook->reference('model:task:creation:prepare', $values); + } +} diff --git a/app/Model/TaskDuplicationModel.php b/app/Model/TaskDuplicationModel.php new file mode 100644 index 0000000..24845da --- /dev/null +++ b/app/Model/TaskDuplicationModel.php @@ -0,0 +1,169 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Task Duplication + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class TaskDuplicationModel extends Base +{ + /** + * Fields to copy when duplicating a task + * + * @access protected + * @var string[] + */ + protected $fieldsToDuplicate = array( + 'title', + 'description', + 'date_due', + 'color_id', + 'project_id', + 'column_id', + 'owner_id', + 'score', + 'priority', + 'category_id', + 'time_estimated', + 'swimlane_id', + 'recurrence_status', + 'recurrence_trigger', + 'recurrence_factor', + 'recurrence_timeframe', + 'recurrence_basedate', + 'external_provider', + 'external_uri', + 'reference', + ); + + /** + * Duplicate a task to the same project + * + * @access public + * @param integer $task_id Task id + * @return boolean|integer Duplicated task id + */ + public function duplicate($task_id) + { + $values = $this->copyFields($task_id); + $values['title'] = t('[DUPLICATE]').' '.$values['title']; + + $new_task_id = $this->save($task_id, $values); + + if ($new_task_id !== false) { + $this->tagDuplicationModel->duplicateTaskTags($task_id, $new_task_id); + $this->taskLinkModel->create($new_task_id, $task_id, 4); + + $externalLinks = $this->taskExternalLinkModel->getAll($task_id); + foreach ($externalLinks as $externalLink) { + $this->taskExternalLinkModel->create([ + 'task_id' => $new_task_id, + 'creator_id' => $externalLink['creator_id'], + 'dependency' => $externalLink['dependency'], + 'title' => $externalLink['title'], + 'link_type' => $externalLink['link_type'], + 'url' => $externalLink['url'], + ]); + } + } + + $hook_values = ['source_task_id' => $task_id, 'destination_task_id' => $new_task_id]; + $this->hook->reference('model:task:duplication:aftersave', $hook_values); + + return $new_task_id; + } + + /** + * Check if the assignee and the category are available in the destination project + * + * @access public + * @param array $values + * @return array + */ + public function checkDestinationProjectValues(array &$values) + { + // Check if the assigned user is allowed for the destination project + if ($values['owner_id'] > 0 && ! $this->projectPermissionModel->isUserAllowed($values['project_id'], $values['owner_id'])) { + $values['owner_id'] = 0; + } + + // Check if the category exists for the destination project + if ($values['category_id'] > 0) { + $values['category_id'] = $this->categoryModel->getIdByName( + $values['project_id'], + $this->categoryModel->getNameById($values['category_id']) + ); + } + + // Check if the swimlane exists for the destination project + $values['swimlane_id'] = $this->swimlaneModel->getIdByName( + $values['project_id'], + $this->swimlaneModel->getNameById($values['swimlane_id']) + ); + + if ($values['swimlane_id'] == 0) { + $values['swimlane_id'] = $this->swimlaneModel->getFirstActiveSwimlaneId($values['project_id']); + } + + // Check if the column exists for the destination project + if ($values['column_id'] > 0) { + $values['column_id'] = $this->columnModel->getColumnIdByTitle( + $values['project_id'], + $this->columnModel->getColumnTitleById($values['column_id']) + ); + + $values['column_id'] = $values['column_id'] ?: $this->columnModel->getFirstColumnId($values['project_id']); + } + + // Check if priority exists for destination project + $values['priority'] = $this->projectTaskPriorityModel->getPriorityForProject( + $values['project_id'], + empty($values['priority']) ? 0 : $values['priority'] + ); + + return $values; + } + + /** + * Duplicate fields for the new task + * + * @access protected + * @param integer $task_id Task id + * @return array + */ + protected function copyFields($task_id) + { + $task = $this->taskFinderModel->getById($task_id); + $values = array(); + + foreach ($this->fieldsToDuplicate as $field) { + $values[$field] = $task[$field]; + } + + return $values; + } + + /** + * Create the new task and duplicate subtasks + * + * @access protected + * @param integer $task_id Task id + * @param array $values Form values + * @return boolean|integer + */ + protected function save($task_id, array $values) + { + $new_task_id = $this->taskCreationModel->create($values); + + if ($new_task_id !== false) { + $this->subtaskModel->duplicate($task_id, $new_task_id); + } + + return $new_task_id; + } +} diff --git a/app/Model/TaskExternalLinkModel.php b/app/Model/TaskExternalLinkModel.php new file mode 100644 index 0000000..4d3488f --- /dev/null +++ b/app/Model/TaskExternalLinkModel.php @@ -0,0 +1,103 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Task External Link Model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class TaskExternalLinkModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'task_has_external_links'; + + /** + * Get all links + * + * @access public + * @param integer $task_id + * @return array + */ + public function getAll($task_id) + { + $types = $this->externalLinkManager->getTypes(); + + $links = $this->db->table(self::TABLE) + ->columns(self::TABLE.'.*', UserModel::TABLE.'.name AS creator_name', UserModel::TABLE.'.username AS creator_username') + ->eq('task_id', $task_id) + ->asc('title') + ->join(UserModel::TABLE, 'id', 'creator_id') + ->findAll(); + + foreach ($links as &$link) { + $link['dependency_label'] = $this->externalLinkManager->getDependencyLabel($link['link_type'], $link['dependency']); + $link['type'] = isset($types[$link['link_type']]) ? $types[$link['link_type']] : t('Unknown'); + } + + return $links; + } + + /** + * Get link + * + * @access public + * @param integer $link_id + * @return array + */ + public function getById($link_id) + { + return $this->db->table(self::TABLE)->eq('id', $link_id)->findOne(); + } + + /** + * Add a new link in the database + * + * @access public + * @param array $values Form values + * @return boolean|integer + */ + public function create(array $values) + { + unset($values['id']); + $values['creator_id'] = $this->userSession->getId(); + $values['date_creation'] = time(); + $values['date_modification'] = $values['date_creation']; + + return $this->db->table(self::TABLE)->persist($values); + } + + /** + * Modify external link + * + * @access public + * @param array $values Form values + * @return boolean + */ + public function update(array $values) + { + $values['date_modification'] = time(); + $updates = $values; + unset($updates['id']); + return $this->db->table(self::TABLE)->eq('id', $values['id'])->update($updates); + } + + /** + * Remove a link + * + * @access public + * @param integer $link_id + * @return boolean + */ + public function remove($link_id) + { + return $this->db->table(self::TABLE)->eq('id', $link_id)->remove(); + } +} diff --git a/app/Model/TaskFileModel.php b/app/Model/TaskFileModel.php new file mode 100644 index 0000000..9d44600 --- /dev/null +++ b/app/Model/TaskFileModel.php @@ -0,0 +1,115 @@ +<?php + +namespace Kanboard\Model; + +/** + * Task File Model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class TaskFileModel extends FileModel +{ + /** + * Table name + * + * @var string + */ + const TABLE = 'task_has_files'; + + /** + * Events + * + * @var string + */ + const EVENT_CREATE = 'task.file.create'; + const EVENT_DESTROY = 'task.file.destroy'; + + /** + * Get the table + * + * @abstract + * @access protected + * @return string + */ + protected function getTable() + { + return self::TABLE; + } + + /** + * Define the foreign key + * + * @abstract + * @access protected + * @return string + */ + protected function getForeignKey() + { + return 'task_id'; + } + + /** + * Define the path prefix + * + * @abstract + * @access protected + * @return string + */ + protected function getPathPrefix() + { + return 'tasks'; + } + + /** + * Get projectId from fileId + * + * @access public + * @param integer $file_id + * @return integer + */ + public function getProjectId($file_id) + { + return $this->db + ->table(self::TABLE) + ->eq(self::TABLE.'.id', $file_id) + ->join(TaskModel::TABLE, 'id', 'task_id') + ->findOneColumn(TaskModel::TABLE . '.project_id') ?: 0; + } + + /** + * Handle screenshot upload + * + * @access public + * @param integer $task_id Task id + * @param string $blob Base64 encoded image + * @return bool|integer + */ + public function uploadScreenshot($task_id, $blob) + { + $original_filename = e('Screenshot taken %s', $this->helper->dt->datetime(time())).'.png'; + return $this->uploadContent($task_id, $original_filename, $blob); + } + + /** + * Fire file creation event + * + * @access protected + * @param integer $file_id + */ + protected function fireCreationEvent($file_id) + { + $this->queueManager->push($this->taskFileEventJob->withParams($file_id, self::EVENT_CREATE)); + } + + /** + * Fire file destruction event + * + * @access protected + * @param integer $file_id + */ + protected function fireDestructionEvent($file_id) + { + $this->queueManager->push($this->taskFileEventJob->withParams($file_id, self::EVENT_DESTROY)); + } +} diff --git a/app/Model/TaskFinderModel.php b/app/Model/TaskFinderModel.php new file mode 100644 index 0000000..6cf787d --- /dev/null +++ b/app/Model/TaskFinderModel.php @@ -0,0 +1,415 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Task Finder model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class TaskFinderModel extends Base +{ + /** + * Get query for project user overview + * + * @access public + * @param array $project_ids + * @param integer $is_active + * @return \PicoDb\Table + */ + public function getProjectUserOverviewQuery(array $project_ids, $is_active) + { + if (empty($project_ids)) { + $project_ids = array(-1); + } + + return $this->db + ->table(TaskModel::TABLE) + ->columns( + TaskModel::TABLE.'.id', + TaskModel::TABLE.'.title', + TaskModel::TABLE.'.date_due', + TaskModel::TABLE.'.date_started', + TaskModel::TABLE.'.project_id', + TaskModel::TABLE.'.color_id', + TaskModel::TABLE.'.priority', + TaskModel::TABLE.'.time_spent', + TaskModel::TABLE.'.time_estimated', + ProjectModel::TABLE.'.name AS project_name', + ColumnModel::TABLE.'.title AS column_name', + UserModel::TABLE.'.username AS assignee_username', + UserModel::TABLE.'.name AS assignee_name' + ) + ->eq(TaskModel::TABLE.'.is_active', $is_active) + ->in(ProjectModel::TABLE.'.id', $project_ids) + ->join(ProjectModel::TABLE, 'id', 'project_id') + ->join(ColumnModel::TABLE, 'id', 'column_id', TaskModel::TABLE) + ->join(UserModel::TABLE, 'id', 'owner_id', TaskModel::TABLE); + } + + /** + * Get query for assigned user tasks + * + * @access public + * @param integer $user_id User id + * @return \PicoDb\Table + */ + public function getUserQuery($user_id) + { + return $this->getExtendedQuery() + ->beginOr() + ->eq(TaskModel::TABLE.'.owner_id', $user_id) + ->inSubquery(TaskModel::TABLE.'.id', $this->db->table(SubtaskModel::TABLE)->columns('task_id')->eq('user_id', $user_id)) + ->closeOr() + ->eq(TaskModel::TABLE.'.is_active', TaskModel::STATUS_OPEN) + ->eq(ProjectModel::TABLE.'.is_active', ProjectModel::ACTIVE) + ->eq(ColumnModel::TABLE.'.hide_in_dashboard', 0); + } + + /** + * Extended query + * + * @access public + * @return \PicoDb\Table + */ + public function getExtendedQuery() + { + return $this->db + ->table(TaskModel::TABLE) + ->columns( + '(SELECT COUNT(*) FROM '.CommentModel::TABLE.' WHERE task_id=tasks.id) AS nb_comments', + '(SELECT COUNT(*) FROM '.TaskFileModel::TABLE.' WHERE task_id=tasks.id) AS nb_files', + '(SELECT COUNT(*) FROM '.SubtaskModel::TABLE.' WHERE '.SubtaskModel::TABLE.'.task_id=tasks.id) AS nb_subtasks', + '(SELECT COUNT(*) FROM '.SubtaskModel::TABLE.' WHERE '.SubtaskModel::TABLE.'.task_id=tasks.id AND status=2) AS nb_completed_subtasks', + '(SELECT COUNT(*) FROM '.TaskLinkModel::TABLE.' WHERE '.TaskLinkModel::TABLE.'.task_id = tasks.id) AS nb_links', + '(SELECT COUNT(*) FROM '.TaskExternalLinkModel::TABLE.' WHERE '.TaskExternalLinkModel::TABLE.'.task_id = tasks.id) AS nb_external_links', + '(SELECT DISTINCT 1 FROM '.TaskLinkModel::TABLE.' tl JOIN '.LinkModel::TABLE.' l ON tl.link_id = l.id WHERE tl.task_id = tasks.id AND l.label = '."'is a milestone of') AS is_milestone", + TaskModel::TABLE.'.id', + TaskModel::TABLE.'.reference', + TaskModel::TABLE.'.title', + TaskModel::TABLE.'.description', + TaskModel::TABLE.'.date_creation', + TaskModel::TABLE.'.date_modification', + TaskModel::TABLE.'.date_completed', + TaskModel::TABLE.'.date_started', + TaskModel::TABLE.'.date_due', + TaskModel::TABLE.'.color_id', + TaskModel::TABLE.'.project_id', + TaskModel::TABLE.'.column_id', + TaskModel::TABLE.'.swimlane_id', + TaskModel::TABLE.'.owner_id', + TaskModel::TABLE.'.creator_id', + TaskModel::TABLE.'.position', + TaskModel::TABLE.'.is_active', + TaskModel::TABLE.'.score', + TaskModel::TABLE.'.category_id', + TaskModel::TABLE.'.priority', + TaskModel::TABLE.'.date_moved', + TaskModel::TABLE.'.recurrence_status', + TaskModel::TABLE.'.recurrence_trigger', + TaskModel::TABLE.'.recurrence_factor', + TaskModel::TABLE.'.recurrence_timeframe', + TaskModel::TABLE.'.recurrence_basedate', + TaskModel::TABLE.'.recurrence_parent', + TaskModel::TABLE.'.recurrence_child', + TaskModel::TABLE.'.time_estimated', + TaskModel::TABLE.'.time_spent', + TaskModel::TABLE.'.reference', + UserModel::TABLE.'.username AS assignee_username', + UserModel::TABLE.'.name AS assignee_name', + UserModel::TABLE.'.email AS assignee_email', + UserModel::TABLE.'.avatar_path AS assignee_avatar_path', + CategoryModel::TABLE.'.name AS category_name', + CategoryModel::TABLE.'.description AS category_description', + CategoryModel::TABLE.'.color_id AS category_color_id', + ColumnModel::TABLE.'.title AS column_name', + ColumnModel::TABLE.'.position AS column_position', + SwimlaneModel::TABLE.'.name AS swimlane_name', + ProjectModel::TABLE.'.name AS project_name' + ) + ->join(UserModel::TABLE, 'id', 'owner_id', TaskModel::TABLE) + ->left(UserModel::TABLE, 'uc', 'id', TaskModel::TABLE, 'creator_id') + ->join(CategoryModel::TABLE, 'id', 'category_id', TaskModel::TABLE) + ->join(ColumnModel::TABLE, 'id', 'column_id', TaskModel::TABLE) + ->join(SwimlaneModel::TABLE, 'id', 'swimlane_id', TaskModel::TABLE) + ->join(ProjectModel::TABLE, 'id', 'project_id', TaskModel::TABLE); + } + + /** + * Get all tasks for a given project and status + * + * @access public + * @param integer $project_id Project id + * @param integer $status_id Status id + * @return array + */ + public function getAll($project_id, $status_id = TaskModel::STATUS_OPEN) + { + return $this->db + ->table(TaskModel::TABLE) + ->eq(TaskModel::TABLE.'.project_id', $project_id) + ->eq(TaskModel::TABLE.'.is_active', $status_id) + ->asc(TaskModel::TABLE.'.id') + ->findAll(); + } + + /** + * Get all tasks for a given project and status + * + * @access public + * @param integer $project_id + * @param array $status + * @return array + */ + public function getAllIds($project_id, array $status = array(TaskModel::STATUS_OPEN)) + { + return $this->db + ->table(TaskModel::TABLE) + ->eq(TaskModel::TABLE.'.project_id', $project_id) + ->in(TaskModel::TABLE.'.is_active', $status) + ->asc(TaskModel::TABLE.'.id') + ->findAllByColumn(TaskModel::TABLE.'.id'); + } + + /** + * Get overdue tasks query + * + * @access public + * @return \PicoDb\Table + */ + public function getOverdueTasksQuery() + { + return $this->db->table(TaskModel::TABLE) + ->columns( + TaskModel::TABLE.'.id', + TaskModel::TABLE.'.title', + TaskModel::TABLE.'.date_due', + TaskModel::TABLE.'.project_id', + TaskModel::TABLE.'.creator_id', + TaskModel::TABLE.'.owner_id', + ProjectModel::TABLE.'.name AS project_name', + UserModel::TABLE.'.username AS assignee_username', + UserModel::TABLE.'.name AS assignee_name' + ) + ->join(ProjectModel::TABLE, 'id', 'project_id') + ->join(UserModel::TABLE, 'id', 'owner_id') + ->eq(ProjectModel::TABLE.'.is_active', 1) + ->eq(TaskModel::TABLE.'.is_active', 1) + ->neq(TaskModel::TABLE.'.date_due', 0) + ->lte(TaskModel::TABLE.'.date_due', time()); + } + + /** + * Get a list of overdue tasks for all projects + * + * @access public + * @return array + */ + public function getOverdueTasks() + { + return $this->getOverdueTasksQuery()->findAll(); + } + + /** + * Get a list of overdue tasks by project + * + * @access public + * @param integer $project_id + * @return array + */ + public function getOverdueTasksByProject($project_id) + { + return $this->getOverdueTasksQuery()->eq(TaskModel::TABLE.'.project_id', $project_id)->findAll(); + } + + /** + * Get a list of overdue tasks by user + * + * @access public + * @param integer $user_id + * @return array + */ + public function getOverdueTasksByUser($user_id) + { + return $this->getOverdueTasksQuery()->eq(TaskModel::TABLE.'.owner_id', $user_id)->findAll(); + } + + /** + * Get project id for a given task + * + * @access public + * @param integer $task_id Task id + * @return integer + */ + public function getProjectId($task_id) + { + return (int) $this->db->table(TaskModel::TABLE)->eq('id', $task_id)->findOneColumn('project_id') ?: 0; + } + + /** + * Fetch a task by the id + * + * @access public + * @param integer $task_id Task id + * @return array + */ + public function getById($task_id) + { + return $this->db->table(TaskModel::TABLE)->eq('id', $task_id)->findOne(); + } + + /** + * Fetch a task by the reference (external id) + * + * @access public + * @param integer $project_id Project id + * @param string $reference Task reference + * @return array + */ + public function getByReference($project_id, $reference) + { + return $this->db->table(TaskModel::TABLE)->eq('project_id', $project_id)->eq('reference', $reference)->findOne(); + } + + /** + * Get task details (fetch more information from other tables) + * + * @access public + * @param integer $task_id Task id + * @return array + */ + public function getDetails($task_id) + { + return $this->db->table(TaskModel::TABLE) + ->columns( + TaskModel::TABLE.'.*', + CategoryModel::TABLE.'.name AS category_name', + SwimlaneModel::TABLE.'.name AS swimlane_name', + ProjectModel::TABLE.'.name AS project_name', + ColumnModel::TABLE.'.title AS column_title', + UserModel::TABLE.'.username AS assignee_username', + UserModel::TABLE.'.name AS assignee_name', + 'uc.username AS creator_username', + 'uc.name AS creator_name', + CategoryModel::TABLE.'.description AS category_description', + ColumnModel::TABLE.'.position AS column_position' + ) + ->join(UserModel::TABLE, 'id', 'owner_id', TaskModel::TABLE) + ->left(UserModel::TABLE, 'uc', 'id', TaskModel::TABLE, 'creator_id') + ->join(CategoryModel::TABLE, 'id', 'category_id', TaskModel::TABLE) + ->join(ColumnModel::TABLE, 'id', 'column_id', TaskModel::TABLE) + ->join(SwimlaneModel::TABLE, 'id', 'swimlane_id', TaskModel::TABLE) + ->join(ProjectModel::TABLE, 'id', 'project_id', TaskModel::TABLE) + ->eq(TaskModel::TABLE.'.id', $task_id) + ->findOne(); + } + + /** + * Get iCal query + * + * @access public + * @return \PicoDb\Table + */ + public function getICalQuery() + { + return $this->db->table(TaskModel::TABLE) + ->left(UserModel::TABLE, 'ua', 'id', TaskModel::TABLE, 'owner_id') + ->left(UserModel::TABLE, 'uc', 'id', TaskModel::TABLE, 'creator_id') + ->columns( + TaskModel::TABLE.'.*', + 'ua.email AS assignee_email', + 'ua.name AS assignee_name', + 'ua.username AS assignee_username', + 'uc.email AS creator_email', + 'uc.name AS creator_name', + 'uc.username AS creator_username' + ); + } + + /** + * Count all tasks for a given project and status + * + * @access public + * @param integer $project_id Project id + * @param array $status List of status id + * @return integer + */ + public function countByProjectId($project_id, array $status = array(TaskModel::STATUS_OPEN, TaskModel::STATUS_CLOSED)) + { + return $this->db + ->table(TaskModel::TABLE) + ->eq('project_id', $project_id) + ->in('is_active', $status) + ->count(); + } + + /** + * Count the number of tasks for a given column and status + * + * @access public + * @param integer $project_id Project id + * @param integer $column_id Column id + * @param array $status + * @return int + */ + public function countByColumnId($project_id, $column_id, array $status = array(TaskModel::STATUS_OPEN)) + { + return $this->db + ->table(TaskModel::TABLE) + ->eq('project_id', $project_id) + ->eq('column_id', $column_id) + ->in('is_active', $status) + ->count(); + } + + /** + * Count the number of tasks for a given column and swimlane + * + * @access public + * @param integer $project_id Project id + * @param integer $column_id Column id + * @param integer $swimlane_id Swimlane id + * @return integer + */ + public function countByColumnAndSwimlaneId($project_id, $column_id, $swimlane_id) + { + return $this->db + ->table(TaskModel::TABLE) + ->eq('project_id', $project_id) + ->eq('column_id', $column_id) + ->eq('swimlane_id', $swimlane_id) + ->eq('is_active', 1) + ->count(); + } + + /** + * Return true if the task exists + * + * @access public + * @param integer $task_id Task id + * @return boolean + */ + public function exists($task_id) + { + return $this->db->table(TaskModel::TABLE)->eq('id', $task_id)->exists(); + } + + /** + * Get project token + * + * @access public + * @param integer $task_id + * @return string + */ + public function getProjectToken($task_id) + { + return $this->db + ->table(TaskModel::TABLE) + ->eq(TaskModel::TABLE.'.id', $task_id) + ->join(ProjectModel::TABLE, 'id', 'project_id') + ->findOneColumn(ProjectModel::TABLE.'.token'); + } +} diff --git a/app/Model/TaskLinkModel.php b/app/Model/TaskLinkModel.php new file mode 100644 index 0000000..116e043 --- /dev/null +++ b/app/Model/TaskLinkModel.php @@ -0,0 +1,305 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * TaskLink model + * + * @package Kanboard\Model + * @author Olivier Maridat + * @author Frederic Guillot + */ +class TaskLinkModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'task_has_links'; + + /** + * Events + * + * @var string + */ + const EVENT_CREATE_UPDATE = 'task_internal_link.create_update'; + const EVENT_DELETE = 'task_internal_link.delete'; + + /** + * Get projectId from $task_link_id + * + * @access public + * @param integer $task_link_id + * @return integer + */ + public function getProjectId($task_link_id) + { + return $this->db + ->table(self::TABLE) + ->eq(self::TABLE.'.id', $task_link_id) + ->join(TaskModel::TABLE, 'id', 'task_id') + ->findOneColumn(TaskModel::TABLE . '.project_id') ?: 0; + } + + /** + * Get a task link + * + * @access public + * @param integer $task_link_id Task link id + * @return array + */ + public function getById($task_link_id) + { + return $this->db + ->table(self::TABLE) + ->columns( + self::TABLE.'.id', + self::TABLE.'.opposite_task_id', + self::TABLE.'.task_id', + self::TABLE.'.link_id', + LinkModel::TABLE.'.label', + LinkModel::TABLE.'.opposite_id AS opposite_link_id' + ) + ->eq(self::TABLE.'.id', $task_link_id) + ->join(LinkModel::TABLE, 'id', 'link_id') + ->findOne(); + } + + /** + * Get the opposite task link (use the unique index task_has_links_unique) + * + * @access public + * @param array $task_link + * @return array + */ + public function getOppositeTaskLink(array $task_link) + { + $opposite_link_id = $this->linkModel->getOppositeLinkId($task_link['link_id']); + + return $this->db->table(self::TABLE) + ->eq('opposite_task_id', $task_link['task_id']) + ->eq('task_id', $task_link['opposite_task_id']) + ->eq('link_id', $opposite_link_id) + ->findOne(); + } + + /** + * Get all links attached to a task + * + * @access public + * @param integer $task_id Task id + * @return array + */ + public function getAll($task_id) + { + return $this->db + ->table(self::TABLE) + ->columns( + self::TABLE.'.id', + self::TABLE.'.opposite_task_id AS task_id', + LinkModel::TABLE.'.label', + TaskModel::TABLE.'.title', + TaskModel::TABLE.'.is_active', + TaskModel::TABLE.'.project_id', + TaskModel::TABLE.'.column_id', + TaskModel::TABLE.'.color_id', + TaskModel::TABLE.'.date_completed', + TaskModel::TABLE.'.date_started', + TaskModel::TABLE.'.date_due', + TaskModel::TABLE.'.time_spent AS task_time_spent', + TaskModel::TABLE.'.time_estimated AS task_time_estimated', + TaskModel::TABLE.'.owner_id AS task_assignee_id', + UserModel::TABLE.'.username AS task_assignee_username', + UserModel::TABLE.'.name AS task_assignee_name', + ColumnModel::TABLE.'.title AS column_title', + ProjectModel::TABLE.'.name AS project_name' + ) + ->eq(self::TABLE.'.task_id', $task_id) + ->join(LinkModel::TABLE, 'id', 'link_id') + ->join(TaskModel::TABLE, 'id', 'opposite_task_id') + ->join(ColumnModel::TABLE, 'id', 'column_id', TaskModel::TABLE) + ->join(UserModel::TABLE, 'id', 'owner_id', TaskModel::TABLE) + ->join(ProjectModel::TABLE, 'id', 'project_id', TaskModel::TABLE) + ->asc(LinkModel::TABLE.'.id') + ->desc(ColumnModel::TABLE.'.position') + ->desc(TaskModel::TABLE.'.is_active') + ->asc(TaskModel::TABLE.'.position') + ->asc(TaskModel::TABLE.'.id') + ->findAll(); + } + + /** + * Get all links attached to a task grouped by label + * + * @access public + * @param integer $task_id Task id + * @return array + */ + public function getAllGroupedByLabel($task_id) + { + $links = $this->getAll($task_id); + $result = array(); + + foreach ($links as $link) { + if (! isset($result[$link['label']])) { + $result[$link['label']] = array(); + } + + $result[$link['label']][] = $link; + } + + return $result; + } + + /** + * Create a new link + * + * @access public + * @param integer $task_id Task id + * @param integer $opposite_task_id Opposite task id + * @param integer $link_id Link id + * @return integer|boolean + */ + public function create($task_id, $opposite_task_id, $link_id) + { + $this->db->startTransaction(); + + $opposite_link_id = $this->linkModel->getOppositeLinkId($link_id); + $task_link_id1 = $this->createTaskLink($task_id, $opposite_task_id, $link_id); + $task_link_id2 = $this->createTaskLink($opposite_task_id, $task_id, $opposite_link_id); + + if ($task_link_id1 === false || $task_link_id2 === false) { + $this->db->cancelTransaction(); + return false; + } + + $this->db->closeTransaction(); + $this->fireEvents(array($task_link_id1, $task_link_id2), self::EVENT_CREATE_UPDATE); + + return $task_link_id1; + } + + /** + * Update a task link + * + * @access public + * @param integer $task_link_id Task link id + * @param integer $task_id Task id + * @param integer $opposite_task_id Opposite task id + * @param integer $link_id Link id + * @return boolean + */ + public function update($task_link_id, $task_id, $opposite_task_id, $link_id) + { + $this->db->startTransaction(); + + $task_link = $this->getById($task_link_id); + $opposite_task_link = $this->getOppositeTaskLink($task_link); + $opposite_link_id = $this->linkModel->getOppositeLinkId($link_id); + + $result1 = $this->updateTaskLink($task_link_id, $task_id, $opposite_task_id, $link_id); + $result2 = $this->updateTaskLink($opposite_task_link['id'], $opposite_task_id, $task_id, $opposite_link_id); + + if ($result1 === false || $result2 === false) { + $this->db->cancelTransaction(); + return false; + } + + $this->db->closeTransaction(); + $this->fireEvents(array($task_link_id, $opposite_task_link['id']), self::EVENT_CREATE_UPDATE); + + return true; + } + + /** + * Remove a link between two tasks + * + * @access public + * @param integer $task_link_id + * @return boolean + */ + public function remove($task_link_id) + { + $this->taskLinkEventJob->execute($task_link_id, self::EVENT_DELETE); + + $this->db->startTransaction(); + + $link = $this->getById($task_link_id); + $link_id = $this->linkModel->getOppositeLinkId($link['link_id']); + + $result1 = $this->db + ->table(self::TABLE) + ->eq('id', $task_link_id) + ->remove(); + + $result2 = $this->db + ->table(self::TABLE) + ->eq('opposite_task_id', $link['task_id']) + ->eq('task_id', $link['opposite_task_id']) + ->eq('link_id', $link_id) + ->remove(); + + if ($result1 === false || $result2 === false) { + $this->db->cancelTransaction(); + return false; + } + + $this->db->closeTransaction(); + + return true; + } + + /** + * Publish events + * + * @access protected + * @param integer[] $task_link_ids + * @param string $eventName + */ + protected function fireEvents(array $task_link_ids, $eventName) + { + foreach ($task_link_ids as $task_link_id) { + $this->queueManager->push($this->taskLinkEventJob->withParams($task_link_id, $eventName)); + } + } + + /** + * Create task link + * + * @access protected + * @param integer $task_id + * @param integer $opposite_task_id + * @param integer $link_id + * @return integer|boolean + */ + protected function createTaskLink($task_id, $opposite_task_id, $link_id) + { + return $this->db->table(self::TABLE)->persist(array( + 'task_id' => $task_id, + 'opposite_task_id' => $opposite_task_id, + 'link_id' => $link_id, + )); + } + + /** + * Update task link + * + * @access protected + * @param integer $task_link_id + * @param integer $task_id + * @param integer $opposite_task_id + * @param integer $link_id + * @return boolean + */ + protected function updateTaskLink($task_link_id, $task_id, $opposite_task_id, $link_id) + { + return $this->db->table(self::TABLE)->eq('id', $task_link_id)->update(array( + 'task_id' => $task_id, + 'opposite_task_id' => $opposite_task_id, + 'link_id' => $link_id, + )); + } +} diff --git a/app/Model/TaskMetadataModel.php b/app/Model/TaskMetadataModel.php new file mode 100644 index 0000000..dc3f56e --- /dev/null +++ b/app/Model/TaskMetadataModel.php @@ -0,0 +1,35 @@ +<?php + +namespace Kanboard\Model; + +/** + * Task Metadata + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class TaskMetadataModel extends MetadataModel +{ + /** + * Get the table + * + * @abstract + * @access protected + * @return string + */ + protected function getTable() + { + return 'task_has_metadata'; + } + + /** + * Define the entity key + * + * @access protected + * @return string + */ + protected function getEntityKey() + { + return 'task_id'; + } +} diff --git a/app/Model/TaskModel.php b/app/Model/TaskModel.php new file mode 100644 index 0000000..b437eb2 --- /dev/null +++ b/app/Model/TaskModel.php @@ -0,0 +1,156 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Task Model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class TaskModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'tasks'; + + /** + * Task status + * + * @var integer + */ + const STATUS_OPEN = 1; + const STATUS_CLOSED = 0; + + /** + * Events + * + * @var string + */ + const EVENT_MOVE_PROJECT = 'task.move.project'; + const EVENT_MOVE_COLUMN = 'task.move.column'; + const EVENT_MOVE_POSITION = 'task.move.position'; + const EVENT_MOVE_SWIMLANE = 'task.move.swimlane'; + const EVENT_UPDATE = 'task.update'; + const EVENT_CREATE = 'task.create'; + const EVENT_CLOSE = 'task.close'; + const EVENT_OPEN = 'task.open'; + const EVENT_CREATE_UPDATE = 'task.create_update'; + const EVENT_ASSIGNEE_CHANGE = 'task.assignee_change'; + const EVENT_OVERDUE = 'task.overdue'; + const EVENT_USER_MENTION = 'task.user.mention'; + const EVENT_DAILY_CRONJOB = 'task.cronjob.daily'; + + /** + * Recurrence: status + * + * @var integer + */ + const RECURRING_STATUS_NONE = 0; + const RECURRING_STATUS_PENDING = 1; + const RECURRING_STATUS_PROCESSED = 2; + + /** + * Recurrence: trigger + * + * @var integer + */ + const RECURRING_TRIGGER_FIRST_COLUMN = 0; + const RECURRING_TRIGGER_LAST_COLUMN = 1; + const RECURRING_TRIGGER_CLOSE = 2; + + /** + * Recurrence: timeframe + * + * @var integer + */ + const RECURRING_TIMEFRAME_DAYS = 0; + const RECURRING_TIMEFRAME_MONTHS = 1; + const RECURRING_TIMEFRAME_YEARS = 2; + + /** + * Recurrence: base date used to calculate new due date + * + * @var integer + */ + const RECURRING_BASEDATE_DUEDATE = 0; + const RECURRING_BASEDATE_TRIGGERDATE = 1; + + /** + * Remove a task + * + * @access public + * @param integer $task_id Task id + * @return boolean + */ + public function remove($task_id) + { + if (!$this->taskFinderModel->exists($task_id)) { + return false; + } + + $this->taskFileModel->removeAll($task_id); + + return $this->db->table(self::TABLE)->eq('id', $task_id)->remove(); + } + + /** + * Get a the task id from a text + * + * Example: "Fix bug #1234" will return 1234 + * + * @access public + * @param string $message Text + * @return integer + */ + public function getTaskIdFromText($message) + { + if (preg_match('!#(\d+)!i', $message, $matches) && isset($matches[1])) { + return $matches[1]; + } + + return 0; + } + + /** + * Get task progress based on the column position + * + * @access public + * @param array $task + * @param array $columns + * @return integer + */ + public function getProgress(array $task, array $columns) + { + if ($task['is_active'] == self::STATUS_CLOSED) { + return 100; + } + + $position = 0; + + foreach ($columns as $column_id => $column_title) { + if ($column_id == $task['column_id']) { + break; + } + + $position++; + } + + return round(($position * 100) / count($columns), 1); + } + + public function getOpenTaskCountBySwimlaneAndColumn($project_id) + { + return $this->db->table(self::TABLE) + ->columns('swimlane_id', 'column_id', 'COUNT(*) AS nb_open_tasks') + ->eq('project_id', $project_id) + ->eq('is_active', 1) + ->groupBy('swimlane_id', 'column_id') + ->findAll(); + } +} diff --git a/app/Model/TaskModificationModel.php b/app/Model/TaskModificationModel.php new file mode 100644 index 0000000..f90586a --- /dev/null +++ b/app/Model/TaskModificationModel.php @@ -0,0 +1,134 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Task Modification + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class TaskModificationModel extends Base +{ + /** + * Update a task + * + * @access public + * @param array $values + * @param boolean $fire_events + * @return boolean + */ + public function update(array $values, $fire_events = true) + { + $task = $this->taskFinderModel->getById($values['id']); + + if (isset($values['tags_only_add_new']) && $values['tags_only_add_new'] == 1) { + $this->updateTags($values, $task, false); + } else { + $this->updateTags($values, $task); + } + + $this->prepare($values); + $result = $this->db->table(TaskModel::TABLE)->eq('id', $task['id'])->update($values); + + if ($fire_events && $result) { + $this->fireEvents($task, $values); + } + + return $result; + } + + /** + * Fire events + * + * @access protected + * @param array $task + * @param array $changes + */ + protected function fireEvents(array $task, array $changes) + { + $events = array(); + + if ($this->isAssigneeChanged($task, $changes)) { + $events[] = TaskModel::EVENT_ASSIGNEE_CHANGE; + } elseif ($this->isModified($task, $changes)) { + $events[] = TaskModel::EVENT_CREATE_UPDATE; + $events[] = TaskModel::EVENT_UPDATE; + } + + if (! empty($events)) { + $this->queueManager->push( + $this->taskEventJob + ->withParams($task['id'], $events, $changes, array(), $task) + ); + } + } + + /** + * Return true if the task have been modified + * + * @access protected + * @param array $task + * @param array $changes + * @return bool + */ + protected function isModified(array $task, array $changes) + { + $diff = array_diff_assoc($changes, $task); + unset($diff['date_modification']); + return count($diff) > 0; + } + + /** + * Return true if the field is the only modified value + * + * @access protected + * @param array $task + * @param array $changes + * @return bool + */ + protected function isAssigneeChanged(array $task, array $changes) + { + $diff = array_diff_assoc($changes, $task); + unset($diff['date_modification']); + return isset($changes['owner_id']) && $task['owner_id'] != $changes['owner_id'] && count($diff) === 1; + } + + /** + * Prepare data before task modification + * + * @access protected + * @param array $values + */ + protected function prepare(array &$values) + { + $values = $this->dateParser->convert($values, array('date_due'), true); + $values = $this->dateParser->convert($values, array('date_started'), true); + + $this->helper->model->removeFields($values, array('id')); + $this->helper->model->resetFields($values, array('date_due', 'date_started', 'score', 'category_id', 'time_estimated', 'time_spent')); + $this->helper->model->convertIntegerFields($values, array('priority', 'is_active', 'recurrence_status', 'recurrence_trigger', 'recurrence_factor', 'recurrence_timeframe', 'recurrence_basedate')); + + $values['date_modification'] = time(); + + $this->hook->reference('model:task:modification:prepare', $values); + } + + /** + * Update tags + * + * @access protected + * @param array $values + * @param array $original_task + */ + protected function updateTags(array &$values, array $original_task, $remove_other_tags = true) + { + if (isset($values['tags'])) { + $this->taskTagModel->save($original_task['project_id'], $values['id'], $values['tags'], $remove_other_tags); + unset($values['tags']); + unset($values['tags_only_add_new']); + } + } +} diff --git a/app/Model/TaskPositionModel.php b/app/Model/TaskPositionModel.php new file mode 100644 index 0000000..56d32c7 --- /dev/null +++ b/app/Model/TaskPositionModel.php @@ -0,0 +1,315 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Task Position + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class TaskPositionModel extends Base +{ + public function moveBottom($project_id, $task_id, $swimlane_id, $column_id) + { + $this->db->startTransaction(); + + $task = $this->taskFinderModel->getById($task_id); + + $result = $this->db->table(TaskModel::TABLE) + ->eq('project_id', $project_id) + ->eq('swimlane_id', $swimlane_id) + ->eq('column_id', $column_id) + ->columns('MAX(position) AS pos') + ->findOne(); + + $position = 1; + if (! empty($result)) { + $position = $result['pos'] + 1; + } + + $result = $this->db->table(TaskModel::TABLE) + ->eq('id', $task_id) + ->eq('project_id', $project_id) + ->update([ + 'swimlane_id' => $swimlane_id, + 'column_id' => $column_id, + 'position' => $position, + 'date_moved' => time(), + 'date_modification' => time(), + ]); + + $this->db->closeTransaction(); + + if ($result) { + $this->fireEvents($task, $column_id, $position, $swimlane_id); + } + + return $result; + } + + /** + * Move a task to another column or to another position + * + * @access public + * @param integer $project_id Project id + * @param integer $task_id Task id + * @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 + * @param bool $onlyOpen Do not move closed tasks + * @return bool + */ + public function movePosition($project_id, $task_id, $column_id, $position, $swimlane_id = 0, $fire_events = true, $onlyOpen = true) + { + if ($position < 1) { + return false; + } + + $task = $this->taskFinderModel->getById($task_id); + + if ($swimlane_id == 0) { + $swimlane_id = $task['swimlane_id']; + } + + if ($onlyOpen && $task['is_active'] == TaskModel::STATUS_CLOSED) { + return true; + } + + $result = false; + + if ($task['swimlane_id'] != $swimlane_id) { + $result = $this->saveSwimlaneChange($project_id, $task_id, $position, $task['column_id'], $column_id, $task['swimlane_id'], $swimlane_id); + } elseif ($task['column_id'] != $column_id) { + $result = $this->saveColumnChange($project_id, $task_id, $position, $swimlane_id, $task['column_id'], $column_id); + } elseif ($task['position'] != $position) { + $result = $this->savePositionChange($project_id, $task_id, $position, $column_id, $swimlane_id); + } + + if ($result && $fire_events) { + $this->fireEvents($task, $column_id, $position, $swimlane_id); + } + + return $result; + } + + /** + * Move a task to another swimlane + * + * @access private + * @param integer $project_id + * @param integer $task_id + * @param integer $position + * @param integer $original_column_id + * @param integer $new_column_id + * @param integer $original_swimlane_id + * @param integer $new_swimlane_id + * @return boolean + */ + private function saveSwimlaneChange($project_id, $task_id, $position, $original_column_id, $new_column_id, $original_swimlane_id, $new_swimlane_id) + { + $this->db->startTransaction(); + $r1 = $this->saveTaskPositions($project_id, $task_id, 0, $original_column_id, $original_swimlane_id); + $r2 = $this->saveTaskPositions($project_id, $task_id, $position, $new_column_id, $new_swimlane_id); + $r3 = $this->saveTaskTimestamps($task_id); + $this->db->closeTransaction(); + + return $r1 && $r2 && $r3; + } + + /** + * Move a task to another column + * + * @access private + * @param integer $project_id + * @param integer $task_id + * @param integer $position + * @param integer $swimlane_id + * @param integer $original_column_id + * @param integer $new_column_id + * @return boolean + */ + private function saveColumnChange($project_id, $task_id, $position, $swimlane_id, $original_column_id, $new_column_id) + { + $this->db->startTransaction(); + $r1 = $this->saveTaskPositions($project_id, $task_id, 0, $original_column_id, $swimlane_id); + $r2 = $this->saveTaskPositions($project_id, $task_id, $position, $new_column_id, $swimlane_id); + $r3 = $this->saveTaskTimestamps($task_id); + $this->db->closeTransaction(); + + return $r1 && $r2 && $r3; + } + + /** + * Move a task to another position in the same column + * + * @access private + * @param integer $project_id + * @param integer $task_id + * @param integer $position + * @param integer $column_id + * @param integer $swimlane_id + * @return boolean + */ + private function savePositionChange($project_id, $task_id, $position, $column_id, $swimlane_id) + { + $this->db->startTransaction(); + $result = $this->saveTaskPositions($project_id, $task_id, $position, $column_id, $swimlane_id); + $this->db->closeTransaction(); + + return $result; + } + + /** + * Save all task positions for one column + * + * @access private + * @param integer $project_id + * @param integer $task_id + * @param integer $position + * @param integer $column_id + * @param integer $swimlane_id + * @return boolean + */ + private function saveTaskPositions($project_id, $task_id, $position, $column_id, $swimlane_id) + { + $tasks_ids = $this->db->table(TaskModel::TABLE) + ->eq('is_active', 1) + ->eq('swimlane_id', $swimlane_id) + ->eq('project_id', $project_id) + ->eq('column_id', $column_id) + ->neq('id', $task_id) + ->asc('position') + ->asc('id') + ->findAllByColumn('id'); + + $offset = 1; + + foreach ($tasks_ids as $current_task_id) { + + // Insert the new task + if ($position == $offset) { + if (! $this->saveTaskPosition($task_id, $offset, $column_id, $swimlane_id)) { + return false; + } + $offset++; + } + + // Rewrite other tasks position + if (! $this->saveTaskPosition($current_task_id, $offset, $column_id, $swimlane_id)) { + return false; + } + + $offset++; + } + + // Insert the new task at the bottom and normalize bad position + if ($position >= $offset && ! $this->saveTaskPosition($task_id, $offset, $column_id, $swimlane_id)) { + return false; + } + + return true; + } + + /** + * Update task timestamps + * + * @access private + * @param integer $task_id + * @return bool + */ + private function saveTaskTimestamps($task_id) + { + $now = time(); + + return $this->db->table(TaskModel::TABLE)->eq('id', $task_id)->update(array( + 'date_moved' => $now, + 'date_modification' => $now, + )); + } + + /** + * Save new task position + * + * @access private + * @param integer $task_id + * @param integer $position + * @param integer $column_id + * @param integer $swimlane_id + * @return boolean + */ + private function saveTaskPosition($task_id, $position, $column_id, $swimlane_id) + { + $result = $this->db->table(TaskModel::TABLE)->eq('id', $task_id)->update(array( + 'position' => $position, + 'column_id' => $column_id, + 'swimlane_id' => $swimlane_id, + )); + + if (! $result) { + $this->db->cancelTransaction(); + return false; + } + + return true; + } + + /** + * Fire events + * + * @access private + * @param array $task + * @param integer $new_column_id + * @param integer $new_position + * @param integer $new_swimlane_id + */ + private function fireEvents(array $task, $new_column_id, $new_position, $new_swimlane_id) + { + $changes = array( + 'project_id' => $task['project_id'], + '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'], + 'recurrence_status' => $task['recurrence_status'], + 'recurrence_trigger' => $task['recurrence_trigger'], + ); + + if ($task['swimlane_id'] != $new_swimlane_id) { + $this->taskEventJob->execute( + $task['id'], + array(TaskModel::EVENT_MOVE_SWIMLANE), + $changes, + $changes + ); + + if ($task['column_id'] != $new_column_id) { + $this->taskEventJob->execute( + $task['id'], + array(TaskModel::EVENT_MOVE_COLUMN), + $changes, + $changes + ); + } + } elseif ($task['column_id'] != $new_column_id) { + $this->taskEventJob->execute( + $task['id'], + array(TaskModel::EVENT_MOVE_COLUMN), + $changes, + $changes + ); + } elseif ($task['position'] != $new_position) { + $this->taskEventJob->execute( + $task['id'], + array(TaskModel::EVENT_MOVE_POSITION), + $changes, + $changes + ); + } + } +} diff --git a/app/Model/TaskProjectDuplicationModel.php b/app/Model/TaskProjectDuplicationModel.php new file mode 100644 index 0000000..c98704e --- /dev/null +++ b/app/Model/TaskProjectDuplicationModel.php @@ -0,0 +1,82 @@ +<?php + +namespace Kanboard\Model; + +/** + * Task Project Duplication + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class TaskProjectDuplicationModel extends TaskDuplicationModel +{ + /** + * Duplicate a task to another project + * + * @access public + * @param integer $task_id + * @param integer $project_id + * @param integer $swimlane_id + * @param integer $column_id + * @param integer $category_id + * @param integer $owner_id + * @return boolean|integer + */ + public function duplicateToProject($task_id, $project_id, $swimlane_id = null, $column_id = null, $category_id = null, $owner_id = null) + { + $values = $this->prepare($task_id, $project_id, $swimlane_id, $column_id, $category_id, $owner_id); + $this->checkDestinationProjectValues($values); + $new_task_id = $this->save($task_id, $values); + + if ($new_task_id !== false) { + $this->tagDuplicationModel->duplicateTaskTagsToAnotherProject($task_id, $new_task_id, $project_id); + $this->taskLinkModel->create($new_task_id, $task_id, 4); + + $attachments = $this->taskFileModel->getAll($task_id); + $externalLinks = $this->taskExternalLinkModel->getAll($task_id); + + foreach ($attachments as $attachment) { + $this->taskFileModel->create($new_task_id, $attachment['name'], $attachment['path'], $attachment['size']); + } + + foreach ($externalLinks as $externalLink) { + $this->taskExternalLinkModel->create([ + 'task_id' => $new_task_id, + 'creator_id' => $externalLink['creator_id'], + 'dependency' => $externalLink['dependency'], + 'title' => $externalLink['title'], + 'link_type' => $externalLink['link_type'], + 'url' => $externalLink['url'], + ]); + } + } + + $hook_values = ['source_task_id' => $task_id, 'destination_task_id' => $new_task_id]; + $this->hook->reference('model:task:project_duplication:aftersave', $hook_values); + + return $new_task_id; + } + + /** + * Prepare values before duplication + * + * @access protected + * @param integer $task_id + * @param integer $project_id + * @param integer $swimlane_id + * @param integer $column_id + * @param integer $category_id + * @param integer $owner_id + * @return array + */ + protected function prepare($task_id, $project_id, $swimlane_id, $column_id, $category_id, $owner_id) + { + $values = $this->copyFields($task_id); + $values['project_id'] = $project_id; + $values['column_id'] = $column_id !== null ? $column_id : $values['column_id']; + $values['swimlane_id'] = $swimlane_id !== null ? $swimlane_id : $values['swimlane_id']; + $values['category_id'] = $category_id !== null ? $category_id : $values['category_id']; + $values['owner_id'] = $owner_id !== null ? $owner_id : $values['owner_id']; + return $values; + } +} diff --git a/app/Model/TaskProjectMoveModel.php b/app/Model/TaskProjectMoveModel.php new file mode 100644 index 0000000..ae3ae08 --- /dev/null +++ b/app/Model/TaskProjectMoveModel.php @@ -0,0 +1,65 @@ +<?php + +namespace Kanboard\Model; + +/** + * Task Project Move + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class TaskProjectMoveModel extends TaskDuplicationModel +{ + /** + * Move a task to another project + * + * @access public + * @param integer $task_id + * @param integer $project_id + * @param integer $swimlane_id + * @param integer $column_id + * @param integer $category_id + * @param integer $owner_id + * @return boolean + */ + public function moveToProject($task_id, $project_id, $swimlane_id = null, $column_id = null, $category_id = null, $owner_id = null) + { + $task = $this->taskFinderModel->getById($task_id); + $values = $this->prepare($project_id, $swimlane_id, $column_id, $category_id, $owner_id, $task); + + $this->checkDestinationProjectValues($values); + $this->tagDuplicationModel->syncTaskTagsToAnotherProject($task_id, $project_id); + + if ($this->db->table(TaskModel::TABLE)->eq('id', $task_id)->update($values)) { + $this->queueManager->push($this->taskEventJob->withParams($task_id, array(TaskModel::EVENT_MOVE_PROJECT), $values)); + } + + return true; + } + + /** + * Prepare new task values + * + * @access protected + * @param integer $project_id + * @param integer $swimlane_id + * @param integer $column_id + * @param integer $category_id + * @param integer $owner_id + * @param array $task + * @return array + */ + protected function prepare($project_id, $swimlane_id, $column_id, $category_id, $owner_id, array $task) + { + $values = array(); + $values['is_active'] = 1; + $values['project_id'] = $project_id; + $values['column_id'] = $column_id !== null ? $column_id : $task['column_id']; + $values['position'] = $this->taskFinderModel->countByColumnId($project_id, $values['column_id']) + 1; + $values['swimlane_id'] = $swimlane_id !== null ? $swimlane_id : $task['swimlane_id']; + $values['category_id'] = $category_id !== null ? $category_id : $task['category_id']; + $values['owner_id'] = $owner_id !== null ? $owner_id : $task['owner_id']; + $values['priority'] = $task['priority']; + return $values; + } +} diff --git a/app/Model/TaskRecurrenceModel.php b/app/Model/TaskRecurrenceModel.php new file mode 100644 index 0000000..201a340 --- /dev/null +++ b/app/Model/TaskRecurrenceModel.php @@ -0,0 +1,159 @@ +<?php + +namespace Kanboard\Model; + +use DateInterval; +use DateTime; + +/** + * Task Recurrence + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class TaskRecurrenceModel extends TaskDuplicationModel +{ + /** + * Return the list user selectable recurrence status + * + * @access public + * @return array + */ + public function getRecurrenceStatusList() + { + return array( + TaskModel::RECURRING_STATUS_NONE => t('No'), + TaskModel::RECURRING_STATUS_PENDING => t('Yes'), + ); + } + + /** + * Return the list recurrence triggers + * + * @access public + * @return array + */ + public function getRecurrenceTriggerList() + { + return array( + TaskModel::RECURRING_TRIGGER_FIRST_COLUMN => t('When task is moved from first column'), + TaskModel::RECURRING_TRIGGER_LAST_COLUMN => t('When task is moved to last column'), + TaskModel::RECURRING_TRIGGER_CLOSE => t('When task is closed'), + ); + } + + /** + * Return the list options to calculate recurrence due date + * + * @access public + * @return array + */ + public function getRecurrenceBasedateList() + { + return array( + TaskModel::RECURRING_BASEDATE_DUEDATE => t('Existing due date'), + TaskModel::RECURRING_BASEDATE_TRIGGERDATE => t('Action date'), + ); + } + + /** + * Return the list recurrence timeframes + * + * @access public + * @return array + */ + public function getRecurrenceTimeframeList() + { + return array( + TaskModel::RECURRING_TIMEFRAME_DAYS => t('Day(s)'), + TaskModel::RECURRING_TIMEFRAME_MONTHS => t('Month(s)'), + TaskModel::RECURRING_TIMEFRAME_YEARS => t('Year(s)'), + ); + } + + /** + * Duplicate recurring task + * + * @access public + * @param integer $task_id Task id + * @return boolean|integer Recurrence task id + */ + public function duplicateRecurringTask($task_id) + { + $values = $this->copyFields($task_id); + + if ($values['recurrence_status'] == TaskModel::RECURRING_STATUS_PENDING) { + $values['recurrence_parent'] = $task_id; + $values['column_id'] = $this->columnModel->getFirstColumnId($values['project_id']); + $this->calculateRecurringTaskDueDate($values); + + $recurring_task_id = $this->save($task_id, $values); + + if ($recurring_task_id !== false) { + $this->tagDuplicationModel->duplicateTaskTags($task_id, $recurring_task_id); + + $externalLinks = $this->taskExternalLinkModel->getAll($task_id); + foreach ($externalLinks as $externalLink) { + $this->taskExternalLinkModel->create([ + 'task_id' => $recurring_task_id, + 'creator_id' => $externalLink['creator_id'], + 'dependency' => $externalLink['dependency'], + 'title' => $externalLink['title'], + 'link_type' => $externalLink['link_type'], + 'url' => $externalLink['url'], + ]); + } + + $parent_update = $this->db + ->table(TaskModel::TABLE) + ->eq('id', $task_id) + ->update(array( + 'recurrence_status' => TaskModel::RECURRING_STATUS_PROCESSED, + 'recurrence_child' => $recurring_task_id, + )); + + if ($parent_update) { + return $recurring_task_id; + } + } + } + + return false; + } + + /** + * Calculate new due date for new recurrence task + * + * @access public + * @param array $values Task fields + */ + public function calculateRecurringTaskDueDate(array &$values) + { + if (! empty($values['date_due']) && $values['recurrence_factor'] != 0) { + if ($values['recurrence_basedate'] == TaskModel::RECURRING_BASEDATE_TRIGGERDATE) { + $values['date_due'] = time(); + } + + $factor = abs($values['recurrence_factor']); + $subtract = $values['recurrence_factor'] < 0; + + switch ($values['recurrence_timeframe']) { + case TaskModel::RECURRING_TIMEFRAME_MONTHS: + $interval = 'P' . $factor . 'M'; + break; + case TaskModel::RECURRING_TIMEFRAME_YEARS: + $interval = 'P' . $factor . 'Y'; + break; + default: + $interval = 'P' . $factor . 'D'; + } + + $date_due = new DateTime(); + $date_due->setTimestamp($values['date_due']); + + $subtract ? $date_due->sub(new DateInterval($interval)) : $date_due->add(new DateInterval($interval)); + + $values['date_due'] = $date_due->getTimestamp(); + } + } +} diff --git a/app/Model/TaskReorderModel.php b/app/Model/TaskReorderModel.php new file mode 100644 index 0000000..d9230c4 --- /dev/null +++ b/app/Model/TaskReorderModel.php @@ -0,0 +1,107 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +class TaskReorderModel extends Base +{ + public function reorderByTaskId($projectID, $swimlaneID, $columnID, $direction) + { + $this->db->startTransaction(); + + $taskIDs = $this->db->table(TaskModel::TABLE) + ->eq('project_id', $projectID) + ->eq('swimlane_id', $swimlaneID) + ->eq('column_id', $columnID) + ->orderBy('id', $direction) + ->findAllByColumn('id'); + + $this->reorderTasks($taskIDs); + + $this->db->closeTransaction(); + } + + public function reorderByPriority($projectID, $swimlaneID, $columnID, $direction) + { + $this->db->startTransaction(); + + $taskIDs = $this->db->table(TaskModel::TABLE) + ->eq('project_id', $projectID) + ->eq('swimlane_id', $swimlaneID) + ->eq('column_id', $columnID) + ->orderBy('priority', $direction) + ->asc('id') + ->findAllByColumn('id'); + + $this->reorderTasks($taskIDs); + + $this->db->closeTransaction(); + } + + public function reorderByAssigneeAndPriority($projectID, $swimlaneID, $columnID, $direction) + { + $this->db->startTransaction(); + + $taskIDs = $this->db->table(TaskModel::TABLE) + ->eq('tasks.project_id', $projectID) + ->eq('tasks.swimlane_id', $swimlaneID) + ->eq('tasks.column_id', $columnID) + ->asc('u.name') + ->asc('u.username') + ->orderBy('tasks.priority', $direction) + ->left(UserModel::TABLE, 'u', 'id', TaskModel::TABLE, 'owner_id') + ->findAllByColumn('tasks.id'); + + $this->reorderTasks($taskIDs); + + $this->db->closeTransaction(); + } + + public function reorderByAssignee($projectID, $swimlaneID, $columnID, $direction) + { + $this->db->startTransaction(); + + $taskIDs = $this->db->table(TaskModel::TABLE) + ->eq('tasks.project_id', $projectID) + ->eq('tasks.swimlane_id', $swimlaneID) + ->eq('tasks.column_id', $columnID) + ->orderBy('u.name', $direction) + ->orderBy('u.username', $direction) + ->orderBy('u.id', $direction) + ->left(UserModel::TABLE, 'u', 'id', TaskModel::TABLE, 'owner_id') + ->findAllByColumn('tasks.id'); + + $this->reorderTasks($taskIDs); + + $this->db->closeTransaction(); + } + + public function reorderByDueDate($projectID, $swimlaneID, $columnID, $direction) + { + $this->db->startTransaction(); + + $taskIDs = $this->db->table(TaskModel::TABLE) + ->eq('project_id', $projectID) + ->eq('swimlane_id', $swimlaneID) + ->eq('column_id', $columnID) + ->orderBy('date_due', $direction) + ->asc('id') + ->findAllByColumn('id'); + + $this->reorderTasks($taskIDs); + + $this->db->closeTransaction(); + } + + protected function reorderTasks(array $taskIDs) + { + $i = 1; + foreach ($taskIDs as $taskID) { + $this->db->table(TaskModel::TABLE) + ->eq('id', $taskID) + ->update(['position' => $i]); + $i++; + } + } +} diff --git a/app/Model/TaskStatusModel.php b/app/Model/TaskStatusModel.php new file mode 100644 index 0000000..bb6725f --- /dev/null +++ b/app/Model/TaskStatusModel.php @@ -0,0 +1,144 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Task Status + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class TaskStatusModel extends Base +{ + /** + * Return true if the task is closed + * + * @access public + * @param integer $task_id Task id + * @return boolean + */ + public function isClosed($task_id) + { + return $this->checkStatus($task_id, TaskModel::STATUS_CLOSED); + } + + /** + * Return true if the task is open + * + * @access public + * @param integer $task_id Task id + * @return boolean + */ + public function isOpen($task_id) + { + return $this->checkStatus($task_id, TaskModel::STATUS_OPEN); + } + + /** + * Mark a task closed + * + * @access public + * @param integer $task_id Task id + * @return boolean + */ + public function close($task_id) + { + $this->subtaskStatusModel->closeAll($task_id); + return $this->changeStatus($task_id, TaskModel::STATUS_CLOSED, time(), TaskModel::EVENT_CLOSE); + } + + /** + * Mark a task open + * + * @access public + * @param integer $task_id Task id + * @return boolean + */ + public function open($task_id) + { + return $this->changeStatus($task_id, TaskModel::STATUS_OPEN, 0, TaskModel::EVENT_OPEN); + } + + /** + * Close multiple tasks + * + * @access public + * @param array $task_ids + */ + public function closeMultipleTasks(array $task_ids) + { + foreach ($task_ids as $task_id) { + $this->close($task_id); + } + } + + /** + * Close all tasks within a column/swimlane + * + * @access public + * @param integer $swimlane_id + * @param integer $column_id + */ + public function closeTasksBySwimlaneAndColumn($swimlane_id, $column_id) + { + $task_ids = $this->db + ->table(TaskModel::TABLE) + ->eq('swimlane_id', $swimlane_id) + ->eq('column_id', $column_id) + ->eq(TaskModel::TABLE.'.is_active', TaskModel::STATUS_OPEN) + ->findAllByColumn('id'); + + $this->closeMultipleTasks($task_ids); + } + + /** + * Common method to change the status of task + * + * @access private + * @param integer $task_id Task id + * @param integer $status Task status + * @param integer $date_completed Timestamp + * @param string $event_name Event name + * @return boolean + */ + private function changeStatus($task_id, $status, $date_completed, $event_name) + { + if (! $this->taskFinderModel->exists($task_id)) { + return false; + } + + $result = $this->db + ->table(TaskModel::TABLE) + ->eq('id', $task_id) + ->update(array( + 'is_active' => $status, + 'date_completed' => $date_completed, + 'date_modification' => time(), + )); + + if ($result) { + $this->queueManager->push($this->taskEventJob->withParams($task_id, array($event_name))); + } + + return $result; + } + + /** + * Check the status of a task + * + * @access private + * @param integer $task_id Task id + * @param integer $status Task status + * @return boolean + */ + private function checkStatus($task_id, $status) + { + return $this->db + ->table(TaskModel::TABLE) + ->eq('id', $task_id) + ->eq('is_active', $status) + ->exists(); + } +} diff --git a/app/Model/TaskTagModel.php b/app/Model/TaskTagModel.php new file mode 100644 index 0000000..f822a48 --- /dev/null +++ b/app/Model/TaskTagModel.php @@ -0,0 +1,189 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Class TaskTagModel + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class TaskTagModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'task_has_tags'; + + /** + * Get all tags not available in a project + * + * @access public + * @param integer $task_id + * @param integer $project_id + * @return array + */ + public function getTagsByTaskNotAvailableInProject($task_id, $project_id) + { + return $this->db->table(TagModel::TABLE) + ->eq(self::TABLE.'.task_id', $task_id) + ->notIn(TagModel::TABLE.'.project_id', array(0, $project_id)) + ->join(self::TABLE, 'tag_id', 'id') + ->findAll(); + } + + /** + * Get all tags associated to a task + * + * @access public + * @param integer $task_id + * @return array + */ + public function getTagsByTask($task_id) + { + return $this->db->table(TagModel::TABLE) + ->columns(TagModel::TABLE.'.id', TagModel::TABLE.'.name', TagModel::TABLE.'.color_id') + ->eq(self::TABLE.'.task_id', $task_id) + ->join(self::TABLE, 'tag_id', 'id') + ->findAll(); + } + + /** + * Get all tags associated to a list of tasks + * + * @access public + * @param integer[] $task_ids + * @return array + */ + public function getTagsByTaskIds($task_ids) + { + if (empty($task_ids)) { + return array(); + } + + $tags = $this->db->table(TagModel::TABLE) + ->columns(TagModel::TABLE.'.id', TagModel::TABLE.'.name', TagModel::TABLE.'.color_id', self::TABLE.'.task_id') + ->in(self::TABLE.'.task_id', $task_ids) + ->join(self::TABLE, 'tag_id', 'id') + ->asc(TagModel::TABLE.'.name') + ->findAll(); + + return array_column_index($tags, 'task_id'); + } + + /** + * Get dictionary of tags + * + * @access public + * @param integer $task_id + * @return array + */ + public function getList($task_id) + { + $tags = $this->getTagsByTask($task_id); + return array_column($tags, 'name', 'id'); + } + + /** + * Add or update a list of tags to a task + * + * @access public + * @param integer $project_id + * @param integer $task_id + * @param string[] $tags + * @return boolean + */ + public function save($project_id, $task_id, array $tags, $remove_other_tags = true) + { + $task_tags = $this->getList($task_id); + $tags = array_filter($tags); + + if ($remove_other_tags) { + return $this->associateTags($project_id, $task_id, $task_tags, $tags) && + $this->dissociateTags($task_id, $task_tags, $tags); + } else { + return $this->associateTags($project_id, $task_id, $task_tags, $tags); + } + } + + /** + * Associate a tag to a task + * + * @access public + * @param integer $task_id + * @param integer $tag_id + * @return boolean + */ + public function associateTag($task_id, $tag_id) + { + return $this->db->table(self::TABLE)->insert(array( + 'task_id' => $task_id, + 'tag_id' => $tag_id, + )); + } + + /** + * Dissociate a tag from a task + * + * @access public + * @param integer $task_id + * @param integer $tag_id + * @return boolean + */ + public function dissociateTag($task_id, $tag_id) + { + return $this->db->table(self::TABLE) + ->eq('task_id', $task_id) + ->eq('tag_id', $tag_id) + ->remove(); + } + + /** + * Associate missing tags + * + * @access protected + * @param integer $project_id + * @param integer $task_id + * @param array $task_tags + * @param string[] $tags + * @return bool + */ + protected function associateTags($project_id, $task_id, $task_tags, $tags) + { + foreach ($tags as $tag) { + $tag_id = $this->tagModel->findOrCreateTag($project_id, $tag); + + if (! isset($task_tags[$tag_id]) && ! $this->associateTag($task_id, $tag_id)) { + return false; + } + } + + return true; + } + + /** + * Dissociate removed tags + * + * @access protected + * @param integer $task_id + * @param array $task_tags + * @param string[] $tags + * @return bool + */ + protected function dissociateTags($task_id, $task_tags, $tags) + { + foreach ($task_tags as $tag_id => $tag) { + if (! in_array($tag, $tags)) { + if (! $this->dissociateTag($task_id, $tag_id)) { + return false; + } + } + } + + return true; + } +} diff --git a/app/Model/ThemeModel.php b/app/Model/ThemeModel.php new file mode 100644 index 0000000..3ec9f95 --- /dev/null +++ b/app/Model/ThemeModel.php @@ -0,0 +1,29 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Class Theme + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class ThemeModel extends Base +{ + /** + * Get available theme + * + * @access public + * @return array + */ + public function getThemes() + { + return [ + 'light' => t('Light theme'), + 'dark' => t('Dark theme'), + 'auto' => t('Automatic theme - Sync with system'), + ]; + } +} diff --git a/app/Model/TimezoneModel.php b/app/Model/TimezoneModel.php new file mode 100644 index 0000000..ef6afc6 --- /dev/null +++ b/app/Model/TimezoneModel.php @@ -0,0 +1,54 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Class Timezone + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class TimezoneModel extends Base +{ + /** + * Get available timezones + * + * @access public + * @param boolean $prepend Prepend a default value + * @return array + */ + public function getTimezones($prepend = false) + { + $timezones = timezone_identifiers_list(); + $listing = array_combine(array_values($timezones), $timezones); + + if ($prepend) { + return array('' => t('Application default')) + $listing; + } + + return $listing; + } + + /** + * Get current timezone + * + * @access public + * @return string + */ + public function getCurrentTimezone() + { + return $this->userSession->getTimezone() ?: $this->configModel->get('application_timezone', 'UTC'); + } + + /** + * Set timezone + * + * @access public + */ + public function setCurrentTimezone() + { + date_default_timezone_set($this->getCurrentTimezone()); + } +} diff --git a/app/Model/TransitionModel.php b/app/Model/TransitionModel.php new file mode 100644 index 0000000..a4a5847 --- /dev/null +++ b/app/Model/TransitionModel.php @@ -0,0 +1,130 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Transition + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class TransitionModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'transitions'; + + /** + * Save transition event + * + * @access public + * @param integer $user_id + * @param array $task_event + * @return bool + */ + public function save($user_id, array $task_event) + { + $time = time(); + + return $this->db->table(self::TABLE)->insert(array( + 'user_id' => $user_id, + 'project_id' => $task_event['project_id'], + 'task_id' => $task_event['task_id'], + 'src_column_id' => $task_event['src_column_id'], + 'dst_column_id' => $task_event['dst_column_id'], + 'date' => $time, + 'time_spent' => $time - $task_event['date_moved'] + )); + } + + /** + * Get time spent by task for each column + * + * @access public + * @param integer $task_id + * @return array + */ + public function getTimeSpentByTask($task_id) + { + return $this->db + ->hashtable(self::TABLE) + ->groupBy('src_column_id') + ->eq('task_id', $task_id) + ->getAll('src_column_id', 'SUM(time_spent) AS time_spent'); + } + + /** + * 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', + UserModel::TABLE.'.name', + UserModel::TABLE.'.username', + self::TABLE.'.user_id', + self::TABLE.'.date', + self::TABLE.'.time_spent' + ) + ->eq('task_id', $task_id) + ->desc('date') + ->join(UserModel::TABLE, 'id', 'user_id') + ->join(ColumnModel::TABLE.' as src', 'id', 'src_column_id', self::TABLE, 'src') + ->join(ColumnModel::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( + TaskModel::TABLE.'.id', + TaskModel::TABLE.'.title', + 'src.title as src_column', + 'dst.title as dst_column', + UserModel::TABLE.'.name', + UserModel::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') + ->desc(self::TABLE.'.id') + ->join(TaskModel::TABLE, 'id', 'task_id') + ->join(UserModel::TABLE, 'id', 'user_id') + ->join(ColumnModel::TABLE.' as src', 'id', 'src_column_id', self::TABLE, 'src') + ->join(ColumnModel::TABLE.' as dst', 'id', 'dst_column_id', self::TABLE, 'dst') + ->findAll(); + } +} diff --git a/app/Model/UserLockingModel.php b/app/Model/UserLockingModel.php new file mode 100644 index 0000000..1d4d994 --- /dev/null +++ b/app/Model/UserLockingModel.php @@ -0,0 +1,105 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * User Locking Model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class UserLockingModel extends Base +{ + /** + * Get the number of failed login for the user + * + * @access public + * @param string $username + * @return integer + */ + public function getFailedLogin($username) + { + return (int) $this->db->table(UserModel::TABLE) + ->eq('username', $username) + ->findOneColumn('nb_failed_login'); + } + + /** + * Reset to 0 the counter of failed login + * + * @access public + * @param string $username + * @return boolean + */ + public function resetFailedLogin($username) + { + return $this->db->table(UserModel::TABLE) + ->eq('username', $username) + ->update(array( + 'nb_failed_login' => 0, + 'lock_expiration_date' => 0, + )); + } + + /** + * Increment failed login counter + * + * @access public + * @param string $username + * @return boolean + */ + public function incrementFailedLogin($username) + { + return $this->db->table(UserModel::TABLE) + ->eq('username', $username) + ->increment('nb_failed_login', 1); + } + + /** + * Check if the account is locked + * + * @access public + * @param string $username + * @return boolean + */ + public function isLocked($username) + { + return $this->db->table(UserModel::TABLE) + ->eq('username', $username) + ->neq('lock_expiration_date', 0) + ->gte('lock_expiration_date', time()) + ->exists(); + } + + /** + * Lock the account for the specified duration + * + * @access public + * @param string $username Username + * @param integer $duration Duration in minutes + * @return boolean + */ + public function lock($username, $duration = 15) + { + return $this->db->table(UserModel::TABLE) + ->eq('username', $username) + ->update(array( + 'lock_expiration_date' => time() + $duration * 60 + )); + } + + /** + * Return true if the captcha must be shown + * + * @access public + * @param string $username + * @param integer $tries + * @return boolean + */ + public function hasCaptcha($username, $tries = BRUTEFORCE_CAPTCHA) + { + return $this->getFailedLogin($username) >= $tries; + } +} diff --git a/app/Model/UserMetadataModel.php b/app/Model/UserMetadataModel.php new file mode 100644 index 0000000..42fe4c6 --- /dev/null +++ b/app/Model/UserMetadataModel.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Model; + +/** + * User Metadata + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class UserMetadataModel extends MetadataModel +{ + const KEY_COMMENT_SORTING_DIRECTION = 'comment.sorting.direction'; + const KEY_BOARD_COLLAPSED = 'board.collapsed.'; + + /** + * Get the table + * + * @abstract + * @access protected + * @return string + */ + protected function getTable() + { + return 'user_has_metadata'; + } + + /** + * Define the entity key + * + * @access protected + * @return string + */ + protected function getEntityKey() + { + return 'user_id'; + } +} diff --git a/app/Model/UserModel.php b/app/Model/UserModel.php new file mode 100644 index 0000000..f4c4f90 --- /dev/null +++ b/app/Model/UserModel.php @@ -0,0 +1,448 @@ +<?php + +namespace Kanboard\Model; + +use PicoDb\Database; +use Kanboard\Core\Base; +use Kanboard\Core\Security\Token; +use Kanboard\Core\Security\Role; +use InvalidArgumentException; + +/** + * User model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class UserModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'users'; + + /** + * Id used for everybody (filtering) + * + * @var integer + */ + const EVERYBODY_ID = -1; + + public function isValidSession($userID, $sessionRole) + { + return $this->db->table(self::TABLE) + ->eq('id', $userID) + ->eq('is_active', 1) + ->eq('role', $sessionRole) + ->exists(); + } + + public function has2FA($username) + { + return $this->db->table(self::TABLE) + ->eq('username', $username) + ->eq('is_active', 1) + ->eq('twofactor_activated', 1) + ->exists(); + } + + /** + * Return true if the user exists + * + * @access public + * @param integer $user_id User id + * @return boolean + */ + public function exists($user_id) + { + return $this->db->table(self::TABLE)->eq('id', $user_id)->exists(); + } + + /** + * Return true if the user is active + * + * @access public + * @param integer $user_id User id + * @return boolean + */ + public function isActive($user_id) + { + return $this->db->table(self::TABLE)->eq('id', $user_id)->eq('is_active', 1)->exists(); + } + + /** + * Get query to fetch all users + * + * @access public + * @return \PicoDb\Table + */ + public function getQuery() + { + return $this->db->table(self::TABLE); + } + + /** + * Return true is the given user id is administrator + * + * @access public + * @param integer $user_id User id + * @return boolean + */ + public function isAdmin($user_id) + { + return $this->userSession->isAdmin() || // Avoid SQL query if connected + $this->db + ->table(UserModel::TABLE) + ->eq('id', $user_id) + ->eq('role', Role::APP_ADMIN) + ->exists(); + } + + /** + * Get a specific user by id + * + * @access public + * @param integer $user_id User id + * @return array + */ + public function getById($user_id) + { + return $this->db->table(self::TABLE)->eq('id', $user_id)->findOne(); + } + + /** + * Get a specific user by the Google id + * + * @access public + * @param string $externalIdColumn + * @param string $externalId + * @return array|boolean + */ + public function getByExternalId($externalIdColumn, $externalId) + { + if (empty($externalId)) { + return false; + } + + if (! $this->db->isValidIdentifier($externalIdColumn)) { + throw new InvalidArgumentException('Invalid external id column name'); + } + + return $this->db->table(self::TABLE)->eq($externalIdColumn, $externalId)->findOne(); + } + + /** + * Get a specific user by the username + * + * @access public + * @param string $username Username + * @return array + */ + public function getByUsername($username) + { + return $this->db->table(self::TABLE)->eq('username', $username)->findOne(); + } + + /** + * Get user_id by username + * + * @access public + * @param string $username Username + * @return integer + */ + public function getIdByUsername($username) + { + return $this->db->table(self::TABLE)->eq('username', $username)->findOneColumn('id'); + } + + /** + * Get a specific user by the email address + * + * @access public + * @param string $email Email + * @return array|boolean + */ + public function getByEmail($email) + { + if (empty($email)) { + return false; + } + + return $this->db->table(self::TABLE)->eq('email', $email)->findOne(); + } + + /** + * Fetch user by using the token + * + * @access public + * @param string $token Token + * @return array|boolean + */ + public function getByToken($token) + { + if (empty($token)) { + return false; + } + + return $this->db + ->table(self::TABLE) + ->eq('token', $token) + ->eq('is_active', 1) + ->findOne(); + } + + /** + * Get all users + * + * @access public + * @return array + */ + public function getAll() + { + return $this->getQuery()->asc('username')->findAll(); + } + + /** + * Get the number of users + * + * @access public + * @return integer + */ + public function count() + { + return $this->db->table(self::TABLE)->count(); + } + + /** + * List all users (key-value pairs with id/username) + * + * @access public + * @param boolean $prepend Prepend "All users" + * @return array + */ + public function getActiveUsersList($prepend = false) + { + $users = $this->db->table(self::TABLE)->eq('is_active', 1)->columns('id', 'username', 'name')->findAll(); + $listing = $this->prepareList($users); + + if ($prepend) { + return array(UserModel::EVERYBODY_ID => t('Everybody')) + $listing; + } + + return $listing; + } + + /** + * Common method to prepare a user list + * + * @access public + * @param array $users Users list (from database) + * @return array Formated list + */ + public function prepareList(array $users) + { + $result = array(); + + foreach ($users as $user) { + $result[$user['id']] = $this->helper->user->getFullname($user); + } + + asort($result); + + return $result; + } + + /** + * Prepare values before an update or a create + * + * @access public + * @param array $values Form values + */ + public function prepare(array &$values) + { + if (isset($values['password'])) { + if (! empty($values['password'])) { + $values['password'] = \password_hash($values['password'], PASSWORD_BCRYPT); + } else { + unset($values['password']); + } + } + + if (isset($values['username'])) { + $values['username'] = trim($values['username']); + } + + $this->helper->model->removeFields($values, array('confirmation', 'current_password')); + $this->helper->model->resetFields($values, array('is_ldap_user', 'disable_login_form')); + $this->helper->model->convertNullFields($values, array('gitlab_id')); + $this->helper->model->convertIntegerFields($values, array('gitlab_id')); + } + + /** + * Add a new user in the database + * + * @access public + * @param array $values Form values + * @return boolean|integer + */ + public function create(array $values) + { + $this->prepare($values); + return $this->db->table(self::TABLE)->persist($values); + } + + /** + * Modify a new user + * + * @access public + * @param array $values Form values + * @return boolean + */ + public function update(array $values) + { + $this->prepare($values); + $updates = $values; + unset($updates['id']); + $result = $this->db->table(self::TABLE)->eq('id', $values['id'])->update($updates); + $this->userSession->refresh($values['id']); + return $result; + } + + /** + * Disable a specific user + * + * @access public + * @param integer $user_id + * @return boolean + */ + public function disable($user_id) + { + $this->db->startTransaction(); + $result1 = $this->db->table(self::TABLE)->eq('id', $user_id)->update(array( + 'is_active' => 0, + 'token' => '', + )); + $result2 = $this->db->table(ProjectModel::TABLE)->eq('is_private', 1)->eq('owner_id', $user_id)->update(array('is_active' => 0)); + $this->db->closeTransaction(); + return $result1 && $result2; + } + + /** + * Enable a specific user + * + * @access public + * @param integer $user_id + * @return boolean + */ + public function enable($user_id) + { + return $this->db->table(self::TABLE)->eq('id', $user_id)->update(array('is_active' => 1)); + } + + /** + * Remove a specific user + * + * @access public + * @param integer $user_id User id + * @return boolean + */ + public function remove($user_id) + { + $this->avatarFileModel->remove($user_id); + + return $this->db->transaction(function (Database $db) use ($user_id) { + + // All assigned tasks are now unassigned (no foreign key) + if (! $db->table(TaskModel::TABLE)->eq('owner_id', $user_id)->update(array('owner_id' => 0))) { + return false; + } + + // All assigned subtasks are now unassigned (no foreign key) + if (! $db->table(SubtaskModel::TABLE)->eq('user_id', $user_id)->update(array('user_id' => 0))) { + return false; + } + + // All comments are not assigned anymore (no foreign key) + if (! $db->table(CommentModel::TABLE)->eq('user_id', $user_id)->update(array('user_id' => 0))) { + return false; + } + + // All private projects are removed + $project_ids = $db->table(ProjectModel::TABLE) + ->eq('is_private', 1) + ->eq(ProjectUserRoleModel::TABLE.'.user_id', $user_id) + ->join(ProjectUserRoleModel::TABLE, 'project_id', 'id') + ->findAllByColumn(ProjectModel::TABLE.'.id'); + + if (! empty($project_ids)) { + $db->table(ProjectModel::TABLE)->in('id', $project_ids)->remove(); + } + + // Finally remove the user + if (! $db->table(UserModel::TABLE)->eq('id', $user_id)->remove()) { + return false; + } + }); + } + + /** + * Enable public access for a user + * + * @access public + * @param integer $user_id User id + * @return bool + */ + public function enablePublicAccess($user_id) + { + return $this->db + ->table(self::TABLE) + ->eq('id', $user_id) + ->save(array('token' => Token::getToken())); + } + + /** + * Disable public access for a user + * + * @access public + * @param integer $user_id User id + * @return bool + */ + public function disablePublicAccess($user_id) + { + return $this->db + ->table(self::TABLE) + ->eq('id', $user_id) + ->save(array('token' => '')); + } + + /** + * Get or create user id by using an external id (Google, GitHub, etc.) + * + * @param string $username Username + * @param string $name Full name + * @param string $externalIdColumn Column name for the external id (e.g. google_id, github_id, etc.) + * @param string $externalId External id (e.g. Google id, GitHub id, etc.) + * @return integer User id + */ + public function getOrCreateExternalUserId($username, $name, $externalIdColumn, $externalId) + { + if (! $this->db->isValidIdentifier($externalIdColumn)) { + throw new InvalidArgumentException('Invalid external id column name'); + } + + $userId = $this->db->table(self::TABLE)->eq($externalIdColumn, $externalId)->findOneColumn('id'); + + if (empty($userId)) { + $userId = $this->create(array( + 'username' => $username, + 'name' => $name, + 'is_ldap_user' => 1, + $externalIdColumn => $externalId, + )); + } + + return $userId; + } +} diff --git a/app/Model/UserNotificationFilterModel.php b/app/Model/UserNotificationFilterModel.php new file mode 100644 index 0000000..5cb2da6 --- /dev/null +++ b/app/Model/UserNotificationFilterModel.php @@ -0,0 +1,206 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * User Notification Filter + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class UserNotificationFilterModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const PROJECT_TABLE = 'user_has_notifications'; + + /** + * User filters + * + * @var integer + */ + const FILTER_NONE = 1; + const FILTER_ASSIGNEE = 2; + const FILTER_CREATOR = 3; + const FILTER_BOTH = 4; + + /** + * Get the list of filters + * + * @access public + * @return array + */ + public function getFilters() + { + return array( + self::FILTER_NONE => t('All tasks'), + self::FILTER_ASSIGNEE => t('Only for tasks assigned to me'), + self::FILTER_CREATOR => t('Only for tasks created by me'), + self::FILTER_BOTH => t('Only for tasks created by me and tasks assigned to me'), + ); + } + + /** + * Get user selected filter + * + * @access public + * @param integer $user_id + * @return integer + */ + public function getSelectedFilter($user_id) + { + return $this->db->table(UserModel::TABLE)->eq('id', $user_id)->findOneColumn('notifications_filter'); + } + + /** + * Save selected filter for a user + * + * @access public + * @param integer $user_id + * @param string $filter + * @return boolean + */ + public function saveFilter($user_id, $filter) + { + return $this->db->table(UserModel::TABLE)->eq('id', $user_id)->update(array( + 'notifications_filter' => $filter, + )); + } + + /** + * Get user selected projects + * + * @access public + * @param integer $user_id + * @return array + */ + public function getSelectedProjects($user_id) + { + return $this->db->table(self::PROJECT_TABLE)->eq('user_id', $user_id)->findAllByColumn('project_id'); + } + + /** + * Save selected projects for a user + * + * @access public + * @param integer $user_id + * @param integer[] $project_ids + * @return boolean + */ + public function saveSelectedProjects($user_id, array $project_ids) + { + $results = array(); + $this->db->table(self::PROJECT_TABLE)->eq('user_id', $user_id)->remove(); + + foreach ($project_ids as $project_id) { + $results[] = $this->db->table(self::PROJECT_TABLE)->insert(array( + 'user_id' => $user_id, + 'project_id' => $project_id, + )); + } + + return !in_array(false, $results, true); + } + + /** + * Return true if the user should receive notification + * + * @access public + * @param array $user + * @param array $event_data + * @return boolean + */ + public function shouldReceiveNotification(array $user, array $event_data) + { + $filters = array( + 'filterNone', + 'filterAssignee', + 'filterCreator', + 'filterBoth', + ); + + foreach ($filters as $filter) { + if ($this->$filter($user, $event_data)) { + return $this->filterProject($user, $event_data); + } + } + + return false; + } + + /** + * Return true if the user will receive all notifications + * + * @access public + * @param array $user + * @return boolean + */ + public function filterNone(array $user) + { + return $user['notifications_filter'] == self::FILTER_NONE; + } + + /** + * Return true if the user is the assignee and selected the filter "assignee" + * + * @access public + * @param array $user + * @param array $event_data + * @return boolean + */ + public function filterAssignee(array $user, array $event_data) + { + return $user['notifications_filter'] == self::FILTER_ASSIGNEE && $event_data['task']['owner_id'] == $user['id']; + } + + /** + * Return true if the user is the creator and enabled the filter "creator" + * + * @access public + * @param array $user + * @param array $event_data + * @return boolean + */ + public function filterCreator(array $user, array $event_data) + { + return $user['notifications_filter'] == self::FILTER_CREATOR && $event_data['task']['creator_id'] == $user['id']; + } + + /** + * Return true if the user is the assignee or the creator and selected the filter "both" + * + * @access public + * @param array $user + * @param array $event_data + * @return boolean + */ + public function filterBoth(array $user, array $event_data) + { + return $user['notifications_filter'] == self::FILTER_BOTH && + ($event_data['task']['creator_id'] == $user['id'] || $event_data['task']['owner_id'] == $user['id']); + } + + /** + * Return true if the user want to receive notification for the selected project + * + * @access public + * @param array $user + * @param array $event_data + * @return boolean + */ + public function filterProject(array $user, array $event_data) + { + $projects = $this->getSelectedProjects($user['id']); + + if (! empty($projects)) { + return in_array($event_data['task']['project_id'], $projects); + } + + return true; + } +} diff --git a/app/Model/UserNotificationModel.php b/app/Model/UserNotificationModel.php new file mode 100644 index 0000000..6e4adc3 --- /dev/null +++ b/app/Model/UserNotificationModel.php @@ -0,0 +1,183 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; +use Kanboard\Core\Translator; + +/** + * User Notification + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class UserNotificationModel extends Base +{ + /** + * Send notifications to people + * + * @access public + * @param string $event_name + * @param array $event_data + */ + public function sendNotifications($event_name, array $event_data) + { + $users = $this->getUsersWithNotificationEnabled($event_data['task']['project_id'], $this->userSession->getId()); + + foreach ($users as $user) { + if ($this->userNotificationFilterModel->shouldReceiveNotification($user, $event_data)) { + $this->sendUserNotification($user, $event_name, $event_data); + } + } + } + + /** + * Send notification to someone + * + * @access public + * @param array $user User + * @param string $event_name + * @param array $event_data + */ + public function sendUserNotification(array $user, $event_name, array $event_data) + { + $loadedLocales = Translator::$locales; + Translator::unload(); + + // Use the user language otherwise use the application language (do not use the session language) + if (! empty($user['language'])) { + Translator::load($user['language']); + } else { + Translator::load($this->configModel->get('application_language', 'en_US')); + } + + foreach ($this->userNotificationTypeModel->getSelectedTypes($user['id']) as $type) { + $this->userNotificationTypeModel->getType($type)->notifyUser($user, $event_name, $event_data); + } + + // Restore locales + Translator::$locales = $loadedLocales; + } + + /** + * Get a list of people with notifications enabled + * + * @access public + * @param integer $project_id Project id + * @param integer $exclude_user_id User id to exclude + * @return array + */ + public function getUsersWithNotificationEnabled($project_id, $exclude_user_id = 0) + { + $users = array(); + $members = $this->getProjectUserMembersWithNotificationEnabled($project_id, $exclude_user_id); + $groups = $this->getProjectGroupMembersWithNotificationEnabled($project_id, $exclude_user_id); + + foreach (array_merge($members, $groups) as $user) { + if (! isset($users[$user['id']])) { + $users[$user['id']] = $user; + } + } + + return array_values($users); + } + + /** + * Enable notification for someone + * + * @access public + * @param integer $user_id + * @return boolean + */ + public function enableNotification($user_id) + { + return $this->db->table(UserModel::TABLE)->eq('id', $user_id)->update(array('notifications_enabled' => 1)); + } + + /** + * Disable notification for someone + * + * @access public + * @param integer $user_id + * @return boolean + */ + public function disableNotification($user_id) + { + return $this->db->table(UserModel::TABLE)->eq('id', $user_id)->update(array('notifications_enabled' => 0)); + } + + /** + * Save settings for the given user + * + * @access public + * @param integer $user_id User id + * @param array $values Form values + */ + public function saveSettings($user_id, array $values) + { + $types = empty($values['notification_types']) ? array() : array_keys($values['notification_types']); + + if (! empty($types)) { + $this->enableNotification($user_id); + } else { + $this->disableNotification($user_id); + } + + $filter = empty($values['notifications_filter']) ? UserNotificationFilterModel::FILTER_BOTH : $values['notifications_filter']; + $project_ids = empty($values['notification_projects']) ? array() : array_keys($values['notification_projects']); + + $this->userNotificationFilterModel->saveFilter($user_id, $filter); + $this->userNotificationFilterModel->saveSelectedProjects($user_id, $project_ids); + $this->userNotificationTypeModel->saveSelectedTypes($user_id, $types); + } + + /** + * Read user settings to display the form + * + * @access public + * @param integer $user_id User id + * @return array + */ + public function readSettings($user_id) + { + $values = $this->db->table(UserModel::TABLE)->eq('id', $user_id)->columns('notifications_enabled', 'notifications_filter')->findOne(); + $values['notification_types'] = $this->userNotificationTypeModel->getSelectedTypes($user_id); + $values['notification_projects'] = $this->userNotificationFilterModel->getSelectedProjects($user_id); + return $values; + } + + /** + * Get a list of group members with notification enabled + * + * @access private + * @param integer $project_id Project id + * @param integer $exclude_user_id User id to exclude + * @return array + */ + private function getProjectUserMembersWithNotificationEnabled($project_id, $exclude_user_id) + { + return $this->db + ->table(ProjectUserRoleModel::TABLE) + ->columns(UserModel::TABLE.'.id', UserModel::TABLE.'.username', UserModel::TABLE.'.name', UserModel::TABLE.'.email', UserModel::TABLE.'.language', UserModel::TABLE.'.notifications_filter') + ->join(UserModel::TABLE, 'id', 'user_id') + ->eq(ProjectUserRoleModel::TABLE.'.project_id', $project_id) + ->eq(UserModel::TABLE.'.notifications_enabled', '1') + ->eq(UserModel::TABLE.'.is_active', 1) + ->neq(UserModel::TABLE.'.id', $exclude_user_id) + ->findAll(); + } + + private function getProjectGroupMembersWithNotificationEnabled($project_id, $exclude_user_id) + { + return $this->db + ->table(ProjectGroupRoleModel::TABLE) + ->columns(UserModel::TABLE.'.id', UserModel::TABLE.'.username', UserModel::TABLE.'.name', UserModel::TABLE.'.email', UserModel::TABLE.'.language', UserModel::TABLE.'.notifications_filter') + ->join(GroupMemberModel::TABLE, 'group_id', 'group_id', ProjectGroupRoleModel::TABLE) + ->join(UserModel::TABLE, 'id', 'user_id', GroupMemberModel::TABLE) + ->eq(ProjectGroupRoleModel::TABLE.'.project_id', $project_id) + ->eq(UserModel::TABLE.'.notifications_enabled', '1') + ->neq(UserModel::TABLE.'.id', $exclude_user_id) + ->eq(UserModel::TABLE.'.is_active', 1) + ->findAll(); + } +} diff --git a/app/Model/UserNotificationTypeModel.php b/app/Model/UserNotificationTypeModel.php new file mode 100644 index 0000000..0f37722 --- /dev/null +++ b/app/Model/UserNotificationTypeModel.php @@ -0,0 +1,52 @@ +<?php + +namespace Kanboard\Model; + +/** + * User Notification Type + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class UserNotificationTypeModel extends NotificationTypeModel +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'user_has_notification_types'; + + /** + * Get selected notification types for a given user + * + * @access public + * @param integer $user_id + * @return array + */ + public function getSelectedTypes($user_id) + { + $types = $this->db->table(self::TABLE)->eq('user_id', $user_id)->asc('notification_type')->findAllByColumn('notification_type'); + return $this->filterTypes($types); + } + + /** + * Save notification types for a given user + * + * @access public + * @param integer $user_id + * @param string[] $types + * @return boolean + */ + public function saveSelectedTypes($user_id, array $types) + { + $results = array(); + $this->db->table(self::TABLE)->eq('user_id', $user_id)->remove(); + + foreach ($types as $type) { + $results[] = $this->db->table(self::TABLE)->insert(array('user_id' => $user_id, 'notification_type' => $type)); + } + + return ! in_array(false, $results, true); + } +} diff --git a/app/Model/UserUnreadNotificationModel.php b/app/Model/UserUnreadNotificationModel.php new file mode 100644 index 0000000..6c930ee --- /dev/null +++ b/app/Model/UserUnreadNotificationModel.php @@ -0,0 +1,117 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * User Unread Notification + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class UserUnreadNotificationModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'user_has_unread_notifications'; + + /** + * Add unread notification to someone + * + * @access public + * @param integer $user_id + * @param string $event_name + * @param array $event_data + */ + public function create($user_id, $event_name, array $event_data) + { + $this->db->table(self::TABLE)->insert(array( + 'user_id' => $user_id, + 'date_creation' => time(), + 'event_name' => $event_name, + 'event_data' => json_encode($event_data), + )); + } + + /** + * Get one notification + * + * @param integer $notification_id + * @return array|null + */ + public function getById($notification_id) + { + $notification = $this->db->table(self::TABLE)->eq('id', $notification_id)->findOne(); + + if (! empty($notification)) { + $this->unserialize($notification); + } + + return $notification; + } + + /** + * Get all notifications for a user + * + * @access public + * @param integer $user_id + * @return array + */ + public function getAll($user_id) + { + $events = $this->db->table(self::TABLE)->eq('user_id', $user_id)->asc('date_creation')->findAll(); + + foreach ($events as &$event) { + $this->unserialize($event); + } + + return $events; + } + + /** + * Mark a notification as read + * + * @access public + * @param integer $user_id + * @param integer $notification_id + * @return boolean + */ + public function markAsRead($user_id, $notification_id) + { + return $this->db->table(self::TABLE)->eq('id', $notification_id)->eq('user_id', $user_id)->remove(); + } + + /** + * Mark all notifications as read for a user + * + * @access public + * @param integer $user_id + * @return boolean + */ + public function markAllAsRead($user_id) + { + return $this->db->table(self::TABLE)->eq('user_id', $user_id)->remove(); + } + + /** + * Return true if the user as unread notifications + * + * @access public + * @param integer $user_id + * @return boolean + */ + public function hasNotifications($user_id) + { + return $this->db->table(self::TABLE)->eq('user_id', $user_id)->exists(); + } + + private function unserialize(&$event) + { + $event['event_data'] = json_decode($event['event_data'], true); + $event['title'] = $this->notificationModel->getTitleWithoutAuthor($event['event_name'], $event['event_data']); + } +} diff --git a/app/Notification/ActivityStreamNotification.php b/app/Notification/ActivityStreamNotification.php new file mode 100644 index 0000000..9f23c88 --- /dev/null +++ b/app/Notification/ActivityStreamNotification.php @@ -0,0 +1,48 @@ +<?php + +namespace Kanboard\Notification; + +use Kanboard\Core\Base; +use Kanboard\Core\Notification\NotificationInterface; + +/** + * Activity Stream Notification + * + * @package Kanboard\Notification + * @author Frederic Guillot + */ +class ActivityStreamNotification extends Base implements NotificationInterface +{ + /** + * Send notification to a user + * + * @access public + * @param array $user + * @param string $event_name + * @param array $event_data + */ + public function notifyUser(array $user, $event_name, array $event_data) + { + } + + /** + * Send notification to a project + * + * @access public + * @param array $project + * @param string $event_name + * @param array $event_data + */ + public function notifyProject(array $project, $event_name, array $event_data) + { + if ($this->userSession->isLogged()) { + $this->projectActivityModel->createEvent( + $project['id'], + $event_data['task']['id'], + $this->userSession->getId(), + $event_name, + $event_data + ); + } + } +} diff --git a/app/Notification/MailNotification.php b/app/Notification/MailNotification.php new file mode 100644 index 0000000..90ca432 --- /dev/null +++ b/app/Notification/MailNotification.php @@ -0,0 +1,84 @@ +<?php + +namespace Kanboard\Notification; + +use Kanboard\Core\Base; +use Kanboard\Core\Notification\NotificationInterface; + +/** + * Email Notification + * + * @package Kanboard\Notification + * @author Frederic Guillot + */ +class MailNotification extends Base implements NotificationInterface +{ + /** + * Notification type + * + * @var string + */ + const TYPE = 'email'; + + /** + * Send notification to a user + * + * @access public + * @param array $user + * @param string $event_name + * @param array $event_data + */ + public function notifyUser(array $user, $event_name, array $event_data) + { + if (! empty($user['email'])) { + $this->emailClient->send( + $user['email'], + $user['name'] ?: $user['username'], + $this->getMailSubject($event_name, $event_data), + $this->getMailContent($event_name, $event_data) + ); + } + } + + /** + * Send notification to a project + * + * @access public + * @param array $project + * @param string $event_name + * @param array $event_data + */ + public function notifyProject(array $project, $event_name, array $event_data) + { + } + + /** + * Get the mail content for a given template name + * + * @access public + * @param string $event_name Event name + * @param array $event_data Event data + * @return string + */ + public function getMailContent($event_name, array $event_data) + { + return $this->template->render('notification/'.str_replace('.', '_', $event_name), $event_data); + } + + /** + * Get the mail subject for a given template name + * + * @access public + * @param string $eventName Event name + * @param array $eventData Event data + * @return string + */ + public function getMailSubject($eventName, array $eventData) + { + return sprintf( + '[%s] %s', + isset($eventData['project_name']) ? $eventData['project_name'] : $eventData['task']['project_name'], + $this->notificationModel->getTitleWithoutAuthor($eventName, $eventData) + ); + } +} diff --git a/app/Notification/WebNotification.php b/app/Notification/WebNotification.php new file mode 100644 index 0000000..d881882 --- /dev/null +++ b/app/Notification/WebNotification.php @@ -0,0 +1,47 @@ +<?php + +namespace Kanboard\Notification; + +use Kanboard\Core\Base; +use Kanboard\Core\Notification\NotificationInterface; + +/** + * Web Notification + * + * @package Kanboard\Notification + * @author Frederic Guillot + */ +class WebNotification extends Base implements NotificationInterface +{ + /** + * Notification type + * + * @var string + */ + const TYPE = 'web'; + + /** + * Send notification to a user + * + * @access public + * @param array $user + * @param string $event_name + * @param array $event_data + */ + public function notifyUser(array $user, $event_name, array $event_data) + { + $this->userUnreadNotificationModel->create($user['id'], $event_name, $event_data); + } + + /** + * Send notification to a project + * + * @access public + * @param array $project + * @param string $event_name + * @param array $event_data + */ + public function notifyProject(array $project, $event_name, array $event_data) + { + } +} diff --git a/app/Notification/WebhookNotification.php b/app/Notification/WebhookNotification.php new file mode 100644 index 0000000..2d009fc --- /dev/null +++ b/app/Notification/WebhookNotification.php @@ -0,0 +1,62 @@ +<?php + +namespace Kanboard\Notification; + +use Kanboard\Core\Base; +use Kanboard\Core\Notification\NotificationInterface; + +/** + * Webhook Notification + * + * @package Kanboard\Notification + * @author Frederic Guillot + */ +class WebhookNotification extends Base implements NotificationInterface +{ + /** + * Send notification to a user + * + * @access public + * @param array $user + * @param string $event_name + * @param array $event_data + */ + public function notifyUser(array $user, $event_name, array $event_data) + { + } + + /** + * Send notification to a project + * + * @access public + * @param array $project + * @param string $event_name + * @param array $event_data + */ + public function notifyProject(array $project, $event_name, array $event_data) + { + $url = $this->configModel->get('webhook_url'); + $token = $this->configModel->get('webhook_token'); + + if (! empty($url)) { + if (! WEBHOOK_ALLOW_PRIVATE_NETWORKS && $this->httpClient->isPrivateURL($url)) { + $this->logger->info('Blocked webhook request to private network URL: '.$url); + return; + } + + if (strpos($url, '?') !== false) { + $url .= '&token='.$token; + } else { + $url .= '?token='.$token; + } + + $payload = array( + 'event_name' => $event_name, + 'event_data' => $event_data, + 'event_author' => ($this->userSession->isLogged() ? $this->userSession->getUsername() : null), + ); + + $this->httpClient->postJson($url, $payload, [], false, false); + } + } +} diff --git a/app/Pagination/DashboardPagination.php b/app/Pagination/DashboardPagination.php new file mode 100644 index 0000000..9669cb9 --- /dev/null +++ b/app/Pagination/DashboardPagination.php @@ -0,0 +1,54 @@ +<?php + +namespace Kanboard\Pagination; + +use Kanboard\Core\Base; +use Kanboard\Model\ProjectModel; +use Kanboard\Model\TaskModel; + +/** + * Class DashboardPagination + * + * @package Kanboard\Pagination + * @author Frederic Guillot + */ +class DashboardPagination extends Base +{ + /** + * Get user listing pagination + * + * @access public + * @param integer $userId + * @return array + */ + public function getOverview($userId) + { + $paginators = array(); + $projects = $this->projectUserRoleModel->getActiveProjectsByUser($userId); + + foreach ($projects as $projectId => $projectName) { + + $query = $this->taskFinderModel->getUserQuery($userId)->eq(ProjectModel::TABLE.'.id', $projectId); + $this->hook->reference('pagination:dashboard:task:query', $query); + + $paginator = $this->paginator + ->setUrl('DashboardController', 'show', array('user_id' => $userId, 'pagination' => 'tasks-'.$projectId), 'project-tasks-'.$projectId) + ->setMax(15) + ->setOrder(TaskModel::TABLE.'.priority') + ->setDirection('DESC') + ->setFormatter($this->taskListSubtaskAssigneeFormatter->withUserId($userId)) + ->setQuery($query) + ->calculateOnlyIf($this->request->getStringParam('pagination') === 'tasks-'.$projectId); + + if ($paginator->getTotal() > 0) { + $paginators[] = array( + 'project_id' => $projectId, + 'project_name' => $projectName, + 'paginator' => $paginator, + ); + } + } + + return $paginators; + } +} diff --git a/app/Pagination/ProjectPagination.php b/app/Pagination/ProjectPagination.php new file mode 100644 index 0000000..563998f --- /dev/null +++ b/app/Pagination/ProjectPagination.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Pagination; + +use Kanboard\Core\Base; +use Kanboard\Core\Paginator; +use Kanboard\Model\ProjectModel; + +/** + * Class ProjectPagination + * + * @package Kanboard\Pagination + * @author Frederic Guillot + */ +class ProjectPagination extends Base +{ + /** + * Get dashboard pagination + * + * @access public + * @param integer $user_id + * @param string $method + * @param integer $max + * @return Paginator + */ + public function getDashboardPaginator($user_id, $method, $max) + { + $query = $this->projectModel->getQueryColumnStats($this->projectPermissionModel->getActiveProjectIds($user_id)); + $this->hook->reference('pagination:dashboard:project:query', $query); + + return $this->paginator + ->setUrl('DashboardController', $method, array('pagination' => 'projects', 'user_id' => $user_id)) + ->setMax($max) + ->setOrder(ProjectModel::TABLE.'.name') + ->setQuery($query) + ->calculateOnlyIf($this->request->getStringParam('pagination') === 'projects'); + } +} diff --git a/app/Pagination/SubtaskPagination.php b/app/Pagination/SubtaskPagination.php new file mode 100644 index 0000000..51a99a8 --- /dev/null +++ b/app/Pagination/SubtaskPagination.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Pagination; + +use Kanboard\Core\Base; +use Kanboard\Core\Paginator; +use Kanboard\Model\TaskModel; + +/** + * Class SubtaskPagination + * + * @package Kanboard\Pagination + * @author Frederic Guillot + */ +class SubtaskPagination extends Base +{ + /** + * Get dashboard pagination + * + * @access public + * @param integer $userId + * @return Paginator + */ + public function getDashboardPaginator($userId) + { + $query = $this->taskFinderModel->getUserQuery($userId); + $this->hook->reference('pagination:dashboard:subtask:query', $query); + + return $this->paginator + ->setUrl('DashboardController', 'subtasks', array('user_id' => $userId)) + ->setMax(50) + ->setOrder(TaskModel::TABLE.'.priority') + ->setDirection('DESC') + ->setFormatter($this->taskListSubtaskAssigneeFormatter->withUserId($userId)->withoutEmptyTasks()) + ->setQuery($query) + ->calculate(); + } +} diff --git a/app/Pagination/TaskPagination.php b/app/Pagination/TaskPagination.php new file mode 100644 index 0000000..53e05c1 --- /dev/null +++ b/app/Pagination/TaskPagination.php @@ -0,0 +1,39 @@ +<?php + +namespace Kanboard\Pagination; + +use Kanboard\Core\Base; +use Kanboard\Core\Paginator; +use Kanboard\Model\TaskModel; + +/** + * Class TaskPagination + * + * @package Kanboard\Pagination + * @author Frederic Guillot + */ +class TaskPagination extends Base +{ + /** + * Get dashboard pagination + * + * @access public + * @param integer $userId + * @param string $method + * @param integer $max + * @return Paginator + */ + public function getDashboardPaginator($userId, $method, $max) + { + $query = $this->taskFinderModel->getUserQuery($userId); + $this->hook->reference('pagination:dashboard:task:query', $query); + + return $this->paginator + ->setUrl('DashboardController', $method, array('pagination' => 'tasks', 'user_id' => $userId)) + ->setMax($max) + ->setOrder(TaskModel::TABLE.'.id') + ->setQuery($query) + ->setFormatter($this->taskListFormatter) + ->calculateOnlyIf($this->request->getStringParam('pagination') === 'tasks'); + } +} diff --git a/app/Pagination/UserPagination.php b/app/Pagination/UserPagination.php new file mode 100644 index 0000000..8768857 --- /dev/null +++ b/app/Pagination/UserPagination.php @@ -0,0 +1,32 @@ +<?php + +namespace Kanboard\Pagination; + +use Kanboard\Core\Base; +use Kanboard\Core\Paginator; +use Kanboard\Model\UserModel; + +/** + * Class UserPagination + * + * @package Kanboard\Pagination + * @author Frederic Guillot + */ +class UserPagination extends Base +{ + /** + * Get user listing pagination + * + * @access public + * @return Paginator + */ + public function getListingPaginator() + { + return $this->paginator + ->setUrl('UserListController', 'show') + ->setMax(30) + ->setOrder(UserModel::TABLE.'.username') + ->setQuery($this->userModel->getQuery()) + ->calculate(); + } +} diff --git a/app/Schema/Migration.php b/app/Schema/Migration.php new file mode 100644 index 0000000..8264523 --- /dev/null +++ b/app/Schema/Migration.php @@ -0,0 +1,79 @@ +<?php + +namespace Schema; + +use PDO; + +function migrate_default_swimlane(PDO $pdo) +{ + $projects = get_all_projects($pdo); + + foreach ($projects as $project) { + if (empty($project['default_swimlane'])) { + $project['default_swimlane'] = 'Default swimlane'; + } + + $rq = $pdo->prepare('SELECT 1 FROM swimlanes WHERE name=? AND project_id=?'); + $rq->execute(array($project['default_swimlane'], $project['id'])); + + if ($rq->fetchColumn()) { + $project['default_swimlane'] = $project['default_swimlane'].' (Default swimlane)'; + } + + // Create new default swimlane + $rq = $pdo->prepare('INSERT INTO swimlanes (project_id, name, is_active, position) VALUES (?, ?, ?, ?)'); + $rq->execute(array( + $project['id'], + $project['default_swimlane'], + (int) $project['show_default_swimlane'], + $project['show_default_swimlane'] == 1 ? 1 : 0, + )); + + $swimlaneId = get_last_insert_id($pdo); + + // Reorder swimlanes if the default one was active + if ($project['show_default_swimlane']) { + $rq = $pdo->prepare("UPDATE swimlanes SET position=position+1 WHERE project_id=? AND is_active='1' AND id!=?"); + $rq->execute(array( + $project['id'], + $swimlaneId, + )); + } + + // Move all tasks to new swimlane + $rq = $pdo->prepare("UPDATE tasks SET swimlane_id=? WHERE swimlane_id='0' AND project_id=?"); + $rq->execute(array( + $swimlaneId, + $project['id'], + )); + + // Migrate automatic actions + $rq = $pdo->prepare("SELECT action_has_params.id FROM action_has_params LEFT JOIN actions ON actions.id=action_has_params.action_id WHERE project_id=? AND name='swimlane_id' AND value='0'"); + $rq->execute(array($project['id'])); + $ids = $rq->fetchAll(PDO::FETCH_COLUMN, 0); + + $rq = $pdo->prepare("UPDATE action_has_params SET value=? WHERE id=?"); + + foreach ($ids as $id) { + $rq->execute(array($swimlaneId, $id)); + } + } +} + +function get_all_projects(PDO $pdo) +{ + $rq = $pdo->prepare('SELECT * FROM projects'); + $rq->execute(); + return $rq->fetchAll(PDO::FETCH_ASSOC); +} + +function get_last_insert_id(PDO $pdo) +{ + if (DB_DRIVER === 'postgres') { + $rq = $pdo->prepare('SELECT LASTVAL()'); + $rq->execute(); + return $rq->fetchColumn(); + } + + return $pdo->lastInsertId(); +} diff --git a/app/Schema/Mssql.php b/app/Schema/Mssql.php new file mode 100644 index 0000000..4d8b268 --- /dev/null +++ b/app/Schema/Mssql.php @@ -0,0 +1,733 @@ +<?php + +namespace Schema; + +require_once __DIR__.'/Migration.php'; + +use PDO; +use Kanboard\Core\Security\Token; +use Kanboard\Core\Security\Role; + +const VERSION = 3; + +function version_3(PDO $pdo) +{ + $pdo->exec("ALTER TABLE dbo.comments ADD visibility nvarchar(25) DEFAULT N'".Role::APP_USER."' NOT NULL"); +} + +function version_2(PDO $pdo) +{ + $pdo->exec("ALTER TABLE dbo.users ADD theme nvarchar(50) DEFAULT N'light' NOT NULL"); +} + +function version_1(PDO $pdo) +{ + // create tables + $pdo->exec(" + CREATE TABLE dbo.users ( + id int identity PRIMARY KEY + , username nvarchar(255) NOT NULL + , password nvarchar(255) + , is_ldap_user bit DEFAULT 0 + , name nvarchar(255) + , email nvarchar(255) + , google_id nvarchar(255) + , github_id nvarchar(30) + , notifications_enabled bit DEFAULT 0 + , timezone nvarchar(50) DEFAULT N'' + , language nvarchar(11) DEFAULT N'' + , disable_login_form bit DEFAULT 0 + , twofactor_activated bit DEFAULT 0 + , twofactor_secret char(16) + , token nvarchar(255) DEFAULT N'' + , notifications_filter int DEFAULT 4 + , nb_failed_login int DEFAULT 0 + , lock_expiration_date bigint DEFAULT 0 + , gitlab_id int + , role nvarchar(25) NOT NULL + , is_active bit DEFAULT 1 + , avatar_path nvarchar(255) + , api_access_token nvarchar(255) + , filter nvarchar(max) DEFAULT N'' + ); + "); + $pdo->exec(" + CREATE TABLE dbo.projects ( + id int identity PRIMARY KEY + , name nvarchar(max) NOT NULL + , is_active bit DEFAULT 1 + , token nvarchar(255) + , last_modified bigint DEFAULT 0 + , is_public bit DEFAULT 0 + , is_private bit DEFAULT 0 + , description nvarchar(max) + , identifier nvarchar(50) DEFAULT N'' + , start_date nvarchar(10) DEFAULT '' + , end_date nvarchar(10) DEFAULT '' + , owner_id int DEFAULT 0 + , priority_default int DEFAULT 0 + , priority_start int DEFAULT 0 + , priority_end int DEFAULT 3 + , email nvarchar(max) + , predefined_email_subjects nvarchar(max) + , per_swimlane_task_limits bit DEFAULT 0 NOT NULL + , task_limit int DEFAULT 0 + , enable_global_tags bit DEFAULT 1 NOT NULL + ); + "); + $pdo->exec(" + CREATE TABLE dbo.columns ( + id int identity PRIMARY KEY + , title nvarchar(255) NOT NULL + , position int + , project_id int NOT NULL + , task_limit int DEFAULT 0 + , description nvarchar(max) + , hide_in_dashboard bit DEFAULT 0 NOT NULL + , FOREIGN KEY(project_id) REFERENCES dbo.projects(id) ON DELETE NO ACTION /* projects_cascade_delete_trigger */ + , UNIQUE (title, project_id) + ); + "); + $pdo->exec(" + CREATE TABLE dbo.project_has_users ( + project_id int NOT NULL + , user_id int NOT NULL + , role nvarchar(255) NOT NULL + , FOREIGN KEY(project_id) REFERENCES dbo.projects(id) ON DELETE CASCADE + , FOREIGN KEY(user_id) REFERENCES dbo.users(id) ON DELETE CASCADE + , UNIQUE(project_id, user_id) + ); + "); + $pdo->exec(" + CREATE TABLE dbo.actions ( + id int identity PRIMARY KEY + , project_id int NOT NULL + , event_name nvarchar(max) NOT NULL + , action_name nvarchar(max) NOT NULL + , FOREIGN KEY(project_id) REFERENCES dbo.projects(id) ON DELETE CASCADE + ); + "); + $pdo->exec(" + CREATE TABLE dbo.action_has_params ( + id int identity PRIMARY KEY + , action_id int NOT NULL + , name nvarchar(max) NOT NULL + , value nvarchar(max) NOT NULL + , FOREIGN KEY(action_id) REFERENCES dbo.actions(id) ON DELETE CASCADE + ); + "); + $pdo->exec(" + CREATE TABLE dbo.remember_me ( + id int identity PRIMARY KEY + , user_id int NOT NULL + , ip nvarchar(45) + , user_agent nvarchar(255) + , token nvarchar(255) + , sequence nvarchar(255) + , expiration int + , date_creation bigint + , FOREIGN KEY(user_id) REFERENCES dbo.users(id) ON DELETE CASCADE + ); + "); + $pdo->exec(" + CREATE TABLE dbo.last_logins ( + id int identity PRIMARY KEY + , auth_type nvarchar(25) + , user_id int NOT NULL + , ip nvarchar(45) + , user_agent nvarchar(255) + , date_creation bigint + , FOREIGN KEY(user_id) REFERENCES dbo.users(id) ON DELETE CASCADE + ); + "); + $pdo->exec(" + CREATE TABLE dbo.project_has_categories ( + id int identity PRIMARY KEY + , name nvarchar(255) NOT NULL + , project_id int NOT NULL + , description nvarchar(max) + , color_id nvarchar(50) + , UNIQUE (project_id, name) + , FOREIGN KEY(project_id) REFERENCES dbo.projects(id) ON DELETE CASCADE + ); + "); + $pdo->exec(" + CREATE TABLE dbo.swimlanes ( + id int identity PRIMARY KEY + , name nvarchar(848) NOT NULL /* max size for unique index */ + , position int DEFAULT 1 + , is_active bit DEFAULT 1 + , project_id int NOT NULL + , description nvarchar(max) + , task_limit int DEFAULT 0 + , FOREIGN KEY(project_id) REFERENCES dbo.projects(id) ON DELETE NO ACTION /* projects_cascade_delete_trigger */ + , UNIQUE (name, project_id) + ); + "); + $pdo->exec(" + CREATE TABLE dbo.tasks + ( + id int identity PRIMARY KEY + , title nvarchar(max) NOT NULL + , description nvarchar(max) + , date_creation bigint + , color_id nvarchar(255) + , project_id int NOT NULL + , column_id int NOT NULL + , owner_id int DEFAULT 0 + , position int + , is_active bit DEFAULT 1 + , date_completed bigint + , score int + , date_due bigint + , category_id int DEFAULT 0 + , creator_id int DEFAULT 0 + , date_modification int DEFAULT 0 + , reference nvarchar(max) DEFAULT '' + , date_started bigint + , time_spent float DEFAULT 0 + , time_estimated float DEFAULT 0 + , swimlane_id int NOT NULL + , date_moved bigint DEFAULT 0 + , recurrence_status int DEFAULT 0 NOT NULL + , recurrence_trigger int DEFAULT 0 NOT NULL + , recurrence_factor int DEFAULT 0 NOT NULL + , recurrence_timeframe int DEFAULT 0 NOT NULL + , recurrence_basedate int DEFAULT 0 NOT NULL + , recurrence_parent int + , recurrence_child int + , priority int DEFAULT 0 + , external_provider nvarchar(255) + , external_uri nvarchar(255) + , FOREIGN KEY (project_id) REFERENCES dbo.projects(id) ON DELETE NO ACTION /* projects_cascade_delete_trigger */ + , FOREIGN KEY (column_id) REFERENCES dbo.columns(id) ON DELETE NO ACTION /* columns_cascade_delete_trigger */ + , FOREIGN KEY (swimlane_id) REFERENCES dbo.swimlanes(id) ON DELETE NO ACTION /* swimlanes_cascade_delete_trigger */ + ); + "); + $pdo->exec(" + CREATE TABLE dbo.task_has_files ( + id int identity PRIMARY KEY + , name nvarchar(max) NOT NULL + , path nvarchar(max) + , is_image bit DEFAULT 0 + , task_id int NOT NULL + , date bigint NOT NULL DEFAULT 0 + , user_id int NOT NULL DEFAULT 0 + , size int NOT NULL DEFAULT 0 + , FOREIGN KEY(task_id) REFERENCES dbo.tasks(id) ON DELETE CASCADE + ); + "); + $pdo->exec(" + CREATE TABLE dbo.subtasks ( + id int identity PRIMARY KEY + , title nvarchar(max) NOT NULL + , status smallint DEFAULT 0 + , time_estimated float DEFAULT 0 + , time_spent float DEFAULT 0 + , task_id int NOT NULL + , user_id int + , position int DEFAULT 1 + , FOREIGN KEY(task_id) REFERENCES dbo.tasks(id) ON DELETE CASCADE + ); + "); + $pdo->exec(" + CREATE TABLE dbo.user_has_notifications ( + user_id int NOT NULL + , project_id int NOT NULL + , FOREIGN KEY(user_id) REFERENCES dbo.users(id) ON DELETE CASCADE + , FOREIGN KEY(project_id) REFERENCES dbo.projects(id) ON DELETE CASCADE + , UNIQUE(project_id, user_id) + ); + "); + $pdo->exec(" + CREATE TABLE dbo.settings ( + [option] nvarchar(100) PRIMARY KEY + , value nvarchar(max) DEFAULT '' + , changed_by int DEFAULT 0 NOT NULL + , changed_on int DEFAULT 0 NOT NULL + ); + "); + $pdo->exec(" + CREATE TABLE dbo.project_daily_column_stats ( + id int identity PRIMARY KEY + , day nchar(10) NOT NULL + , project_id int NOT NULL + , column_id int NOT NULL + , total int NOT NULL DEFAULT 0 + , score int NOT NULL DEFAULT 0 + , FOREIGN KEY(column_id) REFERENCES dbo.columns(id) ON DELETE CASCADE + , FOREIGN KEY(project_id) REFERENCES dbo.projects(id) ON DELETE NO ACTION /* projects_cascade_delete_trigger */ + ); + "); + $pdo->exec(" + CREATE TABLE dbo.subtask_time_tracking ( + id int identity PRIMARY KEY + , user_id int NOT NULL + , subtask_id int NOT NULL + , [start] bigint DEFAULT 0 + , [end] bigint DEFAULT 0 + , time_spent real DEFAULT 0 + , FOREIGN KEY(user_id) REFERENCES dbo.users(id) ON DELETE CASCADE + , FOREIGN KEY(subtask_id) REFERENCES dbo.subtasks(id) ON DELETE CASCADE + ); + "); + $pdo->exec(" + CREATE TABLE dbo.links ( + id int identity PRIMARY KEY + , label nvarchar(255) NOT NULL + , opposite_id int DEFAULT 0 + , UNIQUE(label) + ); + "); + $pdo->exec(" + CREATE TABLE dbo.task_has_links ( + id int identity PRIMARY KEY + , link_id int NOT NULL + , task_id int NOT NULL + , opposite_task_id int NOT NULL + , FOREIGN KEY(link_id) REFERENCES dbo.links(id) ON DELETE CASCADE + , FOREIGN KEY(task_id) REFERENCES dbo.tasks(id) ON DELETE CASCADE + , FOREIGN KEY(opposite_task_id) REFERENCES dbo.tasks(id) ON DELETE NO ACTION /* Handled in tasks_cascade_delete_trigger */ + ); + "); + $pdo->exec(" + CREATE TABLE dbo.transitions ( + id int identity PRIMARY KEY + , 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 bigint NOT NULL + , time_spent int DEFAULT 0 + , FOREIGN KEY(src_column_id) REFERENCES dbo.columns(id) ON DELETE NO ACTION /* columns_cascade_delete_trigger */ + , FOREIGN KEY(dst_column_id) REFERENCES dbo.columns(id) ON DELETE NO ACTION /* columns_cascade_delete_trigger */ + , FOREIGN KEY(user_id) REFERENCES dbo.users(id) ON DELETE CASCADE + , FOREIGN KEY(project_id) REFERENCES dbo.projects(id) ON DELETE NO ACTION /* projects_cascade_delete_trigger */ + , FOREIGN KEY(task_id) REFERENCES dbo.tasks(id) ON DELETE CASCADE + ); + "); + $pdo->exec(" + CREATE TABLE dbo.currencies ( + currency nvarchar(3) NOT NULL UNIQUE + , rate REAL DEFAULT 0 + ); + "); + $pdo->exec(" + CREATE TABLE dbo.comments ( + id int identity PRIMARY KEY + , task_id int NOT NULL + , user_id int DEFAULT 0 + , date_creation bigint NOT NULL + , comment nvarchar(max) NOT NULL + , reference nvarchar(max) DEFAULT N'' + , date_modification bigint + , FOREIGN KEY(task_id) REFERENCES dbo.tasks(id) ON DELETE CASCADE + ); + "); + $pdo->exec(" + CREATE TABLE dbo.project_daily_stats ( + id int identity PRIMARY KEY + , day nchar(10) NOT NULL + , project_id int NOT NULL + , avg_lead_time int NOT NULL DEFAULT 0 + , avg_cycle_time int NOT NULL DEFAULT 0 + , FOREIGN KEY(project_id) REFERENCES dbo.projects(id) ON DELETE CASCADE + ); + "); + $pdo->exec(" + CREATE TABLE dbo.plugin_schema_versions ( + plugin nvarchar(80) NOT NULL PRIMARY KEY + , version int NOT NULL DEFAULT 0 + ); + "); + $pdo->exec(" + CREATE TABLE dbo.custom_filters ( + id int identity PRIMARY KEY + , filter nvarchar(max) NOT NULL + , project_id int NOT NULL + , user_id int NOT NULL + , name nvarchar(max) NOT NULL + , is_shared bit DEFAULT 0 + , append bit DEFAULT 0 + ); + "); + $pdo->exec(" + CREATE TABLE dbo.user_has_unread_notifications ( + id int identity PRIMARY KEY + , user_id int NOT NULL + , date_creation bigint NOT NULL + , event_name nvarchar(max) NOT NULL + , event_data nvarchar(max) NOT NULL + , FOREIGN KEY(user_id) REFERENCES dbo.users(id) ON DELETE CASCADE + ); + "); + $pdo->exec(" + CREATE TABLE dbo.user_has_notification_types ( + id int identity PRIMARY KEY + , user_id int NOT NULL + , notification_type nvarchar(50) + , FOREIGN KEY(user_id) REFERENCES dbo.users(id) ON DELETE CASCADE + ); + "); + $pdo->exec(" + CREATE TABLE dbo.project_has_notification_types ( + id int identity PRIMARY KEY + , project_id int NOT NULL + , notification_type nvarchar(50) NOT NULL + , FOREIGN KEY(project_id) REFERENCES dbo.projects(id) ON DELETE CASCADE + , UNIQUE(project_id, notification_type) + ); + "); + $pdo->exec(" + CREATE TABLE dbo.user_has_metadata ( + user_id int NOT NULL + , name nvarchar(50) NOT NULL + , value nvarchar(255) DEFAULT '' + , changed_by int DEFAULT 0 NOT NULL + , changed_on int DEFAULT 0 NOT NULL /* TODO: should be bigint?? */ + , FOREIGN KEY(user_id) REFERENCES dbo.users(id) ON DELETE CASCADE + , UNIQUE(user_id, name) + ); + "); + $pdo->exec(" + CREATE TABLE dbo.project_has_metadata ( + project_id int NOT NULL + , name nvarchar(50) NOT NULL + , value nvarchar(255) DEFAULT '' + , changed_by int DEFAULT 0 NOT NULL + , changed_on int DEFAULT 0 NOT NULL /* TODO: should be bigint?? */ + , FOREIGN KEY(project_id) REFERENCES dbo.projects(id) ON DELETE CASCADE + , UNIQUE(project_id, name) + ); + "); + $pdo->exec(" + CREATE TABLE dbo.task_has_metadata ( + task_id int NOT NULL + , name nvarchar(50) NOT NULL + , value nvarchar(255) DEFAULT '' + , changed_by int DEFAULT 0 NOT NULL + , changed_on int DEFAULT 0 NOT NULL /* TODO: should be bigint?? */ + , FOREIGN KEY(task_id) REFERENCES dbo.tasks(id) ON DELETE CASCADE + , UNIQUE(task_id, name) + ); + "); + $pdo->exec(" + CREATE TABLE dbo.groups ( + id int identity PRIMARY KEY + , external_id nvarchar(255) DEFAULT '' + , name nvarchar(850) NOT NULL UNIQUE + ); + "); + $pdo->exec(" + CREATE TABLE dbo.group_has_users ( + group_id int NOT NULL + , user_id int NOT NULL + , FOREIGN KEY(group_id) REFERENCES dbo.groups(id) ON DELETE CASCADE + , FOREIGN KEY(user_id) REFERENCES dbo.users(id) ON DELETE CASCADE + , UNIQUE(group_id, user_id) + ); + "); + $pdo->exec(" + CREATE TABLE dbo.project_has_groups ( + group_id int NOT NULL + , project_id int NOT NULL + , role nvarchar(255) NOT NULL + , FOREIGN KEY(group_id) REFERENCES dbo.groups(id) ON DELETE CASCADE + , FOREIGN KEY(project_id) REFERENCES dbo.projects(id) ON DELETE CASCADE + , UNIQUE(group_id, project_id) + ); + "); + $pdo->exec(" + CREATE TABLE dbo.password_reset ( + token nvarchar(80) PRIMARY KEY + , user_id int NOT NULL + , date_expiration int NOT NULL /* TODO: bigint?? */ + , date_creation int NOT NULL /* TODO: bigint?? */ + , ip nvarchar(45) NOT NULL + , user_agent nvarchar(255) NOT NULL + , is_active bit NOT NULL + , FOREIGN KEY(user_id) REFERENCES dbo.users(id) ON DELETE CASCADE + ); + "); + $pdo->exec(" + CREATE TABLE dbo.task_has_external_links ( + id int identity PRIMARY KEY + , link_type nvarchar(100) NOT NULL + , dependency nvarchar(100) NOT NULL + , title nvarchar(max) NOT NULL + , url nvarchar(max) NOT NULL + , date_creation int NOT NULL /* TODO: bigint?? */ + , date_modification int NOT NULL /* TODO: bigint?? */ + , task_id int NOT NULL + , creator_id int DEFAULT 0 + , FOREIGN KEY(task_id) REFERENCES dbo.tasks(id) ON DELETE CASCADE + ); + "); + $pdo->exec(" + CREATE TABLE dbo.project_has_files ( + id int identity PRIMARY KEY + , project_id int NOT NULL + , name nvarchar(max) NOT NULL + , path nvarchar(max) NOT NULL + , is_image bit DEFAULT 0 + , size int DEFAULT 0 NOT NULL + , user_id int DEFAULT 0 NOT NULL + , date int DEFAULT 0 NOT NULL /* TODO: bigint?? */ + , FOREIGN KEY(project_id) REFERENCES dbo.projects(id) ON DELETE CASCADE + ); + "); + $pdo->exec(" + CREATE TABLE dbo.tags ( + id int identity PRIMARY KEY + , name nvarchar(255) NOT NULL + , project_id int NOT NULL + , color_id nvarchar(50) DEFAULT NULL + , UNIQUE(project_id, name) + ); + "); + $pdo->exec(" + CREATE TABLE dbo.task_has_tags ( + task_id int NOT NULL + , tag_id int NOT NULL + , FOREIGN KEY(task_id) REFERENCES dbo.tasks(id) ON DELETE CASCADE + , FOREIGN KEY(tag_id) REFERENCES dbo.tags(id) ON DELETE CASCADE + , UNIQUE(tag_id, task_id) + ); + "); + $pdo->exec(" + CREATE TABLE dbo.project_has_roles ( + role_id int identity PRIMARY KEY + , role nvarchar(255) NOT NULL + , project_id int NOT NULL + , UNIQUE(project_id, role) + , FOREIGN KEY(project_id) REFERENCES dbo.projects(id) ON DELETE CASCADE + ); + "); + $pdo->exec(" + CREATE TABLE dbo.column_has_move_restrictions ( + restriction_id int identity PRIMARY KEY + , project_id int NOT NULL + , role_id int NOT NULL + , src_column_id int NOT NULL + , dst_column_id int NOT NULL + , only_assigned bit DEFAULT 0 + , UNIQUE(role_id, src_column_id, dst_column_id) + , FOREIGN KEY(project_id) REFERENCES dbo.projects(id) ON DELETE NO ACTION /* projects_cascade_delete_trigger */ + , FOREIGN KEY(role_id) REFERENCES dbo.project_has_roles(role_id) ON DELETE CASCADE + , FOREIGN KEY(src_column_id) REFERENCES dbo.columns(id) ON DELETE NO ACTION /* columns_cascade_delete_trigger */ + , FOREIGN KEY(dst_column_id) REFERENCES dbo.columns(id) ON DELETE NO ACTION /* columns_cascade_delete_trigger */ + ); + "); + $pdo->exec(" + CREATE TABLE dbo.project_role_has_restrictions ( + restriction_id int identity PRIMARY KEY + , project_id int NOT NULL + , role_id int NOT NULL + , [rule] nvarchar(255) NOT NULL + , UNIQUE(role_id, [rule]) + , FOREIGN KEY(project_id) REFERENCES dbo.projects(id) ON DELETE NO ACTION /* projects_cascade_delete_trigger */ + , FOREIGN KEY(role_id) REFERENCES dbo.project_has_roles(role_id) ON DELETE CASCADE + ); + "); + $pdo->exec(" + CREATE TABLE dbo.column_has_restrictions ( + restriction_id int identity PRIMARY KEY + , project_id int NOT NULL + , role_id int NOT NULL + , column_id int NOT NULL + , [rule] nvarchar(255) NOT NULL + , UNIQUE(role_id, column_id, [rule]) + , FOREIGN KEY(project_id) REFERENCES dbo.projects(id) ON DELETE NO ACTION /* projects_cascade_delete_trigger */ + , FOREIGN KEY(role_id) REFERENCES dbo.project_has_roles(role_id) ON DELETE CASCADE + , FOREIGN KEY(column_id) REFERENCES dbo.columns(id) ON DELETE CASCADE + ); + "); + $pdo->exec(" + CREATE TABLE dbo.invites ( + email nvarchar(255) NOT NULL + , project_id int NOT NULL + , token nvarchar(255) NOT NULL + , PRIMARY KEY(email, token) + ); + "); + $pdo->exec(" + CREATE TABLE dbo.project_activities ( + id int identity PRIMARY KEY + , date_creation bigint NOT NULL + , event_name nvarchar(max) NOT NULL + , creator_id int NOT NULL + , project_id int NOT NULL + , task_id int NOT NULL + , data nvarchar(max) + , FOREIGN KEY(creator_id) REFERENCES dbo.users(id) ON DELETE CASCADE + , FOREIGN KEY(project_id) REFERENCES dbo.projects(id) ON DELETE NO ACTION /* projects_cascade_delete_trigger */ + , FOREIGN KEY(task_id) REFERENCES dbo.tasks(id) ON DELETE CASCADE + ); + "); + $pdo->exec(" + CREATE TABLE dbo.predefined_task_descriptions ( + id int identity PRIMARY KEY + , project_id int NOT NULL + , title nvarchar(max) NOT NULL + , description nvarchar(max) NOT NULL + , FOREIGN KEY(project_id) REFERENCES dbo.projects(id) ON DELETE CASCADE + ); + "); + $pdo->exec(" + CREATE TABLE dbo.sessions ( + id nvarchar(450) PRIMARY KEY /* max length for primary key */ + , expire_at int NOT NULL + , data nvarchar(max) DEFAULT '' + ); + "); + + // create triggers -- each of which must be in its own batch + $pdo->exec(" + CREATE TRIGGER dbo.columns_cascade_delete_trigger + ON dbo.columns INSTEAD OF DELETE + AS + SET NOCOUNT ON; + DELETE dbo.column_has_move_restrictions + WHERE src_column_id IN (SELECT id FROM deleted) + OR dst_column_id IN (SELECT id FROM deleted); + DELETE dbo.transitions + WHERE src_column_id IN (SELECT id FROM deleted) + OR dst_column_id IN (SELECT id FROM deleted); + DELETE dbo.tasks + WHERE column_id IN (SELECT id FROM deleted); + DELETE dbo.columns + WHERE id IN (SELECT id FROM deleted); + "); + + $pdo->exec(" + CREATE TRIGGER projects_cascade_delete_trigger + ON dbo.projects INSTEAD OF DELETE + AS + SET NOCOUNT ON; + DELETE dbo.column_has_move_restrictions + WHERE project_id IN (SELECT id FROM deleted); + DELETE dbo.column_has_restrictions + WHERE project_id IN (SELECT id FROM deleted); + DELETE dbo.columns + WHERE project_id IN (SELECT id FROM deleted); + DELETE dbo.project_activities + WHERE project_id IN (SELECT id FROM deleted); + DELETE dbo.project_daily_column_stats + WHERE project_id IN (SELECT id FROM deleted); + DELETE dbo.project_role_has_restrictions + WHERE project_id IN (SELECT id FROM deleted); + DELETE dbo.swimlanes + WHERE project_id IN (SELECT id FROM deleted); + DELETE dbo.tasks + WHERE project_id IN (SELECT id FROM deleted); + DELETE dbo.transitions + WHERE project_id IN (SELECT id FROM deleted); + DELETE dbo.projects + WHERE id IN (SELECT id FROM deleted); + "); + + $pdo->exec(" + CREATE TRIGGER dbo.swimlanes_cascade_delete_trigger + ON dbo.swimlanes INSTEAD OF DELETE + AS + SET NOCOUNT ON; + DELETE dbo.tasks + WHERE swimlane_id IN (SELECT id FROM deleted); + DELETE dbo.swimlanes + WHERE id IN (SELECT id FROM deleted); + "); + + $pdo->exec(" + CREATE TRIGGER dbo.tasks_cascade_delete_trigger + ON dbo.tasks INSTEAD OF DELETE + AS + SET NOCOUNT ON; + DELETE dbo.task_has_links + WHERE opposite_task_id IN (SELECT id FROM deleted); + DELETE dbo.tasks + WHERE id IN (SELECT id FROM deleted); + "); + + // set defaults + $pdo->exec(" + ALTER TABLE dbo.project_has_users + ADD DEFAULT N'" .Role::PROJECT_VIEWER. "' FOR role; + "); + $pdo->exec(" + ALTER TABLE dbo.users + ADD DEFAULT N'" .Role::APP_USER. "' FOR role; + "); + + // insert starting data + $aui = $pdo->prepare("INSERT INTO dbo.users (username, password, role) VALUES (?, ?, ?);"); + $aui->execute(array('admin', \password_hash('admin', PASSWORD_BCRYPT), Role::APP_ADMIN)); + + $rq = $pdo->prepare('INSERT INTO dbo.settings ([option],value) VALUES (?, ?);'); + $rq->execute(array('api_token', Token::getToken())); + $rq->execute(array('application_url', defined('KANBOARD_URL') ? KANBOARD_URL : '')); + $rq->execute(array('board_highlight_period', defined('RECENT_TASK_PERIOD') ? RECENT_TASK_PERIOD : 48*60*60)); + $rq->execute(array('board_private_refresh_interval', defined('BOARD_CHECK_INTERVAL') ? BOARD_CHECK_INTERVAL : 10)); + $rq->execute(array('board_public_refresh_interval', defined('BOARD_PUBLIC_CHECK_INTERVAL') ? BOARD_PUBLIC_CHECK_INTERVAL : 60)); + $rq->execute(array('webhook_token', Token::getToken())); + + $pdo->exec(" + INSERT INTO dbo.settings ([option], value) VALUES + ('application_currency','USD'), + ('application_date_format','m/d/Y'), + ('application_language','en_US'), + ('application_stylesheet',''), + ('application_time_format','H:i'), + ('application_timezone','UTC'), + ('board_columns',''), + ('calendar_project_tasks','date_started'), + ('calendar_user_subtasks_time_tracking','0'), + ('calendar_user_tasks','date_started'), + ('cfd_include_closed_tasks','1'), + ('default_color','yellow'), + ('integration_gravatar','0'), + ('password_reset','1'), + ('project_categories',''), + ('subtask_restriction','0'), + ('subtask_time_tracking','1'), + ('webhook_url','') + ; + "); + + $pdo->exec(" + SET IDENTITY_INSERT dbo.links ON; + INSERT INTO dbo.links (id, label, opposite_id) VALUES + (1,'relates to',0), + (2,'blocks',3), + (3,'is blocked by',2), + (4,'duplicates',5), + (5,'is duplicated by',4), + (6,'is a child of',7), + (7,'is a parent of',6), + (8,'targets milestone',9), + (9,'is a milestone of',8), + (10,'fixes',11), + (11,'is fixed by',10) + ; + SET IDENTITY_INSERT dbo.links OFF; + "); + + // create indexes + $pdo->exec(" + CREATE UNIQUE INDEX users_username_idx ON dbo.users(username); + CREATE UNIQUE INDEX project_daily_column_stats_idx ON dbo.project_daily_column_stats(day, project_id, column_id); + CREATE UNIQUE INDEX task_has_links_unique ON dbo.task_has_links(link_id, task_id, opposite_task_id); + CREATE UNIQUE INDEX project_daily_stats_idx ON dbo.project_daily_stats(day, project_id); + CREATE UNIQUE INDEX user_has_notification_types_user_idx ON dbo.user_has_notification_types(user_id, notification_type); + + CREATE INDEX columns_project_idx ON dbo.columns(project_id); + CREATE INDEX swimlanes_project_idx ON dbo.swimlanes(project_id); + CREATE INDEX categories_project_idx ON dbo.project_has_categories(project_id); + CREATE INDEX subtasks_task_idx ON dbo.subtasks(task_id); + CREATE INDEX files_task_idx ON dbo.task_has_files(task_id); + CREATE INDEX task_has_links_task_index ON dbo.task_has_links(task_id); + CREATE INDEX transitions_task_index ON dbo.transitions(task_id); + CREATE INDEX transitions_project_index ON dbo.transitions(project_id); + CREATE INDEX transitions_user_index ON dbo.transitions(user_id); + "); +} diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php new file mode 100644 index 0000000..0b969fa --- /dev/null +++ b/app/Schema/Mysql.php @@ -0,0 +1,1700 @@ +<?php + +namespace Schema; + +require_once __DIR__.'/Migration.php'; + +use PDO; +use Kanboard\Core\Security\Token; +use Kanboard\Core\Security\Role; + +const VERSION = 139; + +function version_139(PDO $pdo) +{ + $pdo->exec("ALTER TABLE `comments` ADD COLUMN `visibility` VARCHAR(25) NOT NULL DEFAULT '".Role::APP_USER."'"); +} + +function version_138(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN theme VARCHAR(50) DEFAULT 'light' NOT NULL"); +} + +function version_137(PDO $pdo) +{ + $pdo->exec('ALTER TABLE `projects` ADD COLUMN `enable_global_tags` TINYINT(1) DEFAULT 1 NOT NULL'); +} + +function version_136(PDO $pdo) +{ + $pdo->exec('ALTER TABLE `swimlanes` ADD COLUMN `task_limit` INT DEFAULT 0'); +} + +function version_135(PDO $pdo) +{ + $pdo->exec('ALTER TABLE `projects` ADD COLUMN `task_limit` INT DEFAULT 0'); +} + +function version_134(PDO $pdo) +{ + $pdo->exec('ALTER TABLE `projects` ADD COLUMN `per_swimlane_task_limits` INT DEFAULT 0 NOT NULL'); +} + +function version_133(PDO $pdo) +{ + $pdo->exec('ALTER TABLE `tags` ADD COLUMN `color_id` VARCHAR(50) DEFAULT NULL'); +} + +function version_132(PDO $pdo) +{ + $pdo->exec('ALTER TABLE `project_has_categories` ADD COLUMN `color_id` VARCHAR(50) DEFAULT NULL'); +} + +function version_131(PDO $pdo) +{ + $pdo->exec("ALTER TABLE `users` MODIFY `language` VARCHAR(11) DEFAULT NULL"); +} + +/* + +This migration convert table encoding to utf8mb4. +You should also convert the database encoding: + +ALTER DATABASE kanboard CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; + +You might need to run: + +REPAIR TABLE table_name; +OPTIMIZE TABLE table_name; + +The max length for Mysql 5.6 is 191 for varchar unique keys in utf8mb4 + +*/ +function version_130(PDO $pdo) +{ + $pdo->exec("ALTER TABLE `swimlanes` MODIFY `name` VARCHAR(191) NOT NULL"); + $pdo->exec("ALTER TABLE `users` MODIFY `username` VARCHAR(191) NOT NULL"); + $pdo->exec("ALTER TABLE `groups` MODIFY `name` VARCHAR(191) NOT NULL"); + $pdo->exec("ALTER TABLE `links` MODIFY `label` VARCHAR(191) NOT NULL"); + $pdo->exec("ALTER TABLE `tags` MODIFY `name` VARCHAR(191) NOT NULL"); + $pdo->exec("ALTER TABLE `sessions` MODIFY `id` VARCHAR(191) NOT NULL"); + $pdo->exec("ALTER TABLE `project_role_has_restrictions` MODIFY `rule` VARCHAR(191) NOT NULL"); + $pdo->exec("ALTER TABLE `project_has_roles` MODIFY `role` VARCHAR(191) NOT NULL"); + $pdo->exec("ALTER TABLE `project_has_categories` MODIFY `name` VARCHAR(191) NOT NULL"); + $pdo->exec("ALTER TABLE `invites` MODIFY `email` VARCHAR(191) NOT NULL"); + $pdo->exec("ALTER TABLE `invites` MODIFY `token` VARCHAR(191) NOT NULL"); + $pdo->exec("ALTER TABLE `groups` MODIFY `name` VARCHAR(191) NOT NULL"); + $pdo->exec("ALTER TABLE `columns` MODIFY `title` VARCHAR(191) NOT NULL"); + $pdo->exec("ALTER TABLE `column_has_restrictions` MODIFY `rule` VARCHAR(191) NOT NULL"); + $pdo->exec("ALTER TABLE `comments` MODIFY `reference` VARCHAR(191) DEFAULT ''"); + $pdo->exec("ALTER TABLE `tasks` MODIFY `reference` VARCHAR(191) DEFAULT ''"); + + $tables = [ + 'action_has_params', + 'actions', + 'column_has_move_restrictions', + 'column_has_restrictions', + 'columns', + 'comments', + 'currencies', + 'custom_filters', + 'group_has_users', + 'groups', + 'invites', + 'last_logins', + 'links', + 'password_reset', + 'plugin_schema_versions', + 'predefined_task_descriptions', + 'project_activities', + 'project_daily_column_stats', + 'project_daily_stats', + 'project_has_categories', + 'project_has_files', + 'project_has_groups', + 'project_has_metadata', + 'project_has_notification_types', + 'project_has_roles', + 'project_has_users', + 'project_role_has_restrictions', + 'projects', + 'remember_me', + 'sessions', + 'settings', + 'subtask_time_tracking', + 'subtasks', + 'swimlanes', + 'tags', + 'task_has_external_links', + 'task_has_files', + 'task_has_links', + 'task_has_metadata', + 'task_has_tags', + 'tasks', + 'transitions', + 'user_has_metadata', + 'user_has_notification_types', + 'user_has_notifications', + 'user_has_unread_notifications', + 'users', + ]; + + foreach ($tables as $table) { + $pdo->exec('ALTER TABLE `'.$table.'` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci'); + } +} + +function version_129(PDO $pdo) +{ + $pdo->exec('ALTER TABLE `projects` MODIFY `name` TEXT NOT NULL'); + $pdo->exec('ALTER TABLE `projects` MODIFY `email` TEXT'); + $pdo->exec('ALTER TABLE `action_has_params` MODIFY `name` TEXT NOT NULL'); + $pdo->exec('ALTER TABLE `action_has_params` MODIFY `value` TEXT NOT NULL'); + $pdo->exec('ALTER TABLE `actions` MODIFY `event_name` TEXT NOT NULL'); + $pdo->exec('ALTER TABLE `actions` MODIFY `action_name` TEXT NOT NULL'); + $pdo->exec("ALTER TABLE `comments` MODIFY `reference` VARCHAR(255) DEFAULT ''"); + $pdo->exec("ALTER TABLE `custom_filters` MODIFY `filter` TEXT NOT NULL"); + $pdo->exec("ALTER TABLE `custom_filters` MODIFY `name` TEXT NOT NULL"); + $pdo->exec("ALTER TABLE `groups` MODIFY `name` VARCHAR(255) NOT NULL"); + $pdo->exec("ALTER TABLE `project_activities` MODIFY `event_name` TEXT NOT NULL"); + $pdo->exec("ALTER TABLE `project_has_files` MODIFY `name` TEXT NOT NULL"); + $pdo->exec("ALTER TABLE `project_has_files` MODIFY `path` TEXT NOT NULL"); + $pdo->exec("ALTER TABLE `subtasks` MODIFY `title` TEXT NOT NULL"); + $pdo->exec("ALTER TABLE `swimlanes` MODIFY `name` VARCHAR(255) NOT NULL"); + $pdo->exec("ALTER TABLE `task_has_external_links` MODIFY `title` TEXT NOT NULL"); + $pdo->exec("ALTER TABLE `task_has_external_links` MODIFY `url` TEXT NOT NULL"); + $pdo->exec("ALTER TABLE `task_has_files` MODIFY `name` TEXT NOT NULL"); + $pdo->exec("ALTER TABLE `task_has_files` MODIFY `path` TEXT NOT NULL"); + $pdo->exec("ALTER TABLE `tasks` MODIFY `title` TEXT NOT NULL"); + $pdo->exec("ALTER TABLE `tasks` MODIFY `reference` VARCHAR(255) DEFAULT ''"); + $pdo->exec("ALTER TABLE `user_has_unread_notifications` MODIFY `event_name` TEXT NOT NULL"); + $pdo->exec("ALTER TABLE `users` MODIFY `username` VARCHAR(255) NOT NULL"); + $pdo->exec("ALTER TABLE `users` MODIFY `filter` TEXT"); +} + +function version_128(PDO $pdo) +{ + $pdo->exec('ALTER TABLE `users` ADD COLUMN `filter` VARCHAR(255) DEFAULT NULL'); +} + +function version_127(PDO $pdo) +{ + $pdo->exec("CREATE TABLE sessions ( + id VARCHAR(255) NOT NULL, + expire_at INT NOT NULL, + data LONGTEXT, + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8"); +} + +function version_126(PDO $pdo) +{ + $pdo->exec('CREATE TABLE predefined_task_descriptions ( + id INT NOT NULL AUTO_INCREMENT, + project_id INT NOT NULL, + title TEXT NOT NULL, + description TEXT NOT NULL, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8'); +} + +function version_125(PDO $pdo) +{ + $pdo->exec('ALTER TABLE projects DROP COLUMN is_everybody_allowed'); +} + +function version_124(PDO $pdo) +{ + $pdo->exec('ALTER TABLE projects ADD COLUMN predefined_email_subjects TEXT'); +} + +function version_123(PDO $pdo) +{ + $pdo->exec('ALTER TABLE column_has_move_restrictions ADD COLUMN only_assigned TINYINT(1) DEFAULT 0'); +} + +function version_122(PDO $pdo) +{ + migrate_default_swimlane($pdo); + + $pdo->exec('ALTER TABLE `projects` DROP COLUMN `default_swimlane`'); + $pdo->exec('ALTER TABLE `projects` DROP COLUMN `show_default_swimlane`'); + $pdo->exec('ALTER TABLE `tasks` MODIFY `swimlane_id` INT(11) NOT NULL;'); + $pdo->exec('ALTER TABLE tasks ADD CONSTRAINT tasks_swimlane_ibfk_1 FOREIGN KEY (swimlane_id) REFERENCES swimlanes(id) ON DELETE CASCADE'); +} + +function version_121(PDO $pdo) +{ + $pdo->exec('ALTER TABLE projects ADD COLUMN email VARCHAR(255)'); +} + +function version_120(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE invites ( + email VARCHAR(255) NOT NULL, + project_id INTEGER NOT NULL, + token VARCHAR(255) NOT NULL, + PRIMARY KEY(email, token) + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec("DELETE FROM settings WHERE `option`='application_datetime_format'"); +} + +function version_119(PDO $pdo) +{ + $pdo->exec('ALTER TABLE `comments` ADD COLUMN `date_modification` BIGINT(20)'); + $pdo->exec('UPDATE `comments` SET `date_modification` = `date_creation` WHERE `date_modification` IS NULL'); +} + +function version_118(PDO $pdo) +{ + $pdo->exec('ALTER TABLE `users` ADD COLUMN `api_access_token` VARCHAR(255) DEFAULT NULL'); +} + +function version_117(PDO $pdo) +{ + $pdo->exec("ALTER TABLE `settings` MODIFY `value` TEXT"); +} + +function version_116(PDO $pdo) +{ + $pdo->exec("ALTER TABLE tasks ADD COLUMN external_provider VARCHAR(255)"); + $pdo->exec("ALTER TABLE tasks ADD COLUMN external_uri VARCHAR(255)"); +} + +function version_115(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE column_has_restrictions ( + restriction_id INT NOT NULL AUTO_INCREMENT, + project_id INT NOT NULL, + role_id INT NOT NULL, + column_id INT NOT NULL, + rule VARCHAR(255) NOT NULL, + UNIQUE(role_id, column_id, rule), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(role_id) REFERENCES project_has_roles(role_id) ON DELETE CASCADE, + FOREIGN KEY(column_id) REFERENCES columns(id) ON DELETE CASCADE, + PRIMARY KEY(restriction_id) + ) ENGINE=InnoDB CHARSET=utf8 + "); +} + +function version_114(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_role_has_restrictions ( + restriction_id INT NOT NULL AUTO_INCREMENT, + project_id INT NOT NULL, + role_id INT NOT NULL, + rule VARCHAR(255) NOT NULL, + UNIQUE(role_id, rule), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(role_id) REFERENCES project_has_roles(role_id) ON DELETE CASCADE, + PRIMARY KEY(restriction_id) + ) ENGINE=InnoDB CHARSET=utf8 + "); +} + +function version_113(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_has_roles ( + role_id INT NOT NULL AUTO_INCREMENT, + `role` VARCHAR(255) NOT NULL, + project_id INT NOT NULL, + UNIQUE(project_id, `role`), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + PRIMARY KEY(role_id) + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE column_has_move_restrictions ( + restriction_id INT NOT NULL AUTO_INCREMENT, + project_id INT NOT NULL, + role_id INT NOT NULL, + src_column_id INT NOT NULL, + dst_column_id INT NOT NULL, + UNIQUE(role_id, src_column_id, dst_column_id), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(role_id) REFERENCES project_has_roles(role_id) ON DELETE CASCADE, + FOREIGN KEY(src_column_id) REFERENCES columns(id) ON DELETE CASCADE, + FOREIGN KEY(dst_column_id) REFERENCES columns(id) ON DELETE CASCADE, + PRIMARY KEY(restriction_id) + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec("ALTER TABLE `project_has_users` MODIFY `role` VARCHAR(255) NOT NULL"); + $pdo->exec("ALTER TABLE `project_has_groups` MODIFY `role` VARCHAR(255) NOT NULL"); +} + +function version_112(PDO $pdo) +{ + $pdo->exec('ALTER TABLE columns ADD COLUMN hide_in_dashboard INT DEFAULT 0 NOT NULL'); +} + +function version_111(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE tags ( + id INT NOT NULL AUTO_INCREMENT, + name VARCHAR(255) NOT NULL, + project_id INT NOT NULL, + UNIQUE(project_id, name), + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE task_has_tags ( + task_id INT NOT NULL, + tag_id INT NOT NULL, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE, + FOREIGN KEY(tag_id) REFERENCES tags(id) ON DELETE CASCADE, + UNIQUE(tag_id, task_id) + ) ENGINE=InnoDB CHARSET=utf8 + "); +} + +function version_110(PDO $pdo) +{ + $pdo->exec("ALTER TABLE user_has_notifications DROP FOREIGN KEY `user_has_notifications_ibfk_1`"); + $pdo->exec("ALTER TABLE user_has_notifications DROP FOREIGN KEY `user_has_notifications_ibfk_2`"); + $pdo->exec("DROP INDEX `project_id` ON user_has_notifications"); + $pdo->exec("ALTER TABLE user_has_notifications DROP KEY `user_id`"); + $pdo->exec("CREATE UNIQUE INDEX `user_has_notifications_unique_idx` ON `user_has_notifications` (`user_id`, `project_id`)"); + $pdo->exec("ALTER TABLE user_has_notifications ADD CONSTRAINT user_has_notifications_ibfk_1 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE"); + $pdo->exec("ALTER TABLE user_has_notifications ADD CONSTRAINT user_has_notifications_ibfk_2 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE"); +} + +function version_109(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN avatar_path VARCHAR(255)"); +} + +function version_108(PDO $pdo) +{ + $pdo->exec("ALTER TABLE user_has_metadata ADD COLUMN changed_by INT DEFAULT 0 NOT NULL"); + $pdo->exec("ALTER TABLE user_has_metadata ADD COLUMN changed_on INT DEFAULT 0 NOT NULL"); + + $pdo->exec("ALTER TABLE project_has_metadata ADD COLUMN changed_by INT DEFAULT 0 NOT NULL"); + $pdo->exec("ALTER TABLE project_has_metadata ADD COLUMN changed_on INT DEFAULT 0 NOT NULL"); + + $pdo->exec("ALTER TABLE task_has_metadata ADD COLUMN changed_by INT DEFAULT 0 NOT NULL"); + $pdo->exec("ALTER TABLE task_has_metadata ADD COLUMN changed_on INT DEFAULT 0 NOT NULL"); + + $pdo->exec("ALTER TABLE settings ADD COLUMN changed_by INT DEFAULT 0 NOT NULL"); + $pdo->exec("ALTER TABLE settings ADD COLUMN changed_on INT DEFAULT 0 NOT NULL"); +} + +function version_107(PDO $pdo) +{ + $pdo->exec("UPDATE project_activities SET event_name='task.file.create' WHERE event_name='file.create'"); +} + +function version_106(PDO $pdo) +{ + $pdo->exec('RENAME TABLE files TO task_has_files'); + + $pdo->exec( + " + CREATE TABLE project_has_files ( + `id` INT NOT NULL AUTO_INCREMENT, + `project_id` INT NOT NULL, + `name` VARCHAR(255) NOT NULL, + `path` VARCHAR(255) NOT NULL, + `is_image` TINYINT(1) DEFAULT 0, + `size` INT DEFAULT 0 NOT NULL, + `user_id` INT DEFAULT 0 NOT NULL, + `date` INT DEFAULT 0 NOT NULL, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8" + ); +} + +function version_105(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN is_active TINYINT(1) DEFAULT 1"); +} + +function version_104(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE task_has_external_links ( + id INT NOT NULL AUTO_INCREMENT, + link_type VARCHAR(100) NOT NULL, + dependency VARCHAR(100) NOT NULL, + title VARCHAR(255) NOT NULL, + url VARCHAR(255) NOT NULL, + date_creation INT NOT NULL, + date_modification INT NOT NULL, + task_id INT NOT NULL, + creator_id INT DEFAULT 0, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE, + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8 + "); +} + +function version_103(PDO $pdo) +{ + $pdo->exec("ALTER TABLE projects ADD COLUMN priority_default INT DEFAULT 0"); + $pdo->exec("ALTER TABLE projects ADD COLUMN priority_start INT DEFAULT 0"); + $pdo->exec("ALTER TABLE projects ADD COLUMN priority_end INT DEFAULT 3"); + $pdo->exec("ALTER TABLE tasks ADD COLUMN priority INT DEFAULT 0"); +} + +function version_102(PDO $pdo) +{ + $pdo->exec("ALTER TABLE projects ADD COLUMN owner_id INT DEFAULT 0"); +} + +function version_101(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE password_reset ( + token VARCHAR(80) PRIMARY KEY, + user_id INT NOT NULL, + date_expiration INT NOT NULL, + date_creation INT NOT NULL, + ip VARCHAR(45) NOT NULL, + user_agent VARCHAR(255) NOT NULL, + is_active TINYINT(1) NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec("INSERT INTO settings VALUES ('password_reset', '1')"); +} + +function version_100(PDO $pdo) +{ + $pdo->exec('ALTER TABLE `actions` MODIFY `action_name` VARCHAR(255)'); +} + +function version_99(PDO $pdo) +{ + $rq = $pdo->prepare('SELECT * FROM actions'); + $rq->execute(); + $rows = $rq->fetchAll(PDO::FETCH_ASSOC) ?: array(); + + $rq = $pdo->prepare('UPDATE actions SET action_name=? WHERE id=?'); + + foreach ($rows as $row) { + if ($row['action_name'] === 'TaskAssignCurrentUser' && $row['event_name'] === 'task.move.column') { + $row['action_name'] = '\Kanboard\Action\TaskAssignCurrentUserColumn'; + } elseif ($row['action_name'] === 'TaskClose' && $row['event_name'] === 'task.move.column') { + $row['action_name'] = '\Kanboard\Action\TaskCloseColumn'; + } elseif ($row['action_name'] === 'TaskLogMoveAnotherColumn') { + $row['action_name'] = '\Kanboard\Action\CommentCreationMoveTaskColumn'; + } elseif ($row['action_name'][0] !== '\\') { + $row['action_name'] = '\Kanboard\Action\\'.$row['action_name']; + } + + $rq->execute(array($row['action_name'], $row['id'])); + } +} + +function version_98(PDO $pdo) +{ + $pdo->exec('ALTER TABLE `users` MODIFY `language` VARCHAR(5)'); +} + +function version_97(PDO $pdo) +{ + $pdo->exec("ALTER TABLE `users` ADD COLUMN `role` VARCHAR(25) NOT NULL DEFAULT '".Role::APP_USER."'"); + + $rq = $pdo->prepare('SELECT * FROM `users`'); + $rq->execute(); + $rows = $rq->fetchAll(PDO::FETCH_ASSOC) ?: array(); + + $rq = $pdo->prepare('UPDATE `users` SET `role`=? WHERE `id`=?'); + + foreach ($rows as $row) { + $role = Role::APP_USER; + + if ($row['is_admin'] == 1) { + $role = Role::APP_ADMIN; + } elseif ($row['is_project_admin']) { + $role = Role::APP_MANAGER; + } + + $rq->execute(array($role, $row['id'])); + } + + $pdo->exec('ALTER TABLE `users` DROP COLUMN `is_admin`'); + $pdo->exec('ALTER TABLE `users` DROP COLUMN `is_project_admin`'); +} + +function version_96(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_has_groups ( + `group_id` INT NOT NULL, + `project_id` INT NOT NULL, + `role` VARCHAR(25) NOT NULL, + FOREIGN KEY(group_id) REFERENCES `groups`(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + UNIQUE(group_id, project_id) + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec("ALTER TABLE `project_has_users` ADD COLUMN `role` VARCHAR(25) NOT NULL DEFAULT '".Role::PROJECT_VIEWER."'"); + + $rq = $pdo->prepare('SELECT * FROM project_has_users'); + $rq->execute(); + $rows = $rq->fetchAll(PDO::FETCH_ASSOC) ?: array(); + + $rq = $pdo->prepare('UPDATE `project_has_users` SET `role`=? WHERE `id`=?'); + + foreach ($rows as $row) { + $rq->execute(array( + $row['is_owner'] == 1 ? Role::PROJECT_MANAGER : Role::PROJECT_MEMBER, + $row['id'], + )); + } + + $pdo->exec('ALTER TABLE `project_has_users` DROP COLUMN `is_owner`'); + $pdo->exec('ALTER TABLE `project_has_users` DROP COLUMN `id`'); +} + +function version_95(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE `groups` ( + id INT NOT NULL AUTO_INCREMENT, + external_id VARCHAR(255) DEFAULT '', + name VARCHAR(100) NOT NULL UNIQUE, + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE group_has_users ( + group_id INT NOT NULL, + user_id INT NOT NULL, + FOREIGN KEY(group_id) REFERENCES `groups`(id) ON DELETE CASCADE, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + UNIQUE(group_id, user_id) + ) ENGINE=InnoDB CHARSET=utf8 + "); +} + +function version_94(PDO $pdo) +{ + $pdo->exec('ALTER TABLE `projects` DROP INDEX `name`'); + $pdo->exec('ALTER TABLE `projects` DROP INDEX `name_2`'); +} + +function version_93(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE user_has_metadata ( + user_id INT NOT NULL, + name VARCHAR(50) NOT NULL, + value VARCHAR(255) DEFAULT '', + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + UNIQUE(user_id, name) + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE project_has_metadata ( + project_id INT NOT NULL, + name VARCHAR(50) NOT NULL, + value VARCHAR(255) DEFAULT '', + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + UNIQUE(project_id, name) + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE task_has_metadata ( + task_id INT NOT NULL, + name VARCHAR(50) NOT NULL, + value VARCHAR(255) DEFAULT '', + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE, + UNIQUE(task_id, name) + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec("DROP TABLE project_integrations"); + + $pdo->exec("DELETE FROM settings WHERE `option`='integration_jabber'"); + $pdo->exec("DELETE FROM settings WHERE `option`='integration_jabber_server'"); + $pdo->exec("DELETE FROM settings WHERE `option`='integration_jabber_domain'"); + $pdo->exec("DELETE FROM settings WHERE `option`='integration_jabber_username'"); + $pdo->exec("DELETE FROM settings WHERE `option`='integration_jabber_password'"); + $pdo->exec("DELETE FROM settings WHERE `option`='integration_jabber_nickname'"); + $pdo->exec("DELETE FROM settings WHERE `option`='integration_jabber_room'"); + $pdo->exec("DELETE FROM settings WHERE `option`='integration_hipchat'"); + $pdo->exec("DELETE FROM settings WHERE `option`='integration_hipchat_api_url'"); + $pdo->exec("DELETE FROM settings WHERE `option`='integration_hipchat_room_id'"); + $pdo->exec("DELETE FROM settings WHERE `option`='integration_hipchat_room_token'"); + $pdo->exec("DELETE FROM settings WHERE `option`='integration_slack_webhook'"); + $pdo->exec("DELETE FROM settings WHERE `option`='integration_slack_webhook_url'"); + $pdo->exec("DELETE FROM settings WHERE `option`='integration_slack_webhook_channel'"); +} + +function version_92(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_has_notification_types ( + id INT NOT NULL AUTO_INCREMENT, + project_id INT NOT NULL, + notification_type VARCHAR(50) NOT NULL, + PRIMARY KEY(id), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + UNIQUE(project_id, notification_type) + ) ENGINE=InnoDB CHARSET=utf8 + "); +} + +function version_91(PDO $pdo) +{ + $pdo->exec("ALTER TABLE custom_filters ADD COLUMN `append` TINYINT(1) DEFAULT 0"); +} + +function version_90(PDO $pdo) +{ + $pdo->exec("ALTER TABLE tasks MODIFY date_due BIGINT"); + $pdo->exec("ALTER TABLE tasks MODIFY date_creation BIGINT"); + $pdo->exec("ALTER TABLE tasks MODIFY date_completed BIGINT"); + $pdo->exec("ALTER TABLE tasks MODIFY date_started BIGINT"); + $pdo->exec("ALTER TABLE tasks MODIFY date_moved BIGINT"); + $pdo->exec("ALTER TABLE comments MODIFY date_creation BIGINT"); + $pdo->exec("ALTER TABLE last_logins MODIFY date_creation BIGINT"); + $pdo->exec("ALTER TABLE project_activities MODIFY date_creation BIGINT"); + $pdo->exec("ALTER TABLE projects MODIFY last_modified BIGINT"); + $pdo->exec("ALTER TABLE remember_me MODIFY date_creation BIGINT"); + $pdo->exec('ALTER TABLE files MODIFY `date` BIGINT'); + $pdo->exec('ALTER TABLE transitions MODIFY `date` BIGINT'); + $pdo->exec('ALTER TABLE subtask_time_tracking MODIFY `start` BIGINT'); + $pdo->exec('ALTER TABLE subtask_time_tracking MODIFY `end` BIGINT'); + $pdo->exec('ALTER TABLE users MODIFY `lock_expiration_date` BIGINT'); +} + +function version_89(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE user_has_unread_notifications ( + id INT NOT NULL AUTO_INCREMENT, + user_id INT NOT NULL, + date_creation BIGINT NOT NULL, + event_name VARCHAR(50) NOT NULL, + event_data TEXT NOT NULL, + PRIMARY KEY(id), + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE user_has_notification_types ( + id INT NOT NULL AUTO_INCREMENT, + user_id INT NOT NULL, + notification_type VARCHAR(50), + PRIMARY KEY(id), + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec('CREATE UNIQUE INDEX user_has_notification_types_user_idx ON user_has_notification_types(user_id, notification_type)'); + + // Migrate people who have notification enabled before + $rq = $pdo->prepare('SELECT id FROM users WHERE notifications_enabled=1'); + $rq->execute(); + $user_ids = $rq->fetchAll(PDO::FETCH_COLUMN, 0); + + foreach ($user_ids as $user_id) { + $rq = $pdo->prepare('INSERT INTO user_has_notification_types (user_id, notification_type) VALUES (?, ?)'); + $rq->execute(array($user_id, 'email')); + } +} + +function version_88(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE custom_filters ( + id INT NOT NULL AUTO_INCREMENT, + filter VARCHAR(100) NOT NULL, + project_id INT NOT NULL, + user_id INT NOT NULL, + name VARCHAR(100) NOT NULL, + is_shared TINYINT(1) DEFAULT 0, + PRIMARY KEY(id), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8 + "); +} + +function version_87(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE plugin_schema_versions ( + plugin VARCHAR(80) NOT NULL, + version INT NOT NULL DEFAULT 0, + PRIMARY KEY(plugin) + ) ENGINE=InnoDB CHARSET=utf8 + "); +} + +function version_86(PDO $pdo) +{ + $pdo->exec("ALTER TABLE swimlanes ADD COLUMN description TEXT"); +} + +function version_85(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN gitlab_id INT"); +} + +function version_84(PDO $pdo) +{ + $pdo->exec("ALTER TABLE projects ADD COLUMN start_date VARCHAR(10) DEFAULT ''"); + $pdo->exec("ALTER TABLE projects ADD COLUMN end_date VARCHAR(10) DEFAULT ''"); +} + +function version_83(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN is_project_admin INT DEFAULT 0"); +} + +function version_82(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN nb_failed_login INT DEFAULT 0"); + $pdo->exec("ALTER TABLE users ADD COLUMN lock_expiration_date INT DEFAULT 0"); +} + +function version_81(PDO $pdo) +{ + $pdo->exec("INSERT INTO settings VALUES ('subtask_time_tracking', '1')"); + $pdo->exec("INSERT INTO settings VALUES ('cfd_include_closed_tasks', '1')"); +} + +function version_80(PDO $pdo) +{ + $pdo->exec("INSERT INTO settings VALUES ('default_color', 'yellow')"); +} + +function version_79(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_daily_stats ( + id INT NOT NULL AUTO_INCREMENT, + day CHAR(10) NOT NULL, + project_id INT NOT NULL, + avg_lead_time INT NOT NULL DEFAULT 0, + avg_cycle_time INT NOT NULL DEFAULT 0, + PRIMARY KEY(id), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec('CREATE UNIQUE INDEX project_daily_stats_idx ON project_daily_stats(day, project_id)'); + + $pdo->exec('RENAME TABLE project_daily_summaries TO project_daily_column_stats'); +} + +function version_78(PDO $pdo) +{ + $pdo->exec("ALTER TABLE project_integrations ADD COLUMN slack_webhook_channel VARCHAR(255) DEFAULT ''"); + $pdo->exec("INSERT INTO settings VALUES ('integration_slack_webhook_channel', '')"); +} + +function version_77(PDO $pdo) +{ + $pdo->exec('ALTER TABLE users DROP COLUMN `default_project_id`'); +} + +function version_76(PDO $pdo) +{ + $pdo->exec("DELETE FROM `settings` WHERE `option`='subtask_time_tracking'"); +} + +function version_75(PDO $pdo) +{ + $pdo->exec('ALTER TABLE comments DROP FOREIGN KEY comments_ibfk_2'); + $pdo->exec('ALTER TABLE comments MODIFY task_id INT NOT NULL'); + $pdo->exec('ALTER TABLE comments CHANGE COLUMN `user_id` `user_id` INT DEFAULT 0'); + $pdo->exec('ALTER TABLE comments CHANGE COLUMN `date` `date_creation` INT NOT NULL'); +} + +function version_74(PDO $pdo) +{ + $pdo->exec('ALTER TABLE project_has_categories MODIFY project_id INT NOT NULL'); + $pdo->exec('ALTER TABLE project_has_categories MODIFY name VARCHAR(255) NOT NULL'); + + $pdo->exec('ALTER TABLE actions MODIFY project_id INT NOT NULL'); + $pdo->exec('ALTER TABLE actions MODIFY event_name VARCHAR(50) NOT NULL'); + $pdo->exec('ALTER TABLE actions MODIFY action_name VARCHAR(50) NOT NULL'); + + $pdo->exec('ALTER TABLE action_has_params MODIFY action_id INT NOT NULL'); + $pdo->exec('ALTER TABLE action_has_params MODIFY name VARCHAR(50) NOT NULL'); + $pdo->exec('ALTER TABLE action_has_params MODIFY value VARCHAR(50) NOT NULL'); + + $pdo->exec('ALTER TABLE files MODIFY name VARCHAR(255) NOT NULL'); + $pdo->exec('ALTER TABLE files MODIFY task_id INT NOT NULL'); + + $pdo->exec('ALTER TABLE subtasks MODIFY title VARCHAR(255) NOT NULL'); + + $pdo->exec('ALTER TABLE tasks MODIFY project_id INT NOT NULL'); + $pdo->exec('ALTER TABLE tasks MODIFY column_id INT NOT NULL'); + + $pdo->exec('ALTER TABLE columns MODIFY title VARCHAR(255) NOT NULL'); + $pdo->exec('ALTER TABLE columns MODIFY project_id INT NOT NULL'); + + $pdo->exec('ALTER TABLE project_has_users MODIFY project_id INT NOT NULL'); + $pdo->exec('ALTER TABLE project_has_users MODIFY user_id INT NOT NULL'); + + $pdo->exec('ALTER TABLE projects MODIFY name VARCHAR(255) NOT NULL UNIQUE'); + + $pdo->exec('ALTER TABLE users MODIFY username VARCHAR(50) NOT NULL'); + + $pdo->exec('ALTER TABLE user_has_notifications MODIFY project_id INT NOT NULL'); + $pdo->exec('ALTER TABLE user_has_notifications MODIFY user_id INT NOT NULL'); +} + +function version_73(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN notifications_filter INT DEFAULT 4"); +} + +function version_72(PDO $pdo) +{ + $pdo->exec('ALTER TABLE files MODIFY name VARCHAR(255)'); +} + +function version_71(PDO $pdo) +{ + $rq = $pdo->prepare('INSERT INTO `settings` VALUES (?, ?)'); + $rq->execute(array('webhook_url', '')); + + $pdo->exec("DELETE FROM `settings` WHERE `option`='webhook_url_task_creation'"); + $pdo->exec("DELETE FROM `settings` WHERE `option`='webhook_url_task_modification'"); +} + +function version_70(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN token VARCHAR(255) DEFAULT ''"); +} + +function version_69(PDO $pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('calendar_user_subtasks_time_tracking', 0)); + $rq->execute(array('calendar_user_tasks', 'date_started')); + $rq->execute(array('calendar_project_tasks', 'date_started')); + + $pdo->exec("DELETE FROM `settings` WHERE `option`='subtask_forecast'"); +} + +function version_68(PDO $pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('integration_jabber', '0')); + $rq->execute(array('integration_jabber_server', '')); + $rq->execute(array('integration_jabber_domain', '')); + $rq->execute(array('integration_jabber_username', '')); + $rq->execute(array('integration_jabber_password', '')); + $rq->execute(array('integration_jabber_nickname', 'kanboard')); + $rq->execute(array('integration_jabber_room', '')); + + $pdo->exec("ALTER TABLE project_integrations ADD COLUMN jabber INTEGER DEFAULT '0'"); + $pdo->exec("ALTER TABLE project_integrations ADD COLUMN jabber_server VARCHAR(255) DEFAULT ''"); + $pdo->exec("ALTER TABLE project_integrations ADD COLUMN jabber_domain VARCHAR(255) DEFAULT ''"); + $pdo->exec("ALTER TABLE project_integrations ADD COLUMN jabber_username VARCHAR(255) DEFAULT ''"); + $pdo->exec("ALTER TABLE project_integrations ADD COLUMN jabber_password VARCHAR(255) DEFAULT ''"); + $pdo->exec("ALTER TABLE project_integrations ADD COLUMN jabber_nickname VARCHAR(255) DEFAULT 'kanboard'"); + $pdo->exec("ALTER TABLE project_integrations ADD COLUMN jabber_room VARCHAR(255) DEFAULT ''"); +} + +function version_67(PDO $pdo) +{ + $pdo->exec('ALTER TABLE tasks ADD COLUMN recurrence_status INTEGER NOT NULL DEFAULT 0'); + $pdo->exec('ALTER TABLE tasks ADD COLUMN recurrence_trigger INTEGER NOT NULL DEFAULT 0'); + $pdo->exec('ALTER TABLE tasks ADD COLUMN recurrence_factor INTEGER NOT NULL DEFAULT 0'); + $pdo->exec('ALTER TABLE tasks ADD COLUMN recurrence_timeframe INTEGER NOT NULL DEFAULT 0'); + $pdo->exec('ALTER TABLE tasks ADD COLUMN recurrence_basedate INTEGER NOT NULL DEFAULT 0'); + $pdo->exec('ALTER TABLE tasks ADD COLUMN recurrence_parent INTEGER'); + $pdo->exec('ALTER TABLE tasks ADD COLUMN recurrence_child INTEGER'); +} + +function version_66(PDO $pdo) +{ + $pdo->exec("ALTER TABLE projects ADD COLUMN identifier VARCHAR(50) DEFAULT ''"); +} + +function version_65(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_integrations ( + `id` INT NOT NULL AUTO_INCREMENT, + `project_id` INT NOT NULL UNIQUE, + `hipchat` TINYINT(1) DEFAULT 0, + `hipchat_api_url` VARCHAR(255) DEFAULT 'https://api.hipchat.com', + `hipchat_room_id` VARCHAR(255), + `hipchat_room_token` VARCHAR(255), + `slack` TINYINT(1) DEFAULT 0, + `slack_webhook_url` VARCHAR(255), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8 + "); +} + +function version_64(PDO $pdo) +{ + $pdo->exec('ALTER TABLE project_daily_summaries ADD COLUMN score INT NOT NULL DEFAULT 0'); +} + +function version_63(PDO $pdo) +{ + $pdo->exec('ALTER TABLE project_has_categories ADD COLUMN description TEXT'); +} + +function version_62(PDO $pdo) +{ + $pdo->exec('ALTER TABLE files ADD COLUMN `date` INT NOT NULL DEFAULT 0'); + $pdo->exec('ALTER TABLE files ADD COLUMN `user_id` INT NOT NULL DEFAULT 0'); + $pdo->exec('ALTER TABLE files ADD COLUMN `size` INT NOT NULL DEFAULT 0'); +} + +function version_61(PDO $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 $pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('integration_gravatar', '0')); +} + +function version_59(PDO $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 $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) +{ + $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) +{ + $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 $pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('subtask_forecast', '0')); +} + +function version_54(PDO $pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('application_stylesheet', '')); +} + +function version_53(PDO $pdo) +{ + $pdo->exec("ALTER TABLE subtask_time_tracking ADD COLUMN time_spent FLOAT DEFAULT 0"); +} + +function version_49(PDO $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) +{ + $pdo->exec('RENAME TABLE task_has_files TO files'); + $pdo->exec('RENAME TABLE task_has_subtasks TO subtasks'); +} + +function version_47(PDO $pdo) +{ + $pdo->exec('ALTER TABLE projects ADD COLUMN description TEXT'); +} + +function version_46(PDO $pdo) +{ + $pdo->exec("CREATE TABLE links ( + id INT NOT NULL AUTO_INCREMENT, + label VARCHAR(255) NOT NULL, + opposite_id INT DEFAULT 0, + PRIMARY KEY(id), + UNIQUE(label) + ) ENGINE=InnoDB CHARSET=utf8"); + + $pdo->exec("CREATE TABLE task_has_links ( + id INT NOT NULL AUTO_INCREMENT, + link_id INT NOT NULL, + task_id INT NOT NULL, + opposite_task_id INT NOT NULL, + FOREIGN KEY(link_id) REFERENCES links(id) ON DELETE CASCADE, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE, + FOREIGN KEY(opposite_task_id) REFERENCES tasks(id) ON DELETE CASCADE, + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8"); + + $pdo->exec("CREATE INDEX task_has_links_task_index ON task_has_links(task_id)"); + $pdo->exec("CREATE UNIQUE INDEX task_has_links_unique ON task_has_links(link_id, task_id, opposite_task_id)"); + + $rq = $pdo->prepare('INSERT INTO links (label, opposite_id) VALUES (?, ?)'); + + # ID cannot be known at time of record creation so we have to update it after the fact + # On MariaDB clusters auto-increment size is normally != 1, so relying on increments of 1 would break + $arq = $pdo->prepare('UPDATE links SET opposite_id=? WHERE label=?'); + + $rq->execute(array('relates to', 0)); + + $rq->execute(array('blocks', 0)); + $rq->execute(array('is blocked by', get_last_insert_id($pdo))); + $arq->execute(array(get_last_insert_id($pdo), 'blocks')); + + $rq->execute(array('duplicates', 0)); + $rq->execute(array('is duplicated by', get_last_insert_id($pdo))); + $arq->execute(array(get_last_insert_id($pdo), 'duplicates')); + + $rq->execute(array('is a parent of', 0)); + $rq->execute(array('is a child of', get_last_insert_id($pdo))); + $arq->execute(array(get_last_insert_id($pdo), 'is a parent of')); + + $rq->execute(array('is a milestone of', 0)); + $rq->execute(array('targets milestone', get_last_insert_id($pdo))); + $arq->execute(array(get_last_insert_id($pdo), 'is a milestone of')); + + $rq->execute(array('is fixed by', 0)); + $rq->execute(array('fixes', get_last_insert_id($pdo))); + $arq->execute(array(get_last_insert_id($pdo), 'is fixed by')); +} + +function version_45(PDO $pdo) +{ + $pdo->exec('ALTER TABLE tasks ADD COLUMN date_moved INT DEFAULT 0'); + + /* Update tasks.date_moved from project_activities table if tasks.date_moved = null or 0. + * We take max project_activities.date_creation where event_name in task.create','task.move.column + * since creation date is always less than task moves + */ + $pdo->exec("UPDATE tasks + SET date_moved = ( + SELECT md + FROM ( + SELECT task_id, max(date_creation) md + FROM project_activities + WHERE event_name IN ('task.create', 'task.move.column') + GROUP BY task_id + ) src + WHERE id = src.task_id + ) + WHERE (date_moved IS NULL OR date_moved = 0) AND id IN ( + SELECT task_id + FROM ( + SELECT task_id, max(date_creation) md + FROM project_activities + WHERE event_name IN ('task.create', 'task.move.column') + GROUP BY task_id + ) src + )"); + + // If there is no activities for some tasks use the date_creation + $pdo->exec("UPDATE tasks SET date_moved = date_creation WHERE date_moved IS NULL OR date_moved = 0"); +} + +function version_44(PDO $pdo) +{ + $pdo->exec('ALTER TABLE users ADD COLUMN disable_login_form TINYINT(1) DEFAULT 0'); +} + +function version_43(PDO $pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('subtask_restriction', '0')); + $rq->execute(array('subtask_time_tracking', '0')); + + $pdo->exec(" + CREATE TABLE subtask_time_tracking ( + id INT NOT NULL AUTO_INCREMENT, + user_id INT NOT NULL, + subtask_id INT NOT NULL, + start INT DEFAULT 0, + end INT DEFAULT 0, + PRIMARY KEY(id), + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(subtask_id) REFERENCES task_has_subtasks(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8 + "); +} + +function version_42(PDO $pdo) +{ + $pdo->exec('ALTER TABLE columns ADD COLUMN description TEXT'); +} + +function version_41(PDO $pdo) +{ + $pdo->exec('ALTER TABLE users ADD COLUMN timezone VARCHAR(50)'); + $pdo->exec('ALTER TABLE users ADD COLUMN language CHAR(5)'); +} + +function version_40(PDO $pdo) +{ + // Avoid some full table scans + $pdo->exec('CREATE INDEX users_admin_idx ON users(is_admin)'); + $pdo->exec('CREATE INDEX columns_project_idx ON columns(project_id)'); + $pdo->exec('CREATE INDEX tasks_project_idx ON tasks(project_id)'); + $pdo->exec('CREATE INDEX swimlanes_project_idx ON swimlanes(project_id)'); + $pdo->exec('CREATE INDEX categories_project_idx ON project_has_categories(project_id)'); + $pdo->exec('CREATE INDEX subtasks_task_idx ON task_has_subtasks(task_id)'); + $pdo->exec('CREATE INDEX files_task_idx ON task_has_files(task_id)'); + $pdo->exec('CREATE INDEX comments_task_idx ON comments(task_id)'); + + // Set the ownership for all private projects + $rq = $pdo->prepare('SELECT id FROM projects WHERE is_private=1'); + $rq->execute(); + $project_ids = $rq->fetchAll(PDO::FETCH_COLUMN, 0); + + $rq = $pdo->prepare('UPDATE project_has_users SET is_owner=1 WHERE project_id=?'); + + foreach ($project_ids as $project_id) { + $rq->execute(array($project_id)); + } +} + +function version_39(PDO $pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('project_categories', '')); +} + +function version_38(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE swimlanes ( + id INT NOT NULL AUTO_INCREMENT, + name VARCHAR(200) NOT NULL, + position INT DEFAULT 1, + is_active INT DEFAULT 1, + project_id INT, + PRIMARY KEY(id), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + UNIQUE (name, project_id) + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec('ALTER TABLE tasks ADD COLUMN swimlane_id INT DEFAULT 0'); + $pdo->exec("ALTER TABLE projects ADD COLUMN default_swimlane VARCHAR(200) DEFAULT 'Default swimlane'"); + $pdo->exec("ALTER TABLE projects ADD COLUMN show_default_swimlane INT DEFAULT 1"); +} + +function version_37(PDO $pdo) +{ + $pdo->exec("ALTER TABLE project_has_users ADD COLUMN is_owner TINYINT(1) DEFAULT '0'"); +} + +function version_36(PDO $pdo) +{ + $pdo->exec('ALTER TABLE tasks MODIFY title VARCHAR(255) NOT NULL'); +} + +function version_35(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_daily_summaries ( + id INT NOT NULL AUTO_INCREMENT, + day CHAR(10) NOT NULL, + project_id INT NOT NULL, + column_id INT NOT NULL, + total INT NOT NULL DEFAULT 0, + PRIMARY KEY(id), + FOREIGN KEY(column_id) REFERENCES columns(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec('CREATE UNIQUE INDEX project_daily_column_stats_idx ON project_daily_summaries(day, project_id, column_id)'); +} + +function version_34(PDO $pdo) +{ + $pdo->exec("ALTER TABLE projects ADD COLUMN is_everybody_allowed TINYINT(1) DEFAULT '0'"); +} + +function version_33(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_activities ( + id INT NOT NULL AUTO_INCREMENT, + date_creation INT NOT NULL, + event_name VARCHAR(50) NOT NULL, + creator_id INT, + project_id INT, + task_id INT, + data TEXT, + PRIMARY KEY(id), + FOREIGN KEY(creator_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 + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec('DROP TABLE task_has_events'); + $pdo->exec('DROP TABLE comment_has_events'); + $pdo->exec('DROP TABLE subtask_has_events'); +} + +function version_32(PDO $pdo) +{ + $pdo->exec("ALTER TABLE tasks ADD COLUMN date_started INTEGER"); + $pdo->exec("ALTER TABLE tasks ADD COLUMN time_spent FLOAT DEFAULT 0"); + $pdo->exec("ALTER TABLE tasks ADD COLUMN time_estimated FLOAT DEFAULT 0"); + + $pdo->exec("ALTER TABLE task_has_subtasks MODIFY time_estimated FLOAT"); + $pdo->exec("ALTER TABLE task_has_subtasks MODIFY time_spent FLOAT"); +} + +function version_31(PDO $pdo) +{ + $pdo->exec("ALTER TABLE projects ADD COLUMN is_private TINYINT(1) DEFAULT '0'"); +} + +function version_30(PDO $pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('application_date_format', 'm/d/Y')); +} + +function version_29(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE settings ( + `option` VARCHAR(100) PRIMARY KEY, + `value` VARCHAR(255) DEFAULT '' + ) + "); + + // Migrate old config parameters + $rq = $pdo->prepare('SELECT * FROM config'); + $rq->execute(); + $parameters = $rq->fetch(PDO::FETCH_ASSOC); + + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('board_highlight_period', defined('RECENT_TASK_PERIOD') ? RECENT_TASK_PERIOD : 48*60*60)); + $rq->execute(array('board_public_refresh_interval', defined('BOARD_PUBLIC_CHECK_INTERVAL') ? BOARD_PUBLIC_CHECK_INTERVAL : 60)); + $rq->execute(array('board_private_refresh_interval', defined('BOARD_CHECK_INTERVAL') ? BOARD_CHECK_INTERVAL : 10)); + $rq->execute(array('board_columns', $parameters['default_columns'])); + $rq->execute(array('webhook_url_task_creation', $parameters['webhooks_url_task_creation'])); + $rq->execute(array('webhook_url_task_modification', $parameters['webhooks_url_task_modification'])); + $rq->execute(array('webhook_token', $parameters['webhooks_token'])); + $rq->execute(array('api_token', $parameters['api_token'])); + $rq->execute(array('application_language', $parameters['language'])); + $rq->execute(array('application_timezone', $parameters['timezone'])); + $rq->execute(array('application_url', defined('KANBOARD_URL') ? KANBOARD_URL : '')); + + $pdo->exec('DROP TABLE config'); +} + +function version_28(PDO $pdo) +{ + $pdo->exec("ALTER TABLE tasks ADD COLUMN reference VARCHAR(50) DEFAULT ''"); + $pdo->exec("ALTER TABLE comments ADD COLUMN reference VARCHAR(50) DEFAULT ''"); + + $pdo->exec('CREATE INDEX tasks_reference_idx ON tasks(reference)'); + $pdo->exec('CREATE INDEX comments_reference_idx ON comments(reference)'); +} + +function version_27(PDO $pdo) +{ + $pdo->exec('CREATE UNIQUE INDEX users_username_idx ON users(username)'); +} + +function version_26(PDO $pdo) +{ + $pdo->exec("ALTER TABLE config ADD COLUMN default_columns VARCHAR(255) DEFAULT ''"); +} + +function version_25(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE task_has_events ( + id INT NOT NULL AUTO_INCREMENT, + date_creation INT NOT NULL, + event_name TEXT NOT NULL, + creator_id INT, + project_id INT, + task_id INT, + data TEXT, + FOREIGN KEY(creator_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 TABLE subtask_has_events ( + id INT NOT NULL AUTO_INCREMENT, + date_creation INT NOT NULL, + event_name TEXT NOT NULL, + creator_id INT, + project_id INT, + subtask_id INT, + task_id INT, + data TEXT, + FOREIGN KEY(creator_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(subtask_id) REFERENCES task_has_subtasks(id) ON DELETE CASCADE, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE, + PRIMARY KEY (id) + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE comment_has_events ( + id INT NOT NULL AUTO_INCREMENT, + date_creation INT NOT NULL, + event_name TEXT NOT NULL, + creator_id INT, + project_id INT, + comment_id INT, + task_id INT, + data TEXT, + FOREIGN KEY(creator_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(comment_id) REFERENCES comments(id) ON DELETE CASCADE, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE, + PRIMARY KEY (id) + ) ENGINE=InnoDB CHARSET=utf8 + "); +} + +function version_24(PDO $pdo) +{ + $pdo->exec("ALTER TABLE projects ADD COLUMN is_public TINYINT(1) DEFAULT '0'"); +} + +function version_23(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN notifications_enabled TINYINT(1) DEFAULT '0'"); + + $pdo->exec(" + CREATE TABLE user_has_notifications ( + user_id INT, + project_id INT, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + UNIQUE(project_id, user_id) + ); + "); +} + +function version_22(PDO $pdo) +{ + $pdo->exec("ALTER TABLE config ADD COLUMN webhooks_url_task_modification VARCHAR(255)"); + $pdo->exec("ALTER TABLE config ADD COLUMN webhooks_url_task_creation VARCHAR(255)"); +} + +function version_21(PDO $pdo) +{ + $pdo->exec("ALTER TABLE tasks ADD COLUMN creator_id INTEGER DEFAULT '0'"); + $pdo->exec("ALTER TABLE tasks ADD COLUMN date_modification INTEGER DEFAULT '0'"); +} + +function version_20(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN github_id VARCHAR(30)"); +} + +function version_19(PDO $pdo) +{ + $pdo->exec("ALTER TABLE config ADD COLUMN api_token VARCHAR(255) DEFAULT ''"); + $pdo->exec("UPDATE config SET api_token='".Token::getToken()."'"); +} + +function version_18(PDO $pdo) +{ + $pdo->exec( + " + CREATE TABLE task_has_subtasks ( + id INT NOT NULL AUTO_INCREMENT, + title VARCHAR(255), + status INT DEFAULT 0, + time_estimated INT DEFAULT 0, + time_spent INT DEFAULT 0, + task_id INT NOT NULL, + user_id INT, + PRIMARY KEY (id), + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8" + ); +} + +function version_17(PDO $pdo) +{ + $pdo->exec( + " + CREATE TABLE task_has_files ( + id INT NOT NULL AUTO_INCREMENT, + name VARCHAR(50), + path VARCHAR(255), + is_image TINYINT(1) DEFAULT 0, + task_id INT, + PRIMARY KEY (id), + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8" + ); +} + +function version_16(PDO $pdo) +{ + $pdo->exec( + " + CREATE TABLE project_has_categories ( + id INT NOT NULL AUTO_INCREMENT, + name VARCHAR(255), + project_id INT, + PRIMARY KEY (id), + UNIQUE KEY `idx_project_category` (project_id, name), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8" + ); + + $pdo->exec("ALTER TABLE tasks ADD COLUMN category_id INT DEFAULT 0"); +} + +function version_15(PDO $pdo) +{ + $pdo->exec("ALTER TABLE projects ADD COLUMN last_modified INT DEFAULT 0"); +} + +function version_14(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN name VARCHAR(255)"); + $pdo->exec("ALTER TABLE users ADD COLUMN email VARCHAR(255)"); + $pdo->exec("ALTER TABLE users ADD COLUMN google_id VARCHAR(30)"); +} + +function version_13(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN is_ldap_user TINYINT(1) DEFAULT 0"); +} + +function version_12(PDO $pdo) +{ + $pdo->exec( + " + CREATE TABLE remember_me ( + id INT NOT NULL AUTO_INCREMENT, + user_id INT, + ip VARCHAR(45), + user_agent VARCHAR(255), + token VARCHAR(255), + sequence VARCHAR(255), + expiration INT, + date_creation INT, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + PRIMARY KEY (id) + ) ENGINE=InnoDB CHARSET=utf8" + ); + + $pdo->exec( + " + CREATE TABLE last_logins ( + id INT NOT NULL AUTO_INCREMENT, + auth_type VARCHAR(25), + user_id INT, + ip VARCHAR(45), + user_agent VARCHAR(255), + date_creation INT, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + PRIMARY KEY (id), + INDEX (user_id) + ) ENGINE=InnoDB CHARSET=utf8" + ); +} + +function version_1(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE config ( + language CHAR(5) DEFAULT 'en_US', + webhooks_token VARCHAR(255) DEFAULT '', + timezone VARCHAR(50) DEFAULT 'UTC' + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE users ( + id INT NOT NULL AUTO_INCREMENT, + username VARCHAR(50), + password VARCHAR(255), + is_admin TINYINT DEFAULT 0, + default_project_id INT DEFAULT 0, + PRIMARY KEY (id) + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE projects ( + id INT NOT NULL AUTO_INCREMENT, + name VARCHAR(50) UNIQUE, + is_active TINYINT DEFAULT 1, + token VARCHAR(255), + PRIMARY KEY (id) + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE project_has_users ( + id INT NOT NULL AUTO_INCREMENT, + project_id INT, + user_id INT, + PRIMARY KEY (id), + UNIQUE KEY `idx_project_user` (project_id, user_id), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE columns ( + id INT NOT NULL AUTO_INCREMENT, + title VARCHAR(255), + position INT NOT NULL, + project_id INT NOT NULL, + task_limit INT DEFAULT '0', + UNIQUE KEY `idx_title_project` (title, project_id), + PRIMARY KEY (id), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE tasks ( + id INT NOT NULL AUTO_INCREMENT, + title VARCHAR(255), + description TEXT, + date_creation INT, + date_completed INT, + date_due INT, + color_id VARCHAR(50), + project_id INT, + column_id INT, + owner_id INT DEFAULT '0', + position INT, + score INT, + is_active TINYINT DEFAULT 1, + PRIMARY KEY (id), + INDEX `idx_task_active` (is_active), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(column_id) REFERENCES columns(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE comments ( + id INT NOT NULL AUTO_INCREMENT, + task_id INT, + user_id INT, + `date` INT, + comment TEXT, + PRIMARY KEY (id), + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE actions ( + id INT NOT NULL AUTO_INCREMENT, + project_id INT, + event_name VARCHAR(50), + action_name VARCHAR(50), + PRIMARY KEY (id), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE action_has_params ( + id INT NOT NULL AUTO_INCREMENT, + action_id INT, + name VARCHAR(50), + value VARCHAR(50), + PRIMARY KEY (id), + FOREIGN KEY(action_id) REFERENCES actions(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + INSERT INTO users + (username, password, is_admin) + VALUES ('admin', '".\password_hash('admin', PASSWORD_BCRYPT)."', '1') + "); + + $pdo->exec(" + INSERT INTO config + (webhooks_token) + VALUES ('".Token::getToken()."') + "); +} diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php new file mode 100644 index 0000000..c17004a --- /dev/null +++ b/app/Schema/Postgres.php @@ -0,0 +1,1473 @@ +<?php + +namespace Schema; + +require_once __DIR__.'/Migration.php'; + +use PDO; +use Kanboard\Core\Security\Token; +use Kanboard\Core\Security\Role; + +const VERSION = 117; + +function version_117(PDO $pdo) +{ + $pdo->exec('ALTER TABLE "comments" ADD COLUMN "visibility" VARCHAR(25) NOT NULL DEFAULT \''.Role::APP_USER."'"); +} + +function version_116(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN theme TEXT DEFAULT 'light' NOT NULL"); +} + +function version_115(PDO $pdo) +{ + $pdo->exec('ALTER TABLE "projects" ADD COLUMN enable_global_tags BOOLEAN DEFAULT TRUE'); +} + +function version_114(PDO $pdo) +{ + $pdo->exec('ALTER TABLE "swimlanes" ADD COLUMN task_limit INTEGER DEFAULT 0'); +} + +function version_113(PDO $pdo) +{ + $pdo->exec('ALTER TABLE "projects" ADD COLUMN task_limit INTEGER DEFAULT 0'); +} + +function version_112(PDO $pdo) +{ + $pdo->exec('ALTER TABLE "projects" ADD COLUMN per_swimlane_task_limits BOOLEAN DEFAULT FALSE'); +} + +function version_111(PDO $pdo) +{ + $pdo->exec('ALTER TABLE "tags" ADD COLUMN "color_id" VARCHAR(50) DEFAULT NULL'); +} + +function version_110(PDO $pdo) +{ + $pdo->exec('ALTER TABLE "project_has_categories" ADD COLUMN "color_id" VARCHAR(50) DEFAULT NULL'); +} + +function version_109(PDO $pdo) +{ + $pdo->exec('ALTER TABLE "users" ALTER COLUMN "language" TYPE VARCHAR(11)'); +} + +function version_108(PDO $pdo) +{ + $pdo->exec('ALTER TABLE "projects" ALTER COLUMN "name" TYPE TEXT'); + $pdo->exec('ALTER TABLE "projects" ALTER COLUMN "email" TYPE TEXT'); + $pdo->exec('ALTER TABLE "action_has_params" ALTER COLUMN "name" TYPE TEXT'); + $pdo->exec('ALTER TABLE "action_has_params" ALTER COLUMN "value" TYPE TEXT'); + $pdo->exec('ALTER TABLE "actions" ALTER COLUMN "event_name" TYPE TEXT'); + $pdo->exec('ALTER TABLE "actions" ALTER COLUMN "action_name" TYPE TEXT'); + $pdo->exec('ALTER TABLE "comments" ALTER COLUMN "reference" TYPE TEXT'); + $pdo->exec('ALTER TABLE "custom_filters" ALTER COLUMN "filter" TYPE TEXT'); + $pdo->exec('ALTER TABLE "custom_filters" ALTER COLUMN "name" TYPE TEXT'); + $pdo->exec('ALTER TABLE "groups" ALTER COLUMN "name" TYPE TEXT'); + $pdo->exec('ALTER TABLE "project_activities" ALTER COLUMN "event_name" TYPE TEXT'); + $pdo->exec('ALTER TABLE "project_has_files" ALTER COLUMN "name" TYPE TEXT'); + $pdo->exec('ALTER TABLE "project_has_files" ALTER COLUMN "path" TYPE TEXT'); + $pdo->exec('ALTER TABLE "subtasks" ALTER COLUMN "title" TYPE TEXT'); + $pdo->exec('ALTER TABLE "swimlanes" ALTER COLUMN "name" TYPE TEXT'); + $pdo->exec('ALTER TABLE "task_has_external_links" ALTER COLUMN "title" TYPE TEXT'); + $pdo->exec('ALTER TABLE "task_has_external_links" ALTER COLUMN "url" TYPE TEXT'); + $pdo->exec('ALTER TABLE "task_has_files" ALTER COLUMN "name" TYPE TEXT'); + $pdo->exec('ALTER TABLE "task_has_files" ALTER COLUMN "path" TYPE TEXT'); + $pdo->exec('ALTER TABLE "tasks" ALTER COLUMN "title" TYPE TEXT'); + $pdo->exec('ALTER TABLE "tasks" ALTER COLUMN "reference" TYPE TEXT'); + $pdo->exec('ALTER TABLE "user_has_unread_notifications" ALTER COLUMN "event_name" TYPE TEXT'); + $pdo->exec('ALTER TABLE "users" ALTER COLUMN "username" TYPE TEXT'); + $pdo->exec('ALTER TABLE "users" ALTER COLUMN "filter" TYPE TEXT'); +} + +function version_107(PDO $pdo) +{ + $pdo->exec('ALTER TABLE "users" ADD COLUMN filter VARCHAR(255) DEFAULT NULL'); +} + +function version_106(PDO $pdo) +{ + $pdo->exec("CREATE TABLE sessions ( + id TEXT PRIMARY KEY, + expire_at INTEGER NOT NULL, + data TEXT DEFAULT '' + )"); +} + +function version_105(PDO $pdo) +{ + $pdo->exec('CREATE TABLE predefined_task_descriptions ( + id SERIAL PRIMARY KEY, + project_id INTEGER NOT NULL, + title TEXT NOT NULL, + description TEXT NOT NULL, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + )'); +} + +function version_104(PDO $pdo) +{ + $pdo->exec('ALTER TABLE projects DROP COLUMN is_everybody_allowed'); +} + +function version_103(PDO $pdo) +{ + $pdo->exec('ALTER TABLE projects ADD COLUMN predefined_email_subjects TEXT'); +} + +function version_102(PDO $pdo) +{ + $pdo->exec('ALTER TABLE column_has_move_restrictions ADD COLUMN only_assigned BOOLEAN DEFAULT FALSE'); +} + +function version_101(PDO $pdo) +{ + migrate_default_swimlane($pdo); + + $pdo->exec('ALTER TABLE "projects" DROP COLUMN "default_swimlane"'); + $pdo->exec('ALTER TABLE "projects" DROP COLUMN "show_default_swimlane"'); + $pdo->exec('ALTER TABLE "tasks" ALTER COLUMN "swimlane_id" SET NOT NULL'); + $pdo->exec('ALTER TABLE "tasks" ALTER COLUMN "swimlane_id" DROP DEFAULT'); + $pdo->exec('ALTER TABLE "tasks" ADD FOREIGN KEY (swimlane_id) REFERENCES swimlanes ON DELETE CASCADE'); +} + +function version_100(PDO $pdo) +{ + $pdo->exec('ALTER TABLE "projects" ADD COLUMN email VARCHAR(255)'); +} + +function version_99(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE invites ( + email VARCHAR(255) NOT NULL, + project_id INTEGER NOT NULL, + token VARCHAR(255) NOT NULL, + PRIMARY KEY(email, token) + ) + "); + + $pdo->exec("DELETE FROM settings WHERE \"option\"='application_datetime_format'"); +} + +function version_98(PDO $pdo) +{ + $pdo->exec('ALTER TABLE "comments" ADD COLUMN date_modification BIGINT'); + $pdo->exec('UPDATE "comments" SET date_modification = date_creation WHERE date_modification IS NULL'); +} + +function version_97(PDO $pdo) +{ + $pdo->exec('ALTER TABLE "users" ADD COLUMN api_access_token VARCHAR(255) DEFAULT NULL'); +} + +function version_96(PDO $pdo) +{ + $pdo->exec('ALTER TABLE "settings" ALTER COLUMN "value" TYPE TEXT'); +} + +function version_95(PDO $pdo) +{ + $pdo->exec("ALTER TABLE tasks ADD COLUMN external_provider VARCHAR(255)"); + $pdo->exec("ALTER TABLE tasks ADD COLUMN external_uri VARCHAR(255)"); +} + +function version_94(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE column_has_restrictions ( + restriction_id SERIAL PRIMARY KEY, + project_id INTEGER NOT NULL, + role_id INTEGER NOT NULL, + column_id INTEGER NOT NULL, + rule VARCHAR(255) NOT NULL, + UNIQUE(role_id, column_id, rule), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(role_id) REFERENCES project_has_roles(role_id) ON DELETE CASCADE, + FOREIGN KEY(column_id) REFERENCES columns(id) ON DELETE CASCADE + ) + "); +} + +function version_93(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_role_has_restrictions ( + restriction_id SERIAL PRIMARY KEY, + project_id INTEGER NOT NULL, + role_id INTEGER NOT NULL, + rule VARCHAR(255) NOT NULL, + UNIQUE(role_id, rule), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(role_id) REFERENCES project_has_roles(role_id) ON DELETE CASCADE + ) + "); +} + +function version_92(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_has_roles ( + role_id SERIAL PRIMARY KEY, + role VARCHAR(255) NOT NULL, + project_id INTEGER NOT NULL, + UNIQUE(project_id, role), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + ) + "); + + $pdo->exec(" + CREATE TABLE column_has_move_restrictions ( + restriction_id SERIAL PRIMARY KEY, + project_id INTEGER NOT NULL, + role_id INTEGER NOT NULL, + src_column_id INTEGER NOT NULL, + dst_column_id INTEGER NOT NULL, + UNIQUE(role_id, src_column_id, dst_column_id), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(role_id) REFERENCES project_has_roles(role_id) ON DELETE CASCADE, + FOREIGN KEY(src_column_id) REFERENCES columns(id) ON DELETE CASCADE, + FOREIGN KEY(dst_column_id) REFERENCES columns(id) ON DELETE CASCADE + ) + "); + + $pdo->exec('ALTER TABLE "project_has_users" ALTER COLUMN "role" TYPE VARCHAR(255)'); + $pdo->exec('ALTER TABLE "project_has_groups" ALTER COLUMN "role" TYPE VARCHAR(255)'); +} + +function version_91(PDO $pdo) +{ + $pdo->exec("ALTER TABLE columns ADD COLUMN hide_in_dashboard BOOLEAN DEFAULT '0'"); +} + +function version_90(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE tags ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + project_id INTEGER NOT NULL, + UNIQUE(project_id, name) + ) + "); + + $pdo->exec(" + CREATE TABLE task_has_tags ( + task_id INTEGER NOT NULL, + tag_id INTEGER NOT NULL, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE, + FOREIGN KEY(tag_id) REFERENCES tags(id) ON DELETE CASCADE, + UNIQUE(tag_id, task_id) + ) + "); +} + +function version_89(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN avatar_path VARCHAR(255)"); +} + +function version_88(PDO $pdo) +{ + $pdo->exec("ALTER TABLE user_has_metadata ADD COLUMN changed_by INTEGER DEFAULT 0 NOT NULL"); + $pdo->exec("ALTER TABLE user_has_metadata ADD COLUMN changed_on INTEGER DEFAULT 0 NOT NULL"); + + $pdo->exec("ALTER TABLE project_has_metadata ADD COLUMN changed_by INTEGER DEFAULT 0 NOT NULL"); + $pdo->exec("ALTER TABLE project_has_metadata ADD COLUMN changed_on INTEGER DEFAULT 0 NOT NULL"); + + $pdo->exec("ALTER TABLE task_has_metadata ADD COLUMN changed_by INTEGER DEFAULT 0 NOT NULL"); + $pdo->exec("ALTER TABLE task_has_metadata ADD COLUMN changed_on INTEGER DEFAULT 0 NOT NULL"); + + $pdo->exec("ALTER TABLE settings ADD COLUMN changed_by INTEGER DEFAULT 0 NOT NULL"); + $pdo->exec("ALTER TABLE settings ADD COLUMN changed_on INTEGER DEFAULT 0 NOT NULL"); +} + +function version_87(PDO $pdo) +{ + $pdo->exec("UPDATE project_activities SET event_name='task.file.create' WHERE event_name='file.create'"); +} + +function version_86(PDO $pdo) +{ + $pdo->exec('ALTER TABLE files RENAME TO task_has_files'); + + $pdo->exec( + " + CREATE TABLE project_has_files ( + id SERIAL PRIMARY KEY, + project_id INTEGER NOT NULL, + name VARCHAR(255) NOT NULL, + path VARCHAR(255) NOT NULL, + is_image BOOLEAN DEFAULT '0', + size INTEGER DEFAULT 0 NOT NULL, + user_id INTEGER DEFAULT 0 NOT NULL, + date INTEGER DEFAULT 0 NOT NULL, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + )" + ); +} + +function version_85(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN is_active BOOLEAN DEFAULT '1'"); +} + +function version_84(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE task_has_external_links ( + id SERIAL PRIMARY KEY, + link_type VARCHAR(100) NOT NULL, + dependency VARCHAR(100) NOT NULL, + title VARCHAR(255) NOT NULL, + url VARCHAR(255) NOT NULL, + date_creation INT NOT NULL, + date_modification INT NOT NULL, + task_id INT NOT NULL, + creator_id INT DEFAULT 0, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + ) + "); +} + +function version_83(PDO $pdo) +{ + $pdo->exec("ALTER TABLE projects ADD COLUMN priority_default INTEGER DEFAULT 0"); + $pdo->exec("ALTER TABLE projects ADD COLUMN priority_start INTEGER DEFAULT 0"); + $pdo->exec("ALTER TABLE projects ADD COLUMN priority_end INTEGER DEFAULT 3"); + $pdo->exec("ALTER TABLE tasks ADD COLUMN priority INTEGER DEFAULT 0"); +} + +function version_82(PDO $pdo) +{ + $pdo->exec("ALTER TABLE projects ADD COLUMN owner_id INTEGER DEFAULT 0"); +} + +function version_81(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE password_reset ( + token VARCHAR(80) PRIMARY KEY, + user_id INTEGER NOT NULL, + date_expiration INTEGER NOT NULL, + date_creation INTEGER NOT NULL, + ip VARCHAR(45) NOT NULL, + user_agent VARCHAR(255) NOT NULL, + is_active BOOLEAN NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + ) + "); + + $pdo->exec("INSERT INTO settings VALUES ('password_reset', '1')"); +} + +function version_80(PDO $pdo) +{ + $pdo->exec('ALTER TABLE "actions" ALTER COLUMN "action_name" TYPE VARCHAR(255)'); +} + +function version_79(PDO $pdo) +{ + $rq = $pdo->prepare('SELECT * FROM actions'); + $rq->execute(); + $rows = $rq->fetchAll(PDO::FETCH_ASSOC) ?: array(); + + $rq = $pdo->prepare('UPDATE actions SET action_name=? WHERE id=?'); + + foreach ($rows as $row) { + if ($row['action_name'] === 'TaskAssignCurrentUser' && $row['event_name'] === 'task.move.column') { + $row['action_name'] = '\Kanboard\Action\TaskAssignCurrentUserColumn'; + } elseif ($row['action_name'] === 'TaskClose' && $row['event_name'] === 'task.move.column') { + $row['action_name'] = '\Kanboard\Action\TaskCloseColumn'; + } elseif ($row['action_name'] === 'TaskLogMoveAnotherColumn') { + $row['action_name'] = '\Kanboard\Action\CommentCreationMoveTaskColumn'; + } elseif ($row['action_name'][0] !== '\\') { + $row['action_name'] = '\Kanboard\Action\\'.$row['action_name']; + } + + $rq->execute(array($row['action_name'], $row['id'])); + } +} + +function version_78(PDO $pdo) +{ + $pdo->exec('ALTER TABLE "users" ALTER COLUMN "language" TYPE VARCHAR(5)'); +} + +function version_77(PDO $pdo) +{ + $pdo->exec('ALTER TABLE "users" ADD COLUMN "role" VARCHAR(25) NOT NULL DEFAULT \''.Role::APP_USER.'\''); + + $rq = $pdo->prepare('SELECT * FROM "users"'); + $rq->execute(); + $rows = $rq->fetchAll(PDO::FETCH_ASSOC) ?: array(); + + $rq = $pdo->prepare('UPDATE "users" SET "role"=? WHERE "id"=?'); + + foreach ($rows as $row) { + $role = Role::APP_USER; + + if ($row['is_admin'] == 1) { + $role = Role::APP_ADMIN; + } elseif ($row['is_project_admin']) { + $role = Role::APP_MANAGER; + } + + $rq->execute(array($role, $row['id'])); + } + + $pdo->exec('ALTER TABLE users DROP COLUMN "is_admin"'); + $pdo->exec('ALTER TABLE users DROP COLUMN "is_project_admin"'); +} + +function version_76(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_has_groups ( + group_id INTEGER NOT NULL, + project_id INTEGER NOT NULL, + role VARCHAR(25) NOT NULL, + FOREIGN KEY(group_id) REFERENCES groups(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + UNIQUE(group_id, project_id) + ) + "); + + $pdo->exec("ALTER TABLE project_has_users ADD COLUMN role VARCHAR(25) NOT NULL DEFAULT '".Role::PROJECT_VIEWER."'"); + + $rq = $pdo->prepare('SELECT * FROM project_has_users'); + $rq->execute(); + $rows = $rq->fetchAll(PDO::FETCH_ASSOC) ?: array(); + + $rq = $pdo->prepare('UPDATE project_has_users SET "role"=? WHERE "id"=?'); + + foreach ($rows as $row) { + $rq->execute(array( + $row['is_owner'] == 1 ? Role::PROJECT_MANAGER : Role::PROJECT_MEMBER, + $row['id'], + )); + } + + $pdo->exec('ALTER TABLE project_has_users DROP COLUMN "is_owner"'); + $pdo->exec('ALTER TABLE project_has_users DROP COLUMN "id"'); +} + +function version_75(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE groups ( + id SERIAL PRIMARY KEY, + external_id VARCHAR(255) DEFAULT '', + name VARCHAR(100) NOT NULL UNIQUE + ) + "); + + $pdo->exec(" + CREATE TABLE group_has_users ( + group_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + FOREIGN KEY(group_id) REFERENCES groups(id) ON DELETE CASCADE, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + UNIQUE(group_id, user_id) + ) + "); +} + +function version_74(PDO $pdo) +{ + $pdo->exec('ALTER TABLE projects DROP CONSTRAINT IF EXISTS projects_name_key'); +} + +function version_73(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE user_has_metadata ( + user_id INTEGER NOT NULL, + name VARCHAR(50) NOT NULL, + value VARCHAR(255) DEFAULT '', + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + UNIQUE(user_id, name) + ) + "); + + $pdo->exec(" + CREATE TABLE project_has_metadata ( + project_id INTEGER NOT NULL, + name VARCHAR(50) NOT NULL, + value VARCHAR(255) DEFAULT '', + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + UNIQUE(project_id, name) + ) + "); + + $pdo->exec(" + CREATE TABLE task_has_metadata ( + task_id INTEGER NOT NULL, + name VARCHAR(50) NOT NULL, + value VARCHAR(255) DEFAULT '', + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE, + UNIQUE(task_id, name) + ) + "); + + $pdo->exec("DROP TABLE project_integrations"); + + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber'"); + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber_server'"); + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber_domain'"); + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber_username'"); + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber_password'"); + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber_nickname'"); + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber_room'"); + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_hipchat'"); + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_hipchat_api_url'"); + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_hipchat_room_id'"); + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_hipchat_room_token'"); + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_slack_webhook'"); + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_slack_webhook_url'"); + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_slack_webhook_channel'"); +} + +function version_72(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_has_notification_types ( + id SERIAL PRIMARY KEY, + project_id INTEGER NOT NULL, + notification_type VARCHAR(50) NOT NULL, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + UNIQUE(project_id, notification_type) + ) + "); +} + +function version_71(PDO $pdo) +{ + $pdo->exec("ALTER TABLE custom_filters ADD COLUMN \"append\" BOOLEAN DEFAULT '0'"); +} + +function version_70(PDO $pdo) +{ + $pdo->exec("ALTER TABLE tasks ALTER COLUMN date_due TYPE BIGINT"); + $pdo->exec("ALTER TABLE tasks ALTER COLUMN date_creation TYPE BIGINT"); + $pdo->exec("ALTER TABLE tasks ALTER COLUMN date_completed TYPE BIGINT"); + $pdo->exec("ALTER TABLE tasks ALTER COLUMN date_started TYPE BIGINT"); + $pdo->exec("ALTER TABLE tasks ALTER COLUMN date_moved TYPE BIGINT"); + $pdo->exec("ALTER TABLE comments ALTER COLUMN date_creation TYPE BIGINT"); + $pdo->exec("ALTER TABLE last_logins ALTER COLUMN date_creation TYPE BIGINT"); + $pdo->exec("ALTER TABLE project_activities ALTER COLUMN date_creation TYPE BIGINT"); + $pdo->exec("ALTER TABLE projects ALTER COLUMN last_modified TYPE BIGINT"); + $pdo->exec("ALTER TABLE remember_me ALTER COLUMN date_creation TYPE BIGINT"); + $pdo->exec('ALTER TABLE files ALTER COLUMN "date" TYPE BIGINT'); + $pdo->exec('ALTER TABLE transitions ALTER COLUMN "date" TYPE BIGINT'); + $pdo->exec('ALTER TABLE subtask_time_tracking ALTER COLUMN "start" TYPE BIGINT'); + $pdo->exec('ALTER TABLE subtask_time_tracking ALTER COLUMN "end" TYPE BIGINT'); + $pdo->exec('ALTER TABLE users ALTER COLUMN "lock_expiration_date" TYPE BIGINT'); +} + +function version_69(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE user_has_unread_notifications ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL, + date_creation BIGINT NOT NULL, + event_name VARCHAR(50) NOT NULL, + event_data TEXT NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + ) + "); + + $pdo->exec(" + CREATE TABLE user_has_notification_types ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL, + notification_type VARCHAR(50), + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + ) + "); + + $pdo->exec('CREATE UNIQUE INDEX user_has_notification_types_user_idx ON user_has_notification_types(user_id, notification_type)'); + + // Migrate people who have notification enabled before + $rq = $pdo->prepare("SELECT id FROM users WHERE notifications_enabled='1'"); + $rq->execute(); + $user_ids = $rq->fetchAll(PDO::FETCH_COLUMN, 0); + + foreach ($user_ids as $user_id) { + $rq = $pdo->prepare('INSERT INTO user_has_notification_types (user_id, notification_type) VALUES (?, ?)'); + $rq->execute(array($user_id, 'email')); + } +} + +function version_68(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE custom_filters ( + id SERIAL PRIMARY KEY, + filter VARCHAR(100) NOT NULL, + project_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + name VARCHAR(100) NOT NULL, + is_shared BOOLEAN DEFAULT '0' + ) + "); +} + +function version_67(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE plugin_schema_versions ( + plugin VARCHAR(80) NOT NULL PRIMARY KEY, + version INTEGER NOT NULL DEFAULT 0 + ) + "); +} + +function version_66(PDO $pdo) +{ + $pdo->exec("ALTER TABLE swimlanes ADD COLUMN description TEXT"); +} + +function version_65(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN gitlab_id INTEGER"); +} + +function version_64(PDO $pdo) +{ + $pdo->exec("ALTER TABLE projects ADD COLUMN start_date VARCHAR(10) DEFAULT ''"); + $pdo->exec("ALTER TABLE projects ADD COLUMN end_date VARCHAR(10) DEFAULT ''"); +} + +function version_63(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN is_project_admin BOOLEAN DEFAULT '0'"); +} + +function version_62(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN nb_failed_login INTEGER DEFAULT 0"); + $pdo->exec("ALTER TABLE users ADD COLUMN lock_expiration_date INTEGER DEFAULT 0"); +} + +function version_61(PDO $pdo) +{ + $pdo->exec("INSERT INTO settings VALUES ('subtask_time_tracking', '1')"); + $pdo->exec("INSERT INTO settings VALUES ('cfd_include_closed_tasks', '1')"); +} + +function version_60(PDO $pdo) +{ + $pdo->exec("INSERT INTO settings VALUES ('default_color', 'yellow')"); +} + +function version_59(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_daily_stats ( + id SERIAL PRIMARY KEY, + day CHAR(10) NOT NULL, + project_id INTEGER NOT NULL, + avg_lead_time INTEGER NOT NULL DEFAULT 0, + avg_cycle_time INTEGER NOT NULL DEFAULT 0, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + ) + "); + + $pdo->exec('CREATE UNIQUE INDEX project_daily_stats_idx ON project_daily_stats(day, project_id)'); + + $pdo->exec('ALTER TABLE project_daily_summaries RENAME TO project_daily_column_stats'); +} + +function version_58(PDO $pdo) +{ + $pdo->exec("ALTER TABLE project_integrations ADD COLUMN slack_webhook_channel VARCHAR(255) DEFAULT ''"); + $pdo->exec("INSERT INTO settings VALUES ('integration_slack_webhook_channel', '')"); +} + +function version_57(PDO $pdo) +{ + $pdo->exec('ALTER TABLE users DROP COLUMN "default_project_id"'); +} + +function version_56(PDO $pdo) +{ + $pdo->exec('DELETE FROM "settings" WHERE "option"=\'subtask_time_tracking\''); +} + +function version_55(PDO $pdo) +{ + $pdo->exec('ALTER TABLE comments DROP CONSTRAINT IF EXISTS comments_user_id_fkey'); + $pdo->exec("ALTER TABLE comments ALTER COLUMN task_id SET NOT NULL"); + $pdo->exec("ALTER TABLE comments ALTER COLUMN user_id SET DEFAULT 0"); + $pdo->exec('ALTER TABLE comments RENAME COLUMN "date" TO "date_creation"'); + $pdo->exec("ALTER TABLE comments ALTER COLUMN date_creation SET NOT NULL"); +} + +function version_54(PDO $pdo) +{ + $pdo->exec("ALTER TABLE project_has_categories ALTER COLUMN project_id SET NOT NULL"); + $pdo->exec("ALTER TABLE project_has_categories ALTER COLUMN name SET NOT NULL"); + + $pdo->exec("ALTER TABLE actions ALTER COLUMN project_id SET NOT NULL"); + $pdo->exec("ALTER TABLE actions ALTER COLUMN event_name SET NOT NULL"); + $pdo->exec("ALTER TABLE actions ALTER COLUMN action_name SET NOT NULL"); + + $pdo->exec("ALTER TABLE action_has_params ALTER COLUMN action_id SET NOT NULL"); + $pdo->exec("ALTER TABLE action_has_params ALTER COLUMN name SET NOT NULL"); + $pdo->exec("ALTER TABLE action_has_params ALTER COLUMN value SET NOT NULL"); + + $pdo->exec("ALTER TABLE files ALTER COLUMN name SET NOT NULL"); + $pdo->exec("ALTER TABLE files ALTER COLUMN task_id SET NOT NULL"); + + $pdo->exec("ALTER TABLE subtasks ALTER COLUMN title SET NOT NULL"); + + $pdo->exec("ALTER TABLE tasks ALTER COLUMN title SET NOT NULL"); + $pdo->exec("ALTER TABLE tasks ALTER COLUMN project_id SET NOT NULL"); + $pdo->exec("ALTER TABLE tasks ALTER COLUMN column_id SET NOT NULL"); + + $pdo->exec("ALTER TABLE columns ALTER COLUMN title SET NOT NULL"); + $pdo->exec("ALTER TABLE columns ALTER COLUMN project_id SET NOT NULL"); + + $pdo->exec("ALTER TABLE project_has_users ALTER COLUMN project_id SET NOT NULL"); + $pdo->exec("ALTER TABLE project_has_users ALTER COLUMN user_id SET NOT NULL"); + + $pdo->exec("ALTER TABLE projects ALTER COLUMN name SET NOT NULL"); + + $pdo->exec("ALTER TABLE users ALTER COLUMN username SET NOT NULL"); + + $pdo->exec("ALTER TABLE user_has_notifications ALTER COLUMN user_id SET NOT NULL"); + $pdo->exec("ALTER TABLE user_has_notifications ALTER COLUMN user_id SET NOT NULL"); +} + +function version_53(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN notifications_filter INTEGER DEFAULT 4"); +} + +function version_52(PDO $pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('webhook_url', '')); + + $pdo->exec("DELETE FROM settings WHERE option='webhook_url_task_creation'"); + $pdo->exec("DELETE FROM settings WHERE option='webhook_url_task_modification'"); +} + +function version_51(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN token VARCHAR(255) DEFAULT ''"); +} + +function version_50(PDO $pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('calendar_user_subtasks_time_tracking', 0)); + $rq->execute(array('calendar_user_tasks', 'date_started')); + $rq->execute(array('calendar_project_tasks', 'date_started')); + + $pdo->exec("DELETE FROM settings WHERE option='subtask_forecast'"); +} + +function version_49(PDO $pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('integration_jabber', '0')); + $rq->execute(array('integration_jabber_server', '')); + $rq->execute(array('integration_jabber_domain', '')); + $rq->execute(array('integration_jabber_username', '')); + $rq->execute(array('integration_jabber_password', '')); + $rq->execute(array('integration_jabber_nickname', 'kanboard')); + $rq->execute(array('integration_jabber_room', '')); + + $pdo->exec("ALTER TABLE project_integrations ADD COLUMN jabber INTEGER DEFAULT '0'"); + $pdo->exec("ALTER TABLE project_integrations ADD COLUMN jabber_server VARCHAR(255) DEFAULT ''"); + $pdo->exec("ALTER TABLE project_integrations ADD COLUMN jabber_domain VARCHAR(255) DEFAULT ''"); + $pdo->exec("ALTER TABLE project_integrations ADD COLUMN jabber_username VARCHAR(255) DEFAULT ''"); + $pdo->exec("ALTER TABLE project_integrations ADD COLUMN jabber_password VARCHAR(255) DEFAULT ''"); + $pdo->exec("ALTER TABLE project_integrations ADD COLUMN jabber_nickname VARCHAR(255) DEFAULT 'kanboard'"); + $pdo->exec("ALTER TABLE project_integrations ADD COLUMN jabber_room VARCHAR(255) DEFAULT ''"); +} + +function version_48(PDO $pdo) +{ + $pdo->exec('ALTER TABLE tasks ADD COLUMN recurrence_status INTEGER NOT NULL DEFAULT 0'); + $pdo->exec('ALTER TABLE tasks ADD COLUMN recurrence_trigger INTEGER NOT NULL DEFAULT 0'); + $pdo->exec('ALTER TABLE tasks ADD COLUMN recurrence_factor INTEGER NOT NULL DEFAULT 0'); + $pdo->exec('ALTER TABLE tasks ADD COLUMN recurrence_timeframe INTEGER NOT NULL DEFAULT 0'); + $pdo->exec('ALTER TABLE tasks ADD COLUMN recurrence_basedate INTEGER NOT NULL DEFAULT 0'); + $pdo->exec('ALTER TABLE tasks ADD COLUMN recurrence_parent INTEGER'); + $pdo->exec('ALTER TABLE tasks ADD COLUMN recurrence_child INTEGER'); +} + +function version_47(PDO $pdo) +{ + $pdo->exec("ALTER TABLE projects ADD COLUMN identifier VARCHAR(50) DEFAULT ''"); +} + +function version_46(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_integrations ( + id SERIAL PRIMARY KEY, + project_id INTEGER NOT NULL UNIQUE, + hipchat BOOLEAN DEFAULT '0', + hipchat_api_url VARCHAR(255) DEFAULT 'https://api.hipchat.com', + hipchat_room_id VARCHAR(255), + hipchat_room_token VARCHAR(255), + slack BOOLEAN DEFAULT '0', + slack_webhook_url VARCHAR(255), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + ) + "); +} + +function version_45(PDO $pdo) +{ + $pdo->exec('ALTER TABLE project_daily_summaries ADD COLUMN score INTEGER NOT NULL DEFAULT 0'); +} + +function version_44(PDO $pdo) +{ + $pdo->exec('ALTER TABLE project_has_categories ADD COLUMN description TEXT'); +} + +function version_43(PDO $pdo) +{ + $pdo->exec('ALTER TABLE files ADD COLUMN "date" INTEGER NOT NULL DEFAULT 0'); + $pdo->exec('ALTER TABLE files ADD COLUMN "user_id" INTEGER NOT NULL DEFAULT 0'); + $pdo->exec('ALTER TABLE files ADD COLUMN "size" INTEGER NOT NULL DEFAULT 0'); +} + +function version_42(PDO $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 $pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('integration_gravatar', '0')); +} + +function version_40(PDO $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 $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) +{ + $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) +{ + $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 $pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('subtask_forecast', '0')); +} + +function version_35(PDO $pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('application_stylesheet', '')); +} + +function version_34(PDO $pdo) +{ + $pdo->exec("ALTER TABLE subtask_time_tracking ADD COLUMN time_spent REAL DEFAULT 0"); +} + +function version_30(PDO $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) +{ + $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) +{ + $pdo->exec('ALTER TABLE projects ADD COLUMN description TEXT'); +} + +function version_27(PDO $pdo) +{ + $pdo->exec('CREATE TABLE links ( + "id" SERIAL PRIMARY KEY, + "label" VARCHAR(255) NOT NULL, + "opposite_id" INTEGER DEFAULT 0, + UNIQUE("label") + )'); + + $pdo->exec("CREATE TABLE task_has_links ( + id SERIAL PRIMARY KEY, + link_id INTEGER NOT NULL, + task_id INTEGER NOT NULL, + opposite_task_id INTEGER NOT NULL, + FOREIGN KEY(link_id) REFERENCES links(id) ON DELETE CASCADE, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE, + FOREIGN KEY(opposite_task_id) REFERENCES tasks(id) ON DELETE CASCADE + )"); + + $pdo->exec("CREATE INDEX task_has_links_task_index ON task_has_links(task_id)"); + $pdo->exec("CREATE UNIQUE INDEX task_has_links_unique ON task_has_links(link_id, task_id, opposite_task_id)"); + + $rq = $pdo->prepare('INSERT INTO links (label, opposite_id) VALUES (?, ?)'); + $rq->execute(array('relates to', 0)); + $rq->execute(array('blocks', 3)); + $rq->execute(array('is blocked by', 2)); + $rq->execute(array('duplicates', 5)); + $rq->execute(array('is duplicated by', 4)); + $rq->execute(array('is a child of', 7)); + $rq->execute(array('is a parent of', 6)); + $rq->execute(array('targets milestone', 9)); + $rq->execute(array('is a milestone of', 8)); + $rq->execute(array('fixes', 11)); + $rq->execute(array('is fixed by', 10)); +} + +function version_26(PDO $pdo) +{ + $pdo->exec('ALTER TABLE tasks ADD COLUMN date_moved INT DEFAULT 0'); + + /* Update tasks.date_moved from project_activities table if tasks.date_moved = null or 0. + * We take max project_activities.date_creation where event_name in task.create','task.move.column + * since creation date is always less than task moves + */ + $pdo->exec("UPDATE tasks + SET date_moved = ( + SELECT md + FROM ( + SELECT task_id, max(date_creation) md + FROM project_activities + WHERE event_name IN ('task.create', 'task.move.column') + GROUP BY task_id + ) src + WHERE id = src.task_id + ) + WHERE (date_moved IS NULL OR date_moved = 0) AND id IN ( + SELECT task_id + FROM ( + SELECT task_id, max(date_creation) md + FROM project_activities + WHERE event_name IN ('task.create', 'task.move.column') + GROUP BY task_id + ) src + )"); + + // If there is no activities for some tasks use the date_creation + $pdo->exec("UPDATE tasks SET date_moved = date_creation WHERE date_moved IS NULL OR date_moved = 0"); +} + +function version_25(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN disable_login_form BOOLEAN DEFAULT '0'"); +} + +function version_24(PDO $pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('subtask_restriction', '0')); + $rq->execute(array('subtask_time_tracking', '0')); + + $pdo->exec(' + CREATE TABLE subtask_time_tracking ( + id SERIAL PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "subtask_id" INTEGER NOT NULL, + "start" INTEGER DEFAULT 0, + "end" INTEGER DEFAULT 0, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(subtask_id) REFERENCES task_has_subtasks(id) ON DELETE CASCADE + ) + '); +} + +function version_23(PDO $pdo) +{ + $pdo->exec('ALTER TABLE columns ADD COLUMN description TEXT'); +} + +function version_22(PDO $pdo) +{ + $pdo->exec('ALTER TABLE users ADD COLUMN timezone VARCHAR(50)'); + $pdo->exec('ALTER TABLE users ADD COLUMN language CHAR(5)'); +} + +function version_21(PDO $pdo) +{ + // Avoid some full table scans + $pdo->exec('CREATE INDEX users_admin_idx ON users(is_admin)'); + $pdo->exec('CREATE INDEX columns_project_idx ON columns(project_id)'); + $pdo->exec('CREATE INDEX tasks_project_idx ON tasks(project_id)'); + $pdo->exec('CREATE INDEX swimlanes_project_idx ON swimlanes(project_id)'); + $pdo->exec('CREATE INDEX categories_project_idx ON project_has_categories(project_id)'); + $pdo->exec('CREATE INDEX subtasks_task_idx ON task_has_subtasks(task_id)'); + $pdo->exec('CREATE INDEX files_task_idx ON task_has_files(task_id)'); + $pdo->exec('CREATE INDEX comments_task_idx ON comments(task_id)'); + + // Set the ownership for all private projects + $rq = $pdo->prepare("SELECT id FROM projects WHERE is_private='1'"); + $rq->execute(); + $project_ids = $rq->fetchAll(PDO::FETCH_COLUMN, 0); + + $rq = $pdo->prepare("UPDATE project_has_users SET is_owner='1' WHERE project_id=?"); + + foreach ($project_ids as $project_id) { + $rq->execute(array($project_id)); + } +} + +function version_20(PDO $pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('project_categories', '')); +} + +function version_19(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE swimlanes ( + id SERIAL PRIMARY KEY, + name VARCHAR(200) NOT NULL, + position INTEGER DEFAULT 1, + is_active BOOLEAN DEFAULT '1', + project_id INTEGER, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + UNIQUE (name, project_id) + ) + "); + + $pdo->exec('ALTER TABLE tasks ADD COLUMN swimlane_id INTEGER DEFAULT 0'); + $pdo->exec("ALTER TABLE projects ADD COLUMN default_swimlane VARCHAR(200) DEFAULT 'Default swimlane'"); + $pdo->exec("ALTER TABLE projects ADD COLUMN show_default_swimlane BOOLEAN DEFAULT '1'"); +} + +function version_18(PDO $pdo) +{ + $pdo->exec("ALTER TABLE project_has_users ADD COLUMN is_owner BOOLEAN DEFAULT '0'"); +} + +function version_17(PDO $pdo) +{ + $pdo->exec('ALTER TABLE tasks ALTER COLUMN title SET NOT NULL'); +} + +function version_16(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_daily_summaries ( + id SERIAL PRIMARY KEY, + day CHAR(10) NOT NULL, + project_id INTEGER NOT NULL, + column_id INTEGER NOT NULL, + total INTEGER NOT NULL DEFAULT 0, + FOREIGN KEY(column_id) REFERENCES columns(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + ) + "); + + $pdo->exec('CREATE UNIQUE INDEX project_daily_column_stats_idx ON project_daily_summaries(day, project_id, column_id)'); +} + +function version_15(PDO $pdo) +{ + $pdo->exec("ALTER TABLE projects ADD COLUMN is_everybody_allowed BOOLEAN DEFAULT '0'"); +} + +function version_14(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_activities ( + id SERIAL PRIMARY KEY, + date_creation INTEGER NOT NULL, + event_name VARCHAR(50) NOT NULL, + creator_id INTEGER, + project_id INTEGER, + task_id INTEGER, + data TEXT, + FOREIGN KEY(creator_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('DROP TABLE task_has_events'); + $pdo->exec('DROP TABLE comment_has_events'); + $pdo->exec('DROP TABLE subtask_has_events'); +} + +function version_13(PDO $pdo) +{ + $pdo->exec("ALTER TABLE tasks ADD COLUMN date_started INTEGER"); + $pdo->exec("ALTER TABLE tasks ADD COLUMN time_spent FLOAT DEFAULT 0"); + $pdo->exec("ALTER TABLE tasks ADD COLUMN time_estimated FLOAT DEFAULT 0"); + + $pdo->exec("ALTER TABLE task_has_subtasks ALTER COLUMN time_estimated TYPE FLOAT"); + $pdo->exec("ALTER TABLE task_has_subtasks ALTER COLUMN time_spent TYPE FLOAT"); +} + +function version_12(PDO $pdo) +{ + $pdo->exec("ALTER TABLE projects ADD COLUMN is_private BOOLEAN DEFAULT '0'"); +} + +function version_11(PDO $pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('application_date_format', 'm/d/Y')); +} + +function version_10(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE settings ( + option VARCHAR(100) PRIMARY KEY, + value VARCHAR(255) DEFAULT '' + ) + "); + + // Migrate old config parameters + $rq = $pdo->prepare('SELECT * FROM config'); + $rq->execute(); + $parameters = $rq->fetch(PDO::FETCH_ASSOC); + + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('board_highlight_period', defined('RECENT_TASK_PERIOD') ? RECENT_TASK_PERIOD : 48*60*60)); + $rq->execute(array('board_public_refresh_interval', defined('BOARD_PUBLIC_CHECK_INTERVAL') ? BOARD_PUBLIC_CHECK_INTERVAL : 60)); + $rq->execute(array('board_private_refresh_interval', defined('BOARD_CHECK_INTERVAL') ? BOARD_CHECK_INTERVAL : 10)); + $rq->execute(array('board_columns', $parameters['default_columns'])); + $rq->execute(array('webhook_url_task_creation', $parameters['webhooks_url_task_creation'])); + $rq->execute(array('webhook_url_task_modification', $parameters['webhooks_url_task_modification'])); + $rq->execute(array('webhook_token', $parameters['webhooks_token'])); + $rq->execute(array('api_token', $parameters['api_token'])); + $rq->execute(array('application_language', $parameters['language'])); + $rq->execute(array('application_timezone', $parameters['timezone'])); + $rq->execute(array('application_url', defined('KANBOARD_URL') ? KANBOARD_URL : '')); + + $pdo->exec('DROP TABLE config'); +} + +function version_9(PDO $pdo) +{ + $pdo->exec("ALTER TABLE tasks ADD COLUMN reference VARCHAR(50) DEFAULT ''"); + $pdo->exec("ALTER TABLE comments ADD COLUMN reference VARCHAR(50) DEFAULT ''"); + + $pdo->exec('CREATE INDEX tasks_reference_idx ON tasks(reference)'); + $pdo->exec('CREATE INDEX comments_reference_idx ON comments(reference)'); +} + +function version_8(PDO $pdo) +{ + $pdo->exec('CREATE UNIQUE INDEX users_username_idx ON users(username)'); +} + +function version_7(PDO $pdo) +{ + $pdo->exec("ALTER TABLE config ADD COLUMN default_columns VARCHAR(255) DEFAULT ''"); +} + +function version_6(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE task_has_events ( + id SERIAL PRIMARY KEY, + date_creation INTEGER NOT NULL, + event_name VARCHAR(50) NOT NULL, + creator_id INTEGER, + project_id INTEGER, + task_id INTEGER, + data TEXT, + FOREIGN KEY(creator_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 TABLE subtask_has_events ( + id SERIAL PRIMARY KEY, + date_creation INTEGER NOT NULL, + event_name VARCHAR(50) NOT NULL, + creator_id INTEGER, + project_id INTEGER, + subtask_id INTEGER, + task_id INTEGER, + data TEXT, + FOREIGN KEY(creator_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(subtask_id) REFERENCES task_has_subtasks(id) ON DELETE CASCADE, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + ); + "); + + $pdo->exec(" + CREATE TABLE comment_has_events ( + id SERIAL PRIMARY KEY, + date_creation INTEGER NOT NULL, + event_name VARCHAR(50) NOT NULL, + creator_id INTEGER, + project_id INTEGER, + comment_id INTEGER, + task_id INTEGER, + data TEXT, + FOREIGN KEY(creator_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(comment_id) REFERENCES comments(id) ON DELETE CASCADE, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + ); + "); +} + +function version_5(PDO $pdo) +{ + $pdo->exec("ALTER TABLE projects ADD COLUMN is_public BOOLEAN DEFAULT '0'"); +} + +function version_4(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN notifications_enabled BOOLEAN DEFAULT '0'"); + + $pdo->exec(" + CREATE TABLE user_has_notifications ( + user_id INTEGER, + project_id INTEGER, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + UNIQUE(project_id, user_id) + ); + "); +} + +function version_3(PDO $pdo) +{ + $pdo->exec("ALTER TABLE config ADD COLUMN webhooks_url_task_modification VARCHAR(255)"); + $pdo->exec("ALTER TABLE config ADD COLUMN webhooks_url_task_creation VARCHAR(255)"); +} + +function version_2(PDO $pdo) +{ + $pdo->exec("ALTER TABLE tasks ADD COLUMN creator_id INTEGER DEFAULT 0"); + $pdo->exec("ALTER TABLE tasks ADD COLUMN date_modification INTEGER DEFAULT 0"); +} + +function version_1(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE config ( + language CHAR(5) DEFAULT 'en_US', + webhooks_token VARCHAR(255) DEFAULT '', + timezone VARCHAR(50) DEFAULT 'UTC', + api_token VARCHAR(255) DEFAULT '' + ); + + CREATE TABLE users ( + id SERIAL PRIMARY KEY, + username VARCHAR(50), + password VARCHAR(255), + is_admin BOOLEAN DEFAULT '0', + default_project_id INTEGER DEFAULT 0, + is_ldap_user BOOLEAN DEFAULT '0', + name VARCHAR(255), + email VARCHAR(255), + google_id VARCHAR(255), + github_id VARCHAR(30) + ); + + CREATE TABLE remember_me ( + id SERIAL PRIMARY KEY, + user_id INTEGER, + ip VARCHAR(45), + user_agent VARCHAR(255), + token VARCHAR(255), + sequence VARCHAR(255), + expiration INTEGER, + date_creation INTEGER, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + ); + + CREATE TABLE last_logins ( + id SERIAL PRIMARY KEY, + auth_type VARCHAR(25), + user_id INTEGER, + ip VARCHAR(45), + user_agent VARCHAR(255), + date_creation INTEGER, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + ); + + CREATE TABLE projects ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) UNIQUE, + is_active BOOLEAN DEFAULT '1', + token VARCHAR(255), + last_modified INTEGER DEFAULT 0 + ); + + CREATE TABLE project_has_users ( + id SERIAL PRIMARY KEY, + project_id INTEGER, + user_id INTEGER, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + UNIQUE(project_id, user_id) + ); + + CREATE TABLE project_has_categories ( + id SERIAL PRIMARY KEY, + name VARCHAR(255), + project_id INTEGER, + UNIQUE (project_id, name), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + ); + + CREATE TABLE columns ( + id SERIAL PRIMARY KEY, + title VARCHAR(255), + position INTEGER, + project_id INTEGER, + task_limit INTEGER DEFAULT 0, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + UNIQUE (title, project_id) + ); + + CREATE TABLE tasks ( + id SERIAL PRIMARY KEY, + title VARCHAR(255), + description TEXT, + date_creation INTEGER, + color_id VARCHAR(255), + project_id INTEGER, + column_id INTEGER, + owner_id INTEGER DEFAULT 0, + position INTEGER, + is_active BOOLEAN DEFAULT '1', + date_completed INTEGER, + score INTEGER, + date_due INTEGER, + category_id INTEGER DEFAULT 0, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(column_id) REFERENCES columns(id) ON DELETE CASCADE + ); + + CREATE TABLE task_has_subtasks ( + id SERIAL PRIMARY KEY, + title VARCHAR(255), + status SMALLINT DEFAULT 0, + time_estimated INTEGER DEFAULT 0, + time_spent INTEGER DEFAULT 0, + task_id INTEGER NOT NULL, + user_id INTEGER, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + ); + + CREATE TABLE task_has_files ( + id SERIAL PRIMARY KEY, + name VARCHAR(255), + path VARCHAR(255), + is_image BOOLEAN DEFAULT '0', + task_id INTEGER, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + ); + + CREATE TABLE comments ( + id SERIAL PRIMARY KEY, + task_id INTEGER, + user_id INTEGER, + date INTEGER, + comment TEXT, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + ); + + CREATE TABLE actions ( + id SERIAL PRIMARY KEY, + project_id INTEGER, + event_name VARCHAR(50), + action_name VARCHAR(50), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + ); + + CREATE TABLE action_has_params ( + id SERIAL PRIMARY KEY, + action_id INTEGER, + name VARCHAR(50), + value VARCHAR(50), + FOREIGN KEY(action_id) REFERENCES actions(id) ON DELETE CASCADE + ); + "); + + $pdo->exec(" + INSERT INTO users + (username, password, is_admin) + VALUES ('admin', '".\password_hash('admin', PASSWORD_BCRYPT)."', '1') + "); + + $pdo->exec(" + INSERT INTO config + (webhooks_token, api_token) + VALUES ('".Token::getToken()."', '".Token::getToken()."') + "); +} diff --git a/app/Schema/Sql/mysql.sql b/app/Schema/Sql/mysql.sql new file mode 100644 index 0000000..1c46ed5 --- /dev/null +++ b/app/Schema/Sql/mysql.sql @@ -0,0 +1,811 @@ + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; + SET NAMES utf8mb4 ; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; +DROP TABLE IF EXISTS `action_has_params`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `action_has_params` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `action_id` int(11) NOT NULL, + `name` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + `value` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`id`), + KEY `action_id` (`action_id`), + CONSTRAINT `action_has_params_ibfk_1` FOREIGN KEY (`action_id`) REFERENCES `actions` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `actions`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `actions` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `project_id` int(11) NOT NULL, + `event_name` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + `action_name` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`id`), + KEY `project_id` (`project_id`), + CONSTRAINT `actions_ibfk_1` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `column_has_move_restrictions`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `column_has_move_restrictions` ( + `restriction_id` int(11) NOT NULL AUTO_INCREMENT, + `project_id` int(11) NOT NULL, + `role_id` int(11) NOT NULL, + `src_column_id` int(11) NOT NULL, + `dst_column_id` int(11) NOT NULL, + `only_assigned` tinyint(1) DEFAULT '0', + PRIMARY KEY (`restriction_id`), + UNIQUE KEY `role_id` (`role_id`,`src_column_id`,`dst_column_id`), + KEY `project_id` (`project_id`), + KEY `src_column_id` (`src_column_id`), + KEY `dst_column_id` (`dst_column_id`), + CONSTRAINT `column_has_move_restrictions_ibfk_1` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE, + CONSTRAINT `column_has_move_restrictions_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `project_has_roles` (`role_id`) ON DELETE CASCADE, + CONSTRAINT `column_has_move_restrictions_ibfk_3` FOREIGN KEY (`src_column_id`) REFERENCES `columns` (`id`) ON DELETE CASCADE, + CONSTRAINT `column_has_move_restrictions_ibfk_4` FOREIGN KEY (`dst_column_id`) REFERENCES `columns` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `column_has_restrictions`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `column_has_restrictions` ( + `restriction_id` int(11) NOT NULL AUTO_INCREMENT, + `project_id` int(11) NOT NULL, + `role_id` int(11) NOT NULL, + `column_id` int(11) NOT NULL, + `rule` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`restriction_id`), + UNIQUE KEY `role_id` (`role_id`,`column_id`,`rule`), + KEY `project_id` (`project_id`), + KEY `column_id` (`column_id`), + CONSTRAINT `column_has_restrictions_ibfk_1` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE, + CONSTRAINT `column_has_restrictions_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `project_has_roles` (`role_id`) ON DELETE CASCADE, + CONSTRAINT `column_has_restrictions_ibfk_3` FOREIGN KEY (`column_id`) REFERENCES `columns` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `columns`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `columns` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `title` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, + `position` int(11) NOT NULL, + `project_id` int(11) NOT NULL, + `task_limit` int(11) DEFAULT '0', + `description` mediumtext COLLATE utf8mb4_unicode_ci, + `hide_in_dashboard` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `idx_title_project` (`title`,`project_id`), + KEY `columns_project_idx` (`project_id`), + CONSTRAINT `columns_ibfk_1` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `comments`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `comments` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `task_id` int(11) NOT NULL, + `user_id` int(11) DEFAULT '0', + `date_creation` bigint(20) DEFAULT NULL, + `comment` mediumtext COLLATE utf8mb4_unicode_ci, + `reference` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT '', + `date_modification` bigint(20) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `user_id` (`user_id`), + KEY `comments_reference_idx` (`reference`), + KEY `comments_task_idx` (`task_id`), + CONSTRAINT `comments_ibfk_1` FOREIGN KEY (`task_id`) REFERENCES `tasks` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `currencies`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `currencies` ( + `currency` char(3) COLLATE utf8mb4_unicode_ci NOT NULL, + `rate` float DEFAULT '0', + UNIQUE KEY `currency` (`currency`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `custom_filters`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `custom_filters` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `filter` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + `project_id` int(11) NOT NULL, + `user_id` int(11) NOT NULL, + `name` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + `is_shared` tinyint(1) DEFAULT '0', + `append` tinyint(1) DEFAULT '0', + PRIMARY KEY (`id`), + KEY `project_id` (`project_id`), + KEY `user_id` (`user_id`), + CONSTRAINT `custom_filters_ibfk_1` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE, + CONSTRAINT `custom_filters_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `group_has_users`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `group_has_users` ( + `group_id` int(11) NOT NULL, + `user_id` int(11) NOT NULL, + UNIQUE KEY `group_id` (`group_id`,`user_id`), + KEY `user_id` (`user_id`), + CONSTRAINT `group_has_users_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE, + CONSTRAINT `group_has_users_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `groups`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `groups` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `external_id` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT '', + `name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `invites`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `invites` ( + `email` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, + `project_id` int(11) NOT NULL, + `token` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`email`,`token`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `last_logins`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `last_logins` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `auth_type` varchar(25) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `user_id` int(11) DEFAULT NULL, + `ip` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `user_agent` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `date_creation` bigint(20) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `user_id` (`user_id`), + CONSTRAINT `last_logins_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `links`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `links` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `label` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, + `opposite_id` int(11) DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `label` (`label`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `password_reset`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `password_reset` ( + `token` varchar(80) COLLATE utf8mb4_unicode_ci NOT NULL, + `user_id` int(11) NOT NULL, + `date_expiration` int(11) NOT NULL, + `date_creation` int(11) NOT NULL, + `ip` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL, + `user_agent` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `is_active` tinyint(1) NOT NULL, + PRIMARY KEY (`token`), + KEY `user_id` (`user_id`), + CONSTRAINT `password_reset_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `plugin_schema_versions`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `plugin_schema_versions` ( + `plugin` varchar(80) COLLATE utf8mb4_unicode_ci NOT NULL, + `version` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`plugin`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `predefined_task_descriptions`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `predefined_task_descriptions` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `project_id` int(11) NOT NULL, + `title` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + `description` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`id`), + KEY `project_id` (`project_id`), + CONSTRAINT `predefined_task_descriptions_ibfk_1` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `project_activities`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `project_activities` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `date_creation` bigint(20) DEFAULT NULL, + `event_name` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + `creator_id` int(11) DEFAULT NULL, + `project_id` int(11) DEFAULT NULL, + `task_id` int(11) DEFAULT NULL, + `data` mediumtext COLLATE utf8mb4_unicode_ci, + PRIMARY KEY (`id`), + KEY `creator_id` (`creator_id`), + KEY `project_id` (`project_id`), + KEY `task_id` (`task_id`), + CONSTRAINT `project_activities_ibfk_1` FOREIGN KEY (`creator_id`) REFERENCES `users` (`id`) ON DELETE CASCADE, + CONSTRAINT `project_activities_ibfk_2` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE, + CONSTRAINT `project_activities_ibfk_3` FOREIGN KEY (`task_id`) REFERENCES `tasks` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `project_daily_column_stats`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `project_daily_column_stats` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `day` char(10) COLLATE utf8mb4_unicode_ci NOT NULL, + `project_id` int(11) NOT NULL, + `column_id` int(11) NOT NULL, + `total` int(11) NOT NULL DEFAULT '0', + `score` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `project_daily_column_stats_idx` (`day`,`project_id`,`column_id`), + KEY `column_id` (`column_id`), + KEY `project_id` (`project_id`), + CONSTRAINT `project_daily_column_stats_ibfk_1` FOREIGN KEY (`column_id`) REFERENCES `columns` (`id`) ON DELETE CASCADE, + CONSTRAINT `project_daily_column_stats_ibfk_2` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `project_daily_stats`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `project_daily_stats` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `day` char(10) COLLATE utf8mb4_unicode_ci NOT NULL, + `project_id` int(11) NOT NULL, + `avg_lead_time` int(11) NOT NULL DEFAULT '0', + `avg_cycle_time` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `project_daily_stats_idx` (`day`,`project_id`), + KEY `project_id` (`project_id`), + CONSTRAINT `project_daily_stats_ibfk_1` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `project_has_categories`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `project_has_categories` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, + `project_id` int(11) NOT NULL, + `description` mediumtext COLLATE utf8mb4_unicode_ci, + `color_id` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `idx_project_category` (`project_id`,`name`), + KEY `categories_project_idx` (`project_id`), + CONSTRAINT `project_has_categories_ibfk_1` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `project_has_files`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `project_has_files` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `project_id` int(11) NOT NULL, + `name` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + `path` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + `is_image` tinyint(1) DEFAULT '0', + `size` int(11) NOT NULL DEFAULT '0', + `user_id` int(11) NOT NULL DEFAULT '0', + `date` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `project_id` (`project_id`), + CONSTRAINT `project_has_files_ibfk_1` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `project_has_groups`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `project_has_groups` ( + `group_id` int(11) NOT NULL, + `project_id` int(11) NOT NULL, + `role` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + UNIQUE KEY `group_id` (`group_id`,`project_id`), + KEY `project_id` (`project_id`), + CONSTRAINT `project_has_groups_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE, + CONSTRAINT `project_has_groups_ibfk_2` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `project_has_metadata`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `project_has_metadata` ( + `project_id` int(11) NOT NULL, + `name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + `value` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT '', + `changed_by` int(11) NOT NULL DEFAULT '0', + `changed_on` int(11) NOT NULL DEFAULT '0', + UNIQUE KEY `project_id` (`project_id`,`name`), + CONSTRAINT `project_has_metadata_ibfk_1` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `project_has_notification_types`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `project_has_notification_types` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `project_id` int(11) NOT NULL, + `notification_type` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `project_id` (`project_id`,`notification_type`), + CONSTRAINT `project_has_notification_types_ibfk_1` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `project_has_roles`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `project_has_roles` ( + `role_id` int(11) NOT NULL AUTO_INCREMENT, + `role` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, + `project_id` int(11) NOT NULL, + PRIMARY KEY (`role_id`), + UNIQUE KEY `project_id` (`project_id`,`role`), + CONSTRAINT `project_has_roles_ibfk_1` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `project_has_users`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `project_has_users` ( + `project_id` int(11) NOT NULL, + `user_id` int(11) NOT NULL, + `role` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + UNIQUE KEY `idx_project_user` (`project_id`,`user_id`), + KEY `user_id` (`user_id`), + CONSTRAINT `project_has_users_ibfk_1` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE, + CONSTRAINT `project_has_users_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `project_role_has_restrictions`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `project_role_has_restrictions` ( + `restriction_id` int(11) NOT NULL AUTO_INCREMENT, + `project_id` int(11) NOT NULL, + `role_id` int(11) NOT NULL, + `rule` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`restriction_id`), + UNIQUE KEY `role_id` (`role_id`,`rule`), + KEY `project_id` (`project_id`), + CONSTRAINT `project_role_has_restrictions_ibfk_1` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE, + CONSTRAINT `project_role_has_restrictions_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `project_has_roles` (`role_id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `projects`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `projects` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + `is_active` tinyint(4) DEFAULT '1', + `token` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `last_modified` bigint(20) DEFAULT NULL, + `is_public` tinyint(1) DEFAULT '0', + `is_private` tinyint(1) DEFAULT '0', + `description` mediumtext COLLATE utf8mb4_unicode_ci, + `identifier` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT '', + `start_date` varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT '', + `end_date` varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT '', + `owner_id` int(11) DEFAULT '0', + `priority_default` int(11) DEFAULT '0', + `priority_start` int(11) DEFAULT '0', + `priority_end` int(11) DEFAULT '3', + `email` mediumtext COLLATE utf8mb4_unicode_ci, + `predefined_email_subjects` mediumtext COLLATE utf8mb4_unicode_ci, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `remember_me`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `remember_me` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` int(11) DEFAULT NULL, + `ip` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `user_agent` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `token` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `sequence` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `expiration` int(11) DEFAULT NULL, + `date_creation` bigint(20) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `user_id` (`user_id`), + CONSTRAINT `remember_me_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `schema_version`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `schema_version` ( + `version` int(11) DEFAULT '0' +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `sessions`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `sessions` ( + `id` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, + `expire_at` int(11) NOT NULL, + `data` longtext COLLATE utf8mb4_unicode_ci, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `settings`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `settings` ( + `option` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, + `value` mediumtext COLLATE utf8mb4_unicode_ci, + `changed_by` int(11) NOT NULL DEFAULT '0', + `changed_on` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`option`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `subtask_time_tracking`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `subtask_time_tracking` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` int(11) NOT NULL, + `subtask_id` int(11) NOT NULL, + `start` bigint(20) DEFAULT NULL, + `end` bigint(20) DEFAULT NULL, + `time_spent` float DEFAULT '0', + PRIMARY KEY (`id`), + KEY `user_id` (`user_id`), + KEY `subtask_id` (`subtask_id`), + CONSTRAINT `subtask_time_tracking_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE, + CONSTRAINT `subtask_time_tracking_ibfk_2` FOREIGN KEY (`subtask_id`) REFERENCES `subtasks` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `subtasks`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `subtasks` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `title` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + `status` int(11) DEFAULT '0', + `time_estimated` float DEFAULT NULL, + `time_spent` float DEFAULT NULL, + `task_id` int(11) NOT NULL, + `user_id` int(11) DEFAULT NULL, + `position` int(11) DEFAULT '1', + PRIMARY KEY (`id`), + KEY `subtasks_task_idx` (`task_id`), + CONSTRAINT `subtasks_ibfk_1` FOREIGN KEY (`task_id`) REFERENCES `tasks` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `swimlanes`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `swimlanes` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, + `position` int(11) DEFAULT '1', + `is_active` int(11) DEFAULT '1', + `project_id` int(11) DEFAULT NULL, + `description` mediumtext COLLATE utf8mb4_unicode_ci, + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`,`project_id`), + KEY `swimlanes_project_idx` (`project_id`), + CONSTRAINT `swimlanes_ibfk_1` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `tags`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `tags` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, + `project_id` int(11) NOT NULL, + `color_id` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `project_id` (`project_id`,`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `task_has_external_links`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `task_has_external_links` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `link_type` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, + `dependency` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, + `title` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + `url` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + `date_creation` int(11) NOT NULL, + `date_modification` int(11) NOT NULL, + `task_id` int(11) NOT NULL, + `creator_id` int(11) DEFAULT '0', + PRIMARY KEY (`id`), + KEY `task_id` (`task_id`), + CONSTRAINT `task_has_external_links_ibfk_1` FOREIGN KEY (`task_id`) REFERENCES `tasks` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `task_has_files`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `task_has_files` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + `path` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + `is_image` tinyint(1) DEFAULT '0', + `task_id` int(11) NOT NULL, + `date` bigint(20) DEFAULT NULL, + `user_id` int(11) NOT NULL DEFAULT '0', + `size` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `files_task_idx` (`task_id`), + CONSTRAINT `task_has_files_ibfk_1` FOREIGN KEY (`task_id`) REFERENCES `tasks` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `task_has_links`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `task_has_links` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `link_id` int(11) NOT NULL, + `task_id` int(11) NOT NULL, + `opposite_task_id` int(11) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `task_has_links_unique` (`link_id`,`task_id`,`opposite_task_id`), + KEY `opposite_task_id` (`opposite_task_id`), + KEY `task_has_links_task_index` (`task_id`), + CONSTRAINT `task_has_links_ibfk_1` FOREIGN KEY (`link_id`) REFERENCES `links` (`id`) ON DELETE CASCADE, + CONSTRAINT `task_has_links_ibfk_2` FOREIGN KEY (`task_id`) REFERENCES `tasks` (`id`) ON DELETE CASCADE, + CONSTRAINT `task_has_links_ibfk_3` FOREIGN KEY (`opposite_task_id`) REFERENCES `tasks` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `task_has_metadata`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `task_has_metadata` ( + `task_id` int(11) NOT NULL, + `name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + `value` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT '', + `changed_by` int(11) NOT NULL DEFAULT '0', + `changed_on` int(11) NOT NULL DEFAULT '0', + UNIQUE KEY `task_id` (`task_id`,`name`), + CONSTRAINT `task_has_metadata_ibfk_1` FOREIGN KEY (`task_id`) REFERENCES `tasks` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `task_has_tags`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `task_has_tags` ( + `task_id` int(11) NOT NULL, + `tag_id` int(11) NOT NULL, + UNIQUE KEY `tag_id` (`tag_id`,`task_id`), + KEY `task_id` (`task_id`), + CONSTRAINT `task_has_tags_ibfk_1` FOREIGN KEY (`task_id`) REFERENCES `tasks` (`id`) ON DELETE CASCADE, + CONSTRAINT `task_has_tags_ibfk_2` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `tasks`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `tasks` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `title` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + `description` mediumtext COLLATE utf8mb4_unicode_ci, + `date_creation` bigint(20) DEFAULT NULL, + `date_completed` bigint(20) DEFAULT NULL, + `date_due` bigint(20) DEFAULT NULL, + `color_id` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `project_id` int(11) NOT NULL, + `column_id` int(11) NOT NULL, + `owner_id` int(11) DEFAULT '0', + `position` int(11) DEFAULT NULL, + `score` int(11) DEFAULT NULL, + `is_active` tinyint(4) DEFAULT '1', + `category_id` int(11) DEFAULT '0', + `creator_id` int(11) DEFAULT '0', + `date_modification` int(11) DEFAULT '0', + `reference` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT '', + `date_started` bigint(20) DEFAULT NULL, + `time_spent` float DEFAULT '0', + `time_estimated` float DEFAULT '0', + `swimlane_id` int(11) NOT NULL, + `date_moved` bigint(20) DEFAULT NULL, + `recurrence_status` int(11) NOT NULL DEFAULT '0', + `recurrence_trigger` int(11) NOT NULL DEFAULT '0', + `recurrence_factor` int(11) NOT NULL DEFAULT '0', + `recurrence_timeframe` int(11) NOT NULL DEFAULT '0', + `recurrence_basedate` int(11) NOT NULL DEFAULT '0', + `recurrence_parent` int(11) DEFAULT NULL, + `recurrence_child` int(11) DEFAULT NULL, + `priority` int(11) DEFAULT '0', + `external_provider` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `external_uri` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_task_active` (`is_active`), + KEY `column_id` (`column_id`), + KEY `tasks_reference_idx` (`reference`), + KEY `tasks_project_idx` (`project_id`), + KEY `tasks_swimlane_ibfk_1` (`swimlane_id`), + CONSTRAINT `tasks_ibfk_1` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE, + CONSTRAINT `tasks_ibfk_2` FOREIGN KEY (`column_id`) REFERENCES `columns` (`id`) ON DELETE CASCADE, + CONSTRAINT `tasks_swimlane_ibfk_1` FOREIGN KEY (`swimlane_id`) REFERENCES `swimlanes` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `transitions`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `transitions` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` int(11) NOT NULL, + `project_id` int(11) NOT NULL, + `task_id` int(11) NOT NULL, + `src_column_id` int(11) NOT NULL, + `dst_column_id` int(11) NOT NULL, + `date` bigint(20) DEFAULT NULL, + `time_spent` int(11) DEFAULT '0', + PRIMARY KEY (`id`), + KEY `src_column_id` (`src_column_id`), + KEY `dst_column_id` (`dst_column_id`), + KEY `transitions_task_index` (`task_id`), + KEY `transitions_project_index` (`project_id`), + KEY `transitions_user_index` (`user_id`), + CONSTRAINT `transitions_ibfk_1` FOREIGN KEY (`src_column_id`) REFERENCES `columns` (`id`) ON DELETE CASCADE, + CONSTRAINT `transitions_ibfk_2` FOREIGN KEY (`dst_column_id`) REFERENCES `columns` (`id`) ON DELETE CASCADE, + CONSTRAINT `transitions_ibfk_3` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE, + CONSTRAINT `transitions_ibfk_4` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE, + CONSTRAINT `transitions_ibfk_5` FOREIGN KEY (`task_id`) REFERENCES `tasks` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `user_has_metadata`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `user_has_metadata` ( + `user_id` int(11) NOT NULL, + `name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + `value` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT '', + `changed_by` int(11) NOT NULL DEFAULT '0', + `changed_on` int(11) NOT NULL DEFAULT '0', + UNIQUE KEY `user_id` (`user_id`,`name`), + CONSTRAINT `user_has_metadata_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `user_has_notification_types`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `user_has_notification_types` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` int(11) NOT NULL, + `notification_type` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `user_has_notification_types_user_idx` (`user_id`,`notification_type`), + CONSTRAINT `user_has_notification_types_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `user_has_notifications`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `user_has_notifications` ( + `user_id` int(11) NOT NULL, + `project_id` int(11) NOT NULL, + UNIQUE KEY `user_has_notifications_unique_idx` (`user_id`,`project_id`), + KEY `user_has_notifications_ibfk_2` (`project_id`), + CONSTRAINT `user_has_notifications_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE, + CONSTRAINT `user_has_notifications_ibfk_2` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `user_has_unread_notifications`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `user_has_unread_notifications` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` int(11) NOT NULL, + `date_creation` bigint(20) NOT NULL, + `event_name` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + `event_data` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`id`), + KEY `user_id` (`user_id`), + CONSTRAINT `user_has_unread_notifications_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `users`; +/*!40101 SET @saved_cs_client = @@character_set_client */; + SET character_set_client = utf8mb4 ; +CREATE TABLE `users` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `username` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, + `password` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `is_ldap_user` tinyint(1) DEFAULT '0', + `name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `email` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `google_id` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `github_id` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `notifications_enabled` tinyint(1) DEFAULT '0', + `timezone` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `language` varchar(11) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `disable_login_form` tinyint(1) DEFAULT '0', + `twofactor_activated` tinyint(1) DEFAULT '0', + `twofactor_secret` char(16) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `token` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT '', + `notifications_filter` int(11) DEFAULT '4', + `nb_failed_login` int(11) DEFAULT '0', + `lock_expiration_date` bigint(20) DEFAULT NULL, + `gitlab_id` int(11) DEFAULT NULL, + `role` varchar(25) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'app-user', + `is_active` tinyint(1) DEFAULT '1', + `avatar_path` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `api_access_token` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `filter` mediumtext COLLATE utf8mb4_unicode_ci, + PRIMARY KEY (`id`), + UNIQUE KEY `users_username_idx` (`username`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +LOCK TABLES `settings` WRITE; +/*!40000 ALTER TABLE `settings` DISABLE KEYS */; +INSERT INTO `settings` VALUES ('api_token','0fde96ab43568a586f9e4ab95e6a38e7a955dcc6124a073f023e951a4197',0,0),('application_currency','USD',0,0),('application_date_format','m/d/Y',0,0),('application_language','en_US',0,0),('application_stylesheet','',0,0),('application_timezone','UTC',0,0),('application_url','',0,0),('board_columns','',0,0),('board_highlight_period','172800',0,0),('board_private_refresh_interval','10',0,0),('board_public_refresh_interval','60',0,0),('calendar_project_tasks','date_started',0,0),('calendar_user_subtasks_time_tracking','0',0,0),('calendar_user_tasks','date_started',0,0),('cfd_include_closed_tasks','1',0,0),('default_color','yellow',0,0),('integration_gravatar','0',0,0),('password_reset','1',0,0),('project_categories','',0,0),('subtask_restriction','0',0,0),('subtask_time_tracking','1',0,0),('webhook_token','b652b0d2d3e086025f8c3b6797f571b2139ea7bbfdca534f680ba7e41a32',0,0),('webhook_url','',0,0); +/*!40000 ALTER TABLE `settings` ENABLE KEYS */; +UNLOCK TABLES; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +LOCK TABLES `links` WRITE; +/*!40000 ALTER TABLE `links` DISABLE KEYS */; +INSERT INTO `links` VALUES (1,'relates to',0),(2,'blocks',3),(3,'is blocked by',2),(4,'duplicates',5),(5,'is duplicated by',4),(6,'is a child of',7),(7,'is a parent of',6),(8,'targets milestone',9),(9,'is a milestone of',8),(10,'fixes',11),(11,'is fixed by',10); +/*!40000 ALTER TABLE `links` ENABLE KEYS */; +UNLOCK TABLES; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +INSERT INTO users (username, password, role) VALUES ('admin', '$2y$10$GzDCeQl/GdH.pCZfz4fWdO3qmayutRCmxEIY9U9t1k9q9F89VNDCm', 'app-admin'); +INSERT INTO schema_version VALUES ('133'); diff --git a/app/Schema/Sql/postgres.sql b/app/Schema/Sql/postgres.sql new file mode 100644 index 0000000..cd28237 --- /dev/null +++ b/app/Schema/Sql/postgres.sql @@ -0,0 +1,2751 @@ +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 10.5 +-- Dumped by pg_dump version 10.5 + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: SCHEMA "public"; Type: COMMENT; Schema: -; Owner: - +-- + +COMMENT ON SCHEMA "public" IS 'standard public schema'; + + +SET default_tablespace = ''; + +SET default_with_oids = false; + +-- +-- Name: action_has_params; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."action_has_params" ( + "id" integer NOT NULL, + "action_id" integer NOT NULL, + "name" "text" NOT NULL, + "value" "text" NOT NULL +); + + +-- +-- Name: action_has_params_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."action_has_params_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: action_has_params_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."action_has_params_id_seq" OWNED BY "public"."action_has_params"."id"; + + +-- +-- Name: actions; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."actions" ( + "id" integer NOT NULL, + "project_id" integer NOT NULL, + "event_name" "text" NOT NULL, + "action_name" "text" NOT NULL +); + + +-- +-- Name: actions_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."actions_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: actions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."actions_id_seq" OWNED BY "public"."actions"."id"; + + +-- +-- Name: column_has_move_restrictions; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."column_has_move_restrictions" ( + "restriction_id" integer NOT NULL, + "project_id" integer NOT NULL, + "role_id" integer NOT NULL, + "src_column_id" integer NOT NULL, + "dst_column_id" integer NOT NULL, + "only_assigned" boolean DEFAULT false +); + + +-- +-- Name: column_has_move_restrictions_restriction_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."column_has_move_restrictions_restriction_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: column_has_move_restrictions_restriction_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."column_has_move_restrictions_restriction_id_seq" OWNED BY "public"."column_has_move_restrictions"."restriction_id"; + + +-- +-- Name: column_has_restrictions; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."column_has_restrictions" ( + "restriction_id" integer NOT NULL, + "project_id" integer NOT NULL, + "role_id" integer NOT NULL, + "column_id" integer NOT NULL, + "rule" character varying(255) NOT NULL +); + + +-- +-- Name: column_has_restrictions_restriction_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."column_has_restrictions_restriction_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: column_has_restrictions_restriction_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."column_has_restrictions_restriction_id_seq" OWNED BY "public"."column_has_restrictions"."restriction_id"; + + +-- +-- Name: columns; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."columns" ( + "id" integer NOT NULL, + "title" character varying(255) NOT NULL, + "position" integer, + "project_id" integer NOT NULL, + "task_limit" integer DEFAULT 0, + "description" "text", + "hide_in_dashboard" boolean DEFAULT false +); + + +-- +-- Name: columns_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."columns_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: columns_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."columns_id_seq" OWNED BY "public"."columns"."id"; + + +-- +-- Name: comments; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."comments" ( + "id" integer NOT NULL, + "task_id" integer NOT NULL, + "user_id" integer DEFAULT 0, + "date_creation" bigint NOT NULL, + "comment" "text", + "reference" "text" DEFAULT ''::character varying, + "date_modification" bigint +); + + +-- +-- Name: comments_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."comments_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: comments_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."comments_id_seq" OWNED BY "public"."comments"."id"; + + +-- +-- Name: currencies; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."currencies" ( + "currency" character(3) NOT NULL, + "rate" real DEFAULT 0 +); + + +-- +-- Name: custom_filters; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."custom_filters" ( + "id" integer NOT NULL, + "filter" "text" NOT NULL, + "project_id" integer NOT NULL, + "user_id" integer NOT NULL, + "name" "text" NOT NULL, + "is_shared" boolean DEFAULT false, + "append" boolean DEFAULT false +); + + +-- +-- Name: custom_filters_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."custom_filters_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: custom_filters_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."custom_filters_id_seq" OWNED BY "public"."custom_filters"."id"; + + +-- +-- Name: group_has_users; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."group_has_users" ( + "group_id" integer NOT NULL, + "user_id" integer NOT NULL +); + + +-- +-- Name: groups; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."groups" ( + "id" integer NOT NULL, + "external_id" character varying(255) DEFAULT ''::character varying, + "name" "text" NOT NULL +); + + +-- +-- Name: groups_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."groups_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: groups_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."groups_id_seq" OWNED BY "public"."groups"."id"; + + +-- +-- Name: invites; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."invites" ( + "email" character varying(255) NOT NULL, + "project_id" integer NOT NULL, + "token" character varying(255) NOT NULL +); + + +-- +-- Name: last_logins; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."last_logins" ( + "id" integer NOT NULL, + "auth_type" character varying(25), + "user_id" integer, + "ip" character varying(45), + "user_agent" character varying(255), + "date_creation" bigint +); + + +-- +-- Name: last_logins_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."last_logins_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: last_logins_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."last_logins_id_seq" OWNED BY "public"."last_logins"."id"; + + +-- +-- Name: links; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."links" ( + "id" integer NOT NULL, + "label" character varying(255) NOT NULL, + "opposite_id" integer DEFAULT 0 +); + + +-- +-- Name: links_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."links_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: links_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."links_id_seq" OWNED BY "public"."links"."id"; + + +-- +-- Name: password_reset; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."password_reset" ( + "token" character varying(80) NOT NULL, + "user_id" integer NOT NULL, + "date_expiration" integer NOT NULL, + "date_creation" integer NOT NULL, + "ip" character varying(45) NOT NULL, + "user_agent" character varying(255) NOT NULL, + "is_active" boolean NOT NULL +); + + +-- +-- Name: plugin_schema_versions; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."plugin_schema_versions" ( + "plugin" character varying(80) NOT NULL, + "version" integer DEFAULT 0 NOT NULL +); + + +-- +-- Name: predefined_task_descriptions; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."predefined_task_descriptions" ( + "id" integer NOT NULL, + "project_id" integer NOT NULL, + "title" "text" NOT NULL, + "description" "text" NOT NULL +); + + +-- +-- Name: predefined_task_descriptions_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."predefined_task_descriptions_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: predefined_task_descriptions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."predefined_task_descriptions_id_seq" OWNED BY "public"."predefined_task_descriptions"."id"; + + +-- +-- Name: project_activities; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."project_activities" ( + "id" integer NOT NULL, + "date_creation" bigint NOT NULL, + "event_name" "text" NOT NULL, + "creator_id" integer, + "project_id" integer, + "task_id" integer, + "data" "text" +); + + +-- +-- Name: project_activities_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."project_activities_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: project_activities_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."project_activities_id_seq" OWNED BY "public"."project_activities"."id"; + + +-- +-- Name: project_daily_column_stats; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."project_daily_column_stats" ( + "id" integer NOT NULL, + "day" character(10) NOT NULL, + "project_id" integer NOT NULL, + "column_id" integer NOT NULL, + "total" integer DEFAULT 0 NOT NULL, + "score" integer DEFAULT 0 NOT NULL +); + + +-- +-- Name: project_daily_stats; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."project_daily_stats" ( + "id" integer NOT NULL, + "day" character(10) NOT NULL, + "project_id" integer NOT NULL, + "avg_lead_time" integer DEFAULT 0 NOT NULL, + "avg_cycle_time" integer DEFAULT 0 NOT NULL +); + + +-- +-- Name: project_daily_stats_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."project_daily_stats_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: project_daily_stats_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."project_daily_stats_id_seq" OWNED BY "public"."project_daily_stats"."id"; + + +-- +-- Name: project_daily_summaries_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."project_daily_summaries_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: project_daily_summaries_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."project_daily_summaries_id_seq" OWNED BY "public"."project_daily_column_stats"."id"; + + +-- +-- Name: project_has_categories; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."project_has_categories" ( + "id" integer NOT NULL, + "name" character varying(255) NOT NULL, + "project_id" integer NOT NULL, + "description" "text", + "color_id" character varying(50) DEFAULT NULL::character varying +); + + +-- +-- Name: project_has_categories_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."project_has_categories_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: project_has_categories_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."project_has_categories_id_seq" OWNED BY "public"."project_has_categories"."id"; + + +-- +-- Name: project_has_files; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."project_has_files" ( + "id" integer NOT NULL, + "project_id" integer NOT NULL, + "name" "text" NOT NULL, + "path" "text" NOT NULL, + "is_image" boolean DEFAULT false, + "size" integer DEFAULT 0 NOT NULL, + "user_id" integer DEFAULT 0 NOT NULL, + "date" integer DEFAULT 0 NOT NULL +); + + +-- +-- Name: project_has_files_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."project_has_files_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: project_has_files_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."project_has_files_id_seq" OWNED BY "public"."project_has_files"."id"; + + +-- +-- Name: project_has_groups; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."project_has_groups" ( + "group_id" integer NOT NULL, + "project_id" integer NOT NULL, + "role" character varying(255) NOT NULL +); + + +-- +-- Name: project_has_metadata; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."project_has_metadata" ( + "project_id" integer NOT NULL, + "name" character varying(50) NOT NULL, + "value" character varying(255) DEFAULT ''::character varying, + "changed_by" integer DEFAULT 0 NOT NULL, + "changed_on" integer DEFAULT 0 NOT NULL +); + + +-- +-- Name: project_has_notification_types; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."project_has_notification_types" ( + "id" integer NOT NULL, + "project_id" integer NOT NULL, + "notification_type" character varying(50) NOT NULL +); + + +-- +-- Name: project_has_notification_types_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."project_has_notification_types_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: project_has_notification_types_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."project_has_notification_types_id_seq" OWNED BY "public"."project_has_notification_types"."id"; + + +-- +-- Name: project_has_roles; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."project_has_roles" ( + "role_id" integer NOT NULL, + "role" character varying(255) NOT NULL, + "project_id" integer NOT NULL +); + + +-- +-- Name: project_has_roles_role_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."project_has_roles_role_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: project_has_roles_role_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."project_has_roles_role_id_seq" OWNED BY "public"."project_has_roles"."role_id"; + + +-- +-- Name: project_has_users; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."project_has_users" ( + "project_id" integer NOT NULL, + "user_id" integer NOT NULL, + "role" character varying(255) DEFAULT 'project-viewer'::character varying NOT NULL +); + + +-- +-- Name: project_role_has_restrictions; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."project_role_has_restrictions" ( + "restriction_id" integer NOT NULL, + "project_id" integer NOT NULL, + "role_id" integer NOT NULL, + "rule" character varying(255) NOT NULL +); + + +-- +-- Name: project_role_has_restrictions_restriction_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."project_role_has_restrictions_restriction_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: project_role_has_restrictions_restriction_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."project_role_has_restrictions_restriction_id_seq" OWNED BY "public"."project_role_has_restrictions"."restriction_id"; + + +-- +-- Name: projects; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."projects" ( + "id" integer NOT NULL, + "name" "text" NOT NULL, + "is_active" boolean DEFAULT true, + "token" character varying(255), + "last_modified" bigint DEFAULT 0, + "is_public" boolean DEFAULT false, + "is_private" boolean DEFAULT false, + "description" "text", + "identifier" character varying(50) DEFAULT ''::character varying, + "start_date" character varying(10) DEFAULT ''::character varying, + "end_date" character varying(10) DEFAULT ''::character varying, + "owner_id" integer DEFAULT 0, + "priority_default" integer DEFAULT 0, + "priority_start" integer DEFAULT 0, + "priority_end" integer DEFAULT 3, + "email" "text", + "predefined_email_subjects" "text" +); + + +-- +-- Name: projects_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."projects_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: projects_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."projects_id_seq" OWNED BY "public"."projects"."id"; + + +-- +-- Name: remember_me; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."remember_me" ( + "id" integer NOT NULL, + "user_id" integer, + "ip" character varying(45), + "user_agent" character varying(255), + "token" character varying(255), + "sequence" character varying(255), + "expiration" integer, + "date_creation" bigint +); + + +-- +-- Name: remember_me_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."remember_me_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: remember_me_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."remember_me_id_seq" OWNED BY "public"."remember_me"."id"; + + +-- +-- Name: schema_version; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."schema_version" ( + "version" integer DEFAULT 0 +); + + +-- +-- Name: sessions; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."sessions" ( + "id" "text" NOT NULL, + "expire_at" integer NOT NULL, + "data" "text" DEFAULT ''::"text" +); + + +-- +-- Name: settings; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."settings" ( + "option" character varying(100) NOT NULL, + "value" "text" DEFAULT ''::character varying, + "changed_by" integer DEFAULT 0 NOT NULL, + "changed_on" integer DEFAULT 0 NOT NULL +); + + +-- +-- Name: subtask_time_tracking; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."subtask_time_tracking" ( + "id" integer NOT NULL, + "user_id" integer NOT NULL, + "subtask_id" integer NOT NULL, + "start" bigint DEFAULT 0, + "end" bigint DEFAULT 0, + "time_spent" real DEFAULT 0 +); + + +-- +-- Name: subtask_time_tracking_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."subtask_time_tracking_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: subtask_time_tracking_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."subtask_time_tracking_id_seq" OWNED BY "public"."subtask_time_tracking"."id"; + + +-- +-- Name: subtasks; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."subtasks" ( + "id" integer NOT NULL, + "title" "text" NOT NULL, + "status" smallint DEFAULT 0, + "time_estimated" double precision DEFAULT 0, + "time_spent" double precision DEFAULT 0, + "task_id" integer NOT NULL, + "user_id" integer, + "position" integer DEFAULT 1 +); + + +-- +-- Name: swimlanes; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."swimlanes" ( + "id" integer NOT NULL, + "name" "text" NOT NULL, + "position" integer DEFAULT 1, + "is_active" boolean DEFAULT true, + "project_id" integer, + "description" "text" +); + + +-- +-- Name: swimlanes_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."swimlanes_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: swimlanes_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."swimlanes_id_seq" OWNED BY "public"."swimlanes"."id"; + + +-- +-- Name: tags; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."tags" ( + "id" integer NOT NULL, + "name" character varying(255) NOT NULL, + "project_id" integer NOT NULL, + "color_id" character varying(50) DEFAULT NULL::character varying +); + + +-- +-- Name: tags_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."tags_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: tags_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."tags_id_seq" OWNED BY "public"."tags"."id"; + + +-- +-- Name: task_has_external_links; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."task_has_external_links" ( + "id" integer NOT NULL, + "link_type" character varying(100) NOT NULL, + "dependency" character varying(100) NOT NULL, + "title" "text" NOT NULL, + "url" "text" NOT NULL, + "date_creation" integer NOT NULL, + "date_modification" integer NOT NULL, + "task_id" integer NOT NULL, + "creator_id" integer DEFAULT 0 +); + + +-- +-- Name: task_has_external_links_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."task_has_external_links_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: task_has_external_links_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."task_has_external_links_id_seq" OWNED BY "public"."task_has_external_links"."id"; + + +-- +-- Name: task_has_files; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."task_has_files" ( + "id" integer NOT NULL, + "name" "text" NOT NULL, + "path" "text", + "is_image" boolean DEFAULT false, + "task_id" integer NOT NULL, + "date" bigint DEFAULT 0 NOT NULL, + "user_id" integer DEFAULT 0 NOT NULL, + "size" integer DEFAULT 0 NOT NULL +); + + +-- +-- Name: task_has_files_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."task_has_files_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: task_has_files_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."task_has_files_id_seq" OWNED BY "public"."task_has_files"."id"; + + +-- +-- Name: task_has_links; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."task_has_links" ( + "id" integer NOT NULL, + "link_id" integer NOT NULL, + "task_id" integer NOT NULL, + "opposite_task_id" integer NOT NULL +); + + +-- +-- Name: task_has_links_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."task_has_links_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: task_has_links_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."task_has_links_id_seq" OWNED BY "public"."task_has_links"."id"; + + +-- +-- Name: task_has_metadata; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."task_has_metadata" ( + "task_id" integer NOT NULL, + "name" character varying(50) NOT NULL, + "value" character varying(255) DEFAULT ''::character varying, + "changed_by" integer DEFAULT 0 NOT NULL, + "changed_on" integer DEFAULT 0 NOT NULL +); + + +-- +-- Name: task_has_subtasks_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."task_has_subtasks_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: task_has_subtasks_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."task_has_subtasks_id_seq" OWNED BY "public"."subtasks"."id"; + + +-- +-- Name: task_has_tags; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."task_has_tags" ( + "task_id" integer NOT NULL, + "tag_id" integer NOT NULL +); + + +-- +-- Name: tasks; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."tasks" ( + "id" integer NOT NULL, + "title" "text" NOT NULL, + "description" "text", + "date_creation" bigint, + "color_id" character varying(255), + "project_id" integer NOT NULL, + "column_id" integer NOT NULL, + "owner_id" integer DEFAULT 0, + "position" integer, + "is_active" boolean DEFAULT true, + "date_completed" bigint, + "score" integer, + "date_due" bigint, + "category_id" integer DEFAULT 0, + "creator_id" integer DEFAULT 0, + "date_modification" integer DEFAULT 0, + "reference" "text" DEFAULT ''::character varying, + "date_started" bigint, + "time_spent" double precision DEFAULT 0, + "time_estimated" double precision DEFAULT 0, + "swimlane_id" integer NOT NULL, + "date_moved" bigint DEFAULT 0, + "recurrence_status" integer DEFAULT 0 NOT NULL, + "recurrence_trigger" integer DEFAULT 0 NOT NULL, + "recurrence_factor" integer DEFAULT 0 NOT NULL, + "recurrence_timeframe" integer DEFAULT 0 NOT NULL, + "recurrence_basedate" integer DEFAULT 0 NOT NULL, + "recurrence_parent" integer, + "recurrence_child" integer, + "priority" integer DEFAULT 0, + "external_provider" character varying(255), + "external_uri" character varying(255) +); + + +-- +-- Name: tasks_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."tasks_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: tasks_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."tasks_id_seq" OWNED BY "public"."tasks"."id"; + + +-- +-- Name: transitions; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."transitions" ( + "id" integer NOT NULL, + "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" bigint NOT NULL, + "time_spent" integer DEFAULT 0 +); + + +-- +-- Name: transitions_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."transitions_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: transitions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."transitions_id_seq" OWNED BY "public"."transitions"."id"; + + +-- +-- Name: user_has_metadata; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."user_has_metadata" ( + "user_id" integer NOT NULL, + "name" character varying(50) NOT NULL, + "value" character varying(255) DEFAULT ''::character varying, + "changed_by" integer DEFAULT 0 NOT NULL, + "changed_on" integer DEFAULT 0 NOT NULL +); + + +-- +-- Name: user_has_notification_types; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."user_has_notification_types" ( + "id" integer NOT NULL, + "user_id" integer NOT NULL, + "notification_type" character varying(50) +); + + +-- +-- Name: user_has_notification_types_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."user_has_notification_types_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: user_has_notification_types_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."user_has_notification_types_id_seq" OWNED BY "public"."user_has_notification_types"."id"; + + +-- +-- Name: user_has_notifications; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."user_has_notifications" ( + "user_id" integer NOT NULL, + "project_id" integer +); + + +-- +-- Name: user_has_unread_notifications; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."user_has_unread_notifications" ( + "id" integer NOT NULL, + "user_id" integer NOT NULL, + "date_creation" bigint NOT NULL, + "event_name" "text" NOT NULL, + "event_data" "text" NOT NULL +); + + +-- +-- Name: user_has_unread_notifications_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."user_has_unread_notifications_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: user_has_unread_notifications_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."user_has_unread_notifications_id_seq" OWNED BY "public"."user_has_unread_notifications"."id"; + + +-- +-- Name: users; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "public"."users" ( + "id" integer NOT NULL, + "username" "text" NOT NULL, + "password" character varying(255), + "is_ldap_user" boolean DEFAULT false, + "name" character varying(255), + "email" character varying(255), + "google_id" character varying(255), + "github_id" character varying(30), + "notifications_enabled" boolean DEFAULT false, + "timezone" character varying(50), + "language" character varying(11), + "disable_login_form" boolean DEFAULT false, + "twofactor_activated" boolean DEFAULT false, + "twofactor_secret" character(16), + "token" character varying(255) DEFAULT ''::character varying, + "notifications_filter" integer DEFAULT 4, + "nb_failed_login" integer DEFAULT 0, + "lock_expiration_date" bigint DEFAULT 0, + "gitlab_id" integer, + "role" character varying(25) DEFAULT 'app-user'::character varying NOT NULL, + "is_active" boolean DEFAULT true, + "avatar_path" character varying(255), + "api_access_token" character varying(255) DEFAULT NULL::character varying, + "filter" "text" DEFAULT NULL::character varying +); + + +-- +-- Name: users_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "public"."users_id_seq" + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: users_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "public"."users_id_seq" OWNED BY "public"."users"."id"; + + +-- +-- Name: action_has_params id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."action_has_params" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."action_has_params_id_seq"'::"regclass"); + + +-- +-- Name: actions id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."actions" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."actions_id_seq"'::"regclass"); + + +-- +-- Name: column_has_move_restrictions restriction_id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."column_has_move_restrictions" ALTER COLUMN "restriction_id" SET DEFAULT "nextval"('"public"."column_has_move_restrictions_restriction_id_seq"'::"regclass"); + + +-- +-- Name: column_has_restrictions restriction_id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."column_has_restrictions" ALTER COLUMN "restriction_id" SET DEFAULT "nextval"('"public"."column_has_restrictions_restriction_id_seq"'::"regclass"); + + +-- +-- Name: columns id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."columns" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."columns_id_seq"'::"regclass"); + + +-- +-- Name: comments id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."comments" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."comments_id_seq"'::"regclass"); + + +-- +-- Name: custom_filters id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."custom_filters" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."custom_filters_id_seq"'::"regclass"); + + +-- +-- Name: groups id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."groups" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."groups_id_seq"'::"regclass"); + + +-- +-- Name: last_logins id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."last_logins" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."last_logins_id_seq"'::"regclass"); + + +-- +-- Name: links id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."links" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."links_id_seq"'::"regclass"); + + +-- +-- Name: predefined_task_descriptions id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."predefined_task_descriptions" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."predefined_task_descriptions_id_seq"'::"regclass"); + + +-- +-- Name: project_activities id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_activities" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."project_activities_id_seq"'::"regclass"); + + +-- +-- Name: project_daily_column_stats id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_daily_column_stats" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."project_daily_summaries_id_seq"'::"regclass"); + + +-- +-- Name: project_daily_stats id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_daily_stats" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."project_daily_stats_id_seq"'::"regclass"); + + +-- +-- Name: project_has_categories id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_has_categories" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."project_has_categories_id_seq"'::"regclass"); + + +-- +-- Name: project_has_files id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_has_files" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."project_has_files_id_seq"'::"regclass"); + + +-- +-- Name: project_has_notification_types id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_has_notification_types" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."project_has_notification_types_id_seq"'::"regclass"); + + +-- +-- Name: project_has_roles role_id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_has_roles" ALTER COLUMN "role_id" SET DEFAULT "nextval"('"public"."project_has_roles_role_id_seq"'::"regclass"); + + +-- +-- Name: project_role_has_restrictions restriction_id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_role_has_restrictions" ALTER COLUMN "restriction_id" SET DEFAULT "nextval"('"public"."project_role_has_restrictions_restriction_id_seq"'::"regclass"); + + +-- +-- Name: projects id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."projects" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."projects_id_seq"'::"regclass"); + + +-- +-- Name: remember_me id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."remember_me" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."remember_me_id_seq"'::"regclass"); + + +-- +-- Name: subtask_time_tracking id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."subtask_time_tracking" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."subtask_time_tracking_id_seq"'::"regclass"); + + +-- +-- Name: subtasks id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."subtasks" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."task_has_subtasks_id_seq"'::"regclass"); + + +-- +-- Name: swimlanes id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."swimlanes" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."swimlanes_id_seq"'::"regclass"); + + +-- +-- Name: tags id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."tags" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."tags_id_seq"'::"regclass"); + + +-- +-- Name: task_has_external_links id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."task_has_external_links" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."task_has_external_links_id_seq"'::"regclass"); + + +-- +-- Name: task_has_files id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."task_has_files" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."task_has_files_id_seq"'::"regclass"); + + +-- +-- Name: task_has_links id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."task_has_links" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."task_has_links_id_seq"'::"regclass"); + + +-- +-- Name: tasks id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."tasks" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."tasks_id_seq"'::"regclass"); + + +-- +-- Name: transitions id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."transitions" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."transitions_id_seq"'::"regclass"); + + +-- +-- Name: user_has_notification_types id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."user_has_notification_types" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."user_has_notification_types_id_seq"'::"regclass"); + + +-- +-- Name: user_has_unread_notifications id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."user_has_unread_notifications" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."user_has_unread_notifications_id_seq"'::"regclass"); + + +-- +-- Name: users id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."users" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."users_id_seq"'::"regclass"); + + +-- +-- Name: action_has_params action_has_params_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."action_has_params" + ADD CONSTRAINT "action_has_params_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: actions actions_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."actions" + ADD CONSTRAINT "actions_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: column_has_move_restrictions column_has_move_restrictions_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."column_has_move_restrictions" + ADD CONSTRAINT "column_has_move_restrictions_pkey" PRIMARY KEY ("restriction_id"); + + +-- +-- Name: column_has_move_restrictions column_has_move_restrictions_role_id_src_column_id_dst_colu_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."column_has_move_restrictions" + ADD CONSTRAINT "column_has_move_restrictions_role_id_src_column_id_dst_colu_key" UNIQUE ("role_id", "src_column_id", "dst_column_id"); + + +-- +-- Name: column_has_restrictions column_has_restrictions_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."column_has_restrictions" + ADD CONSTRAINT "column_has_restrictions_pkey" PRIMARY KEY ("restriction_id"); + + +-- +-- Name: column_has_restrictions column_has_restrictions_role_id_column_id_rule_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."column_has_restrictions" + ADD CONSTRAINT "column_has_restrictions_role_id_column_id_rule_key" UNIQUE ("role_id", "column_id", "rule"); + + +-- +-- Name: columns columns_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."columns" + ADD CONSTRAINT "columns_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: columns columns_title_project_id_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."columns" + ADD CONSTRAINT "columns_title_project_id_key" UNIQUE ("title", "project_id"); + + +-- +-- Name: comments comments_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."comments" + ADD CONSTRAINT "comments_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: currencies currencies_currency_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."currencies" + ADD CONSTRAINT "currencies_currency_key" UNIQUE ("currency"); + + +-- +-- Name: custom_filters custom_filters_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."custom_filters" + ADD CONSTRAINT "custom_filters_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: group_has_users group_has_users_group_id_user_id_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."group_has_users" + ADD CONSTRAINT "group_has_users_group_id_user_id_key" UNIQUE ("group_id", "user_id"); + + +-- +-- Name: groups groups_name_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."groups" + ADD CONSTRAINT "groups_name_key" UNIQUE ("name"); + + +-- +-- Name: groups groups_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."groups" + ADD CONSTRAINT "groups_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: invites invites_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."invites" + ADD CONSTRAINT "invites_pkey" PRIMARY KEY ("email", "token"); + + +-- +-- Name: last_logins last_logins_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."last_logins" + ADD CONSTRAINT "last_logins_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: links links_label_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."links" + ADD CONSTRAINT "links_label_key" UNIQUE ("label"); + + +-- +-- Name: links links_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."links" + ADD CONSTRAINT "links_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: password_reset password_reset_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."password_reset" + ADD CONSTRAINT "password_reset_pkey" PRIMARY KEY ("token"); + + +-- +-- Name: plugin_schema_versions plugin_schema_versions_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."plugin_schema_versions" + ADD CONSTRAINT "plugin_schema_versions_pkey" PRIMARY KEY ("plugin"); + + +-- +-- Name: predefined_task_descriptions predefined_task_descriptions_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."predefined_task_descriptions" + ADD CONSTRAINT "predefined_task_descriptions_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: project_activities project_activities_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_activities" + ADD CONSTRAINT "project_activities_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: project_daily_stats project_daily_stats_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_daily_stats" + ADD CONSTRAINT "project_daily_stats_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: project_daily_column_stats project_daily_summaries_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_daily_column_stats" + ADD CONSTRAINT "project_daily_summaries_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: project_has_categories project_has_categories_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_has_categories" + ADD CONSTRAINT "project_has_categories_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: project_has_categories project_has_categories_project_id_name_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_has_categories" + ADD CONSTRAINT "project_has_categories_project_id_name_key" UNIQUE ("project_id", "name"); + + +-- +-- Name: project_has_files project_has_files_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_has_files" + ADD CONSTRAINT "project_has_files_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: project_has_groups project_has_groups_group_id_project_id_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_has_groups" + ADD CONSTRAINT "project_has_groups_group_id_project_id_key" UNIQUE ("group_id", "project_id"); + + +-- +-- Name: project_has_metadata project_has_metadata_project_id_name_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_has_metadata" + ADD CONSTRAINT "project_has_metadata_project_id_name_key" UNIQUE ("project_id", "name"); + + +-- +-- Name: project_has_notification_types project_has_notification_types_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_has_notification_types" + ADD CONSTRAINT "project_has_notification_types_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: project_has_notification_types project_has_notification_types_project_id_notification_type_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_has_notification_types" + ADD CONSTRAINT "project_has_notification_types_project_id_notification_type_key" UNIQUE ("project_id", "notification_type"); + + +-- +-- Name: project_has_roles project_has_roles_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_has_roles" + ADD CONSTRAINT "project_has_roles_pkey" PRIMARY KEY ("role_id"); + + +-- +-- Name: project_has_roles project_has_roles_project_id_role_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_has_roles" + ADD CONSTRAINT "project_has_roles_project_id_role_key" UNIQUE ("project_id", "role"); + + +-- +-- Name: project_has_users project_has_users_project_id_user_id_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_has_users" + ADD CONSTRAINT "project_has_users_project_id_user_id_key" UNIQUE ("project_id", "user_id"); + + +-- +-- Name: project_role_has_restrictions project_role_has_restrictions_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_role_has_restrictions" + ADD CONSTRAINT "project_role_has_restrictions_pkey" PRIMARY KEY ("restriction_id"); + + +-- +-- Name: project_role_has_restrictions project_role_has_restrictions_role_id_rule_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_role_has_restrictions" + ADD CONSTRAINT "project_role_has_restrictions_role_id_rule_key" UNIQUE ("role_id", "rule"); + + +-- +-- Name: projects projects_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."projects" + ADD CONSTRAINT "projects_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: remember_me remember_me_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."remember_me" + ADD CONSTRAINT "remember_me_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: sessions sessions_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."sessions" + ADD CONSTRAINT "sessions_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: settings settings_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."settings" + ADD CONSTRAINT "settings_pkey" PRIMARY KEY ("option"); + + +-- +-- Name: subtask_time_tracking subtask_time_tracking_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."subtask_time_tracking" + ADD CONSTRAINT "subtask_time_tracking_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: swimlanes swimlanes_name_project_id_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."swimlanes" + ADD CONSTRAINT "swimlanes_name_project_id_key" UNIQUE ("name", "project_id"); + + +-- +-- Name: swimlanes swimlanes_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."swimlanes" + ADD CONSTRAINT "swimlanes_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: tags tags_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."tags" + ADD CONSTRAINT "tags_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: tags tags_project_id_name_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."tags" + ADD CONSTRAINT "tags_project_id_name_key" UNIQUE ("project_id", "name"); + + +-- +-- Name: task_has_external_links task_has_external_links_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."task_has_external_links" + ADD CONSTRAINT "task_has_external_links_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: task_has_files task_has_files_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."task_has_files" + ADD CONSTRAINT "task_has_files_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: task_has_links task_has_links_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."task_has_links" + ADD CONSTRAINT "task_has_links_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: task_has_metadata task_has_metadata_task_id_name_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."task_has_metadata" + ADD CONSTRAINT "task_has_metadata_task_id_name_key" UNIQUE ("task_id", "name"); + + +-- +-- Name: subtasks task_has_subtasks_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."subtasks" + ADD CONSTRAINT "task_has_subtasks_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: task_has_tags task_has_tags_tag_id_task_id_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."task_has_tags" + ADD CONSTRAINT "task_has_tags_tag_id_task_id_key" UNIQUE ("tag_id", "task_id"); + + +-- +-- Name: tasks tasks_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."tasks" + ADD CONSTRAINT "tasks_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: transitions transitions_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."transitions" + ADD CONSTRAINT "transitions_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: user_has_metadata user_has_metadata_user_id_name_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."user_has_metadata" + ADD CONSTRAINT "user_has_metadata_user_id_name_key" UNIQUE ("user_id", "name"); + + +-- +-- Name: user_has_notification_types user_has_notification_types_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."user_has_notification_types" + ADD CONSTRAINT "user_has_notification_types_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: user_has_notifications user_has_notifications_project_id_user_id_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."user_has_notifications" + ADD CONSTRAINT "user_has_notifications_project_id_user_id_key" UNIQUE ("project_id", "user_id"); + + +-- +-- Name: user_has_unread_notifications user_has_unread_notifications_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."user_has_unread_notifications" + ADD CONSTRAINT "user_has_unread_notifications_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."users" + ADD CONSTRAINT "users_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: categories_project_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX "categories_project_idx" ON "public"."project_has_categories" USING "btree" ("project_id"); + + +-- +-- Name: columns_project_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX "columns_project_idx" ON "public"."columns" USING "btree" ("project_id"); + + +-- +-- Name: comments_reference_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX "comments_reference_idx" ON "public"."comments" USING "btree" ("reference"); + + +-- +-- Name: comments_task_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX "comments_task_idx" ON "public"."comments" USING "btree" ("task_id"); + + +-- +-- Name: files_task_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX "files_task_idx" ON "public"."task_has_files" USING "btree" ("task_id"); + + +-- +-- Name: project_daily_column_stats_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX "project_daily_column_stats_idx" ON "public"."project_daily_column_stats" USING "btree" ("day", "project_id", "column_id"); + + +-- +-- Name: project_daily_stats_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX "project_daily_stats_idx" ON "public"."project_daily_stats" USING "btree" ("day", "project_id"); + + +-- +-- Name: subtasks_task_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX "subtasks_task_idx" ON "public"."subtasks" USING "btree" ("task_id"); + + +-- +-- Name: swimlanes_project_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX "swimlanes_project_idx" ON "public"."swimlanes" USING "btree" ("project_id"); + + +-- +-- Name: task_has_links_task_index; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX "task_has_links_task_index" ON "public"."task_has_links" USING "btree" ("task_id"); + + +-- +-- Name: task_has_links_unique; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX "task_has_links_unique" ON "public"."task_has_links" USING "btree" ("link_id", "task_id", "opposite_task_id"); + + +-- +-- Name: tasks_project_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX "tasks_project_idx" ON "public"."tasks" USING "btree" ("project_id"); + + +-- +-- Name: tasks_reference_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX "tasks_reference_idx" ON "public"."tasks" USING "btree" ("reference"); + + +-- +-- Name: transitions_project_index; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX "transitions_project_index" ON "public"."transitions" USING "btree" ("project_id"); + + +-- +-- Name: transitions_task_index; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX "transitions_task_index" ON "public"."transitions" USING "btree" ("task_id"); + + +-- +-- Name: transitions_user_index; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX "transitions_user_index" ON "public"."transitions" USING "btree" ("user_id"); + + +-- +-- Name: user_has_notification_types_user_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX "user_has_notification_types_user_idx" ON "public"."user_has_notification_types" USING "btree" ("user_id", "notification_type"); + + +-- +-- Name: users_username_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX "users_username_idx" ON "public"."users" USING "btree" ("username"); + + +-- +-- Name: action_has_params action_has_params_action_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."action_has_params" + ADD CONSTRAINT "action_has_params_action_id_fkey" FOREIGN KEY ("action_id") REFERENCES "public"."actions"("id") ON DELETE CASCADE; + + +-- +-- Name: actions actions_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."actions" + ADD CONSTRAINT "actions_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE CASCADE; + + +-- +-- Name: column_has_move_restrictions column_has_move_restrictions_dst_column_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."column_has_move_restrictions" + ADD CONSTRAINT "column_has_move_restrictions_dst_column_id_fkey" FOREIGN KEY ("dst_column_id") REFERENCES "public"."columns"("id") ON DELETE CASCADE; + + +-- +-- Name: column_has_move_restrictions column_has_move_restrictions_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."column_has_move_restrictions" + ADD CONSTRAINT "column_has_move_restrictions_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE CASCADE; + + +-- +-- Name: column_has_move_restrictions column_has_move_restrictions_role_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."column_has_move_restrictions" + ADD CONSTRAINT "column_has_move_restrictions_role_id_fkey" FOREIGN KEY ("role_id") REFERENCES "public"."project_has_roles"("role_id") ON DELETE CASCADE; + + +-- +-- Name: column_has_move_restrictions column_has_move_restrictions_src_column_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."column_has_move_restrictions" + ADD CONSTRAINT "column_has_move_restrictions_src_column_id_fkey" FOREIGN KEY ("src_column_id") REFERENCES "public"."columns"("id") ON DELETE CASCADE; + + +-- +-- Name: column_has_restrictions column_has_restrictions_column_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."column_has_restrictions" + ADD CONSTRAINT "column_has_restrictions_column_id_fkey" FOREIGN KEY ("column_id") REFERENCES "public"."columns"("id") ON DELETE CASCADE; + + +-- +-- Name: column_has_restrictions column_has_restrictions_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."column_has_restrictions" + ADD CONSTRAINT "column_has_restrictions_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE CASCADE; + + +-- +-- Name: column_has_restrictions column_has_restrictions_role_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."column_has_restrictions" + ADD CONSTRAINT "column_has_restrictions_role_id_fkey" FOREIGN KEY ("role_id") REFERENCES "public"."project_has_roles"("role_id") ON DELETE CASCADE; + + +-- +-- Name: columns columns_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."columns" + ADD CONSTRAINT "columns_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE CASCADE; + + +-- +-- Name: comments comments_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."comments" + ADD CONSTRAINT "comments_task_id_fkey" FOREIGN KEY ("task_id") REFERENCES "public"."tasks"("id") ON DELETE CASCADE; + + +-- +-- Name: group_has_users group_has_users_group_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."group_has_users" + ADD CONSTRAINT "group_has_users_group_id_fkey" FOREIGN KEY ("group_id") REFERENCES "public"."groups"("id") ON DELETE CASCADE; + + +-- +-- Name: group_has_users group_has_users_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."group_has_users" + ADD CONSTRAINT "group_has_users_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE CASCADE; + + +-- +-- Name: last_logins last_logins_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."last_logins" + ADD CONSTRAINT "last_logins_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE CASCADE; + + +-- +-- Name: password_reset password_reset_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."password_reset" + ADD CONSTRAINT "password_reset_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE CASCADE; + + +-- +-- Name: predefined_task_descriptions predefined_task_descriptions_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."predefined_task_descriptions" + ADD CONSTRAINT "predefined_task_descriptions_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE CASCADE; + + +-- +-- Name: project_activities project_activities_creator_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_activities" + ADD CONSTRAINT "project_activities_creator_id_fkey" FOREIGN KEY ("creator_id") REFERENCES "public"."users"("id") ON DELETE CASCADE; + + +-- +-- Name: project_activities project_activities_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_activities" + ADD CONSTRAINT "project_activities_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE CASCADE; + + +-- +-- Name: project_activities project_activities_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_activities" + ADD CONSTRAINT "project_activities_task_id_fkey" FOREIGN KEY ("task_id") REFERENCES "public"."tasks"("id") ON DELETE CASCADE; + + +-- +-- Name: project_daily_stats project_daily_stats_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_daily_stats" + ADD CONSTRAINT "project_daily_stats_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE CASCADE; + + +-- +-- Name: project_daily_column_stats project_daily_summaries_column_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_daily_column_stats" + ADD CONSTRAINT "project_daily_summaries_column_id_fkey" FOREIGN KEY ("column_id") REFERENCES "public"."columns"("id") ON DELETE CASCADE; + + +-- +-- Name: project_daily_column_stats project_daily_summaries_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_daily_column_stats" + ADD CONSTRAINT "project_daily_summaries_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE CASCADE; + + +-- +-- Name: project_has_categories project_has_categories_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_has_categories" + ADD CONSTRAINT "project_has_categories_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE CASCADE; + + +-- +-- Name: project_has_files project_has_files_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_has_files" + ADD CONSTRAINT "project_has_files_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE CASCADE; + + +-- +-- Name: project_has_groups project_has_groups_group_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_has_groups" + ADD CONSTRAINT "project_has_groups_group_id_fkey" FOREIGN KEY ("group_id") REFERENCES "public"."groups"("id") ON DELETE CASCADE; + + +-- +-- Name: project_has_groups project_has_groups_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_has_groups" + ADD CONSTRAINT "project_has_groups_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE CASCADE; + + +-- +-- Name: project_has_metadata project_has_metadata_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_has_metadata" + ADD CONSTRAINT "project_has_metadata_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE CASCADE; + + +-- +-- Name: project_has_notification_types project_has_notification_types_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_has_notification_types" + ADD CONSTRAINT "project_has_notification_types_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE CASCADE; + + +-- +-- Name: project_has_roles project_has_roles_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_has_roles" + ADD CONSTRAINT "project_has_roles_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE CASCADE; + + +-- +-- Name: project_has_users project_has_users_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_has_users" + ADD CONSTRAINT "project_has_users_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE CASCADE; + + +-- +-- Name: project_has_users project_has_users_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_has_users" + ADD CONSTRAINT "project_has_users_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE CASCADE; + + +-- +-- Name: project_role_has_restrictions project_role_has_restrictions_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_role_has_restrictions" + ADD CONSTRAINT "project_role_has_restrictions_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE CASCADE; + + +-- +-- Name: project_role_has_restrictions project_role_has_restrictions_role_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."project_role_has_restrictions" + ADD CONSTRAINT "project_role_has_restrictions_role_id_fkey" FOREIGN KEY ("role_id") REFERENCES "public"."project_has_roles"("role_id") ON DELETE CASCADE; + + +-- +-- Name: remember_me remember_me_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."remember_me" + ADD CONSTRAINT "remember_me_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE CASCADE; + + +-- +-- Name: subtask_time_tracking subtask_time_tracking_subtask_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."subtask_time_tracking" + ADD CONSTRAINT "subtask_time_tracking_subtask_id_fkey" FOREIGN KEY ("subtask_id") REFERENCES "public"."subtasks"("id") ON DELETE CASCADE; + + +-- +-- Name: subtask_time_tracking subtask_time_tracking_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."subtask_time_tracking" + ADD CONSTRAINT "subtask_time_tracking_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE CASCADE; + + +-- +-- Name: swimlanes swimlanes_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."swimlanes" + ADD CONSTRAINT "swimlanes_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE CASCADE; + + +-- +-- Name: task_has_external_links task_has_external_links_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."task_has_external_links" + ADD CONSTRAINT "task_has_external_links_task_id_fkey" FOREIGN KEY ("task_id") REFERENCES "public"."tasks"("id") ON DELETE CASCADE; + + +-- +-- Name: task_has_files task_has_files_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."task_has_files" + ADD CONSTRAINT "task_has_files_task_id_fkey" FOREIGN KEY ("task_id") REFERENCES "public"."tasks"("id") ON DELETE CASCADE; + + +-- +-- Name: task_has_links task_has_links_link_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."task_has_links" + ADD CONSTRAINT "task_has_links_link_id_fkey" FOREIGN KEY ("link_id") REFERENCES "public"."links"("id") ON DELETE CASCADE; + + +-- +-- Name: task_has_links task_has_links_opposite_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."task_has_links" + ADD CONSTRAINT "task_has_links_opposite_task_id_fkey" FOREIGN KEY ("opposite_task_id") REFERENCES "public"."tasks"("id") ON DELETE CASCADE; + + +-- +-- Name: task_has_links task_has_links_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."task_has_links" + ADD CONSTRAINT "task_has_links_task_id_fkey" FOREIGN KEY ("task_id") REFERENCES "public"."tasks"("id") ON DELETE CASCADE; + + +-- +-- Name: task_has_metadata task_has_metadata_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."task_has_metadata" + ADD CONSTRAINT "task_has_metadata_task_id_fkey" FOREIGN KEY ("task_id") REFERENCES "public"."tasks"("id") ON DELETE CASCADE; + + +-- +-- Name: subtasks task_has_subtasks_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."subtasks" + ADD CONSTRAINT "task_has_subtasks_task_id_fkey" FOREIGN KEY ("task_id") REFERENCES "public"."tasks"("id") ON DELETE CASCADE; + + +-- +-- Name: task_has_tags task_has_tags_tag_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."task_has_tags" + ADD CONSTRAINT "task_has_tags_tag_id_fkey" FOREIGN KEY ("tag_id") REFERENCES "public"."tags"("id") ON DELETE CASCADE; + + +-- +-- Name: task_has_tags task_has_tags_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."task_has_tags" + ADD CONSTRAINT "task_has_tags_task_id_fkey" FOREIGN KEY ("task_id") REFERENCES "public"."tasks"("id") ON DELETE CASCADE; + + +-- +-- Name: tasks tasks_column_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."tasks" + ADD CONSTRAINT "tasks_column_id_fkey" FOREIGN KEY ("column_id") REFERENCES "public"."columns"("id") ON DELETE CASCADE; + + +-- +-- Name: tasks tasks_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."tasks" + ADD CONSTRAINT "tasks_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE CASCADE; + + +-- +-- Name: tasks tasks_swimlane_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."tasks" + ADD CONSTRAINT "tasks_swimlane_id_fkey" FOREIGN KEY ("swimlane_id") REFERENCES "public"."swimlanes"("id") ON DELETE CASCADE; + + +-- +-- Name: transitions transitions_dst_column_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."transitions" + ADD CONSTRAINT "transitions_dst_column_id_fkey" FOREIGN KEY ("dst_column_id") REFERENCES "public"."columns"("id") ON DELETE CASCADE; + + +-- +-- Name: transitions transitions_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."transitions" + ADD CONSTRAINT "transitions_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE CASCADE; + + +-- +-- Name: transitions transitions_src_column_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."transitions" + ADD CONSTRAINT "transitions_src_column_id_fkey" FOREIGN KEY ("src_column_id") REFERENCES "public"."columns"("id") ON DELETE CASCADE; + + +-- +-- Name: transitions transitions_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."transitions" + ADD CONSTRAINT "transitions_task_id_fkey" FOREIGN KEY ("task_id") REFERENCES "public"."tasks"("id") ON DELETE CASCADE; + + +-- +-- Name: transitions transitions_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."transitions" + ADD CONSTRAINT "transitions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE CASCADE; + + +-- +-- Name: user_has_metadata user_has_metadata_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."user_has_metadata" + ADD CONSTRAINT "user_has_metadata_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE CASCADE; + + +-- +-- Name: user_has_notification_types user_has_notification_types_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."user_has_notification_types" + ADD CONSTRAINT "user_has_notification_types_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE CASCADE; + + +-- +-- Name: user_has_notifications user_has_notifications_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."user_has_notifications" + ADD CONSTRAINT "user_has_notifications_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE CASCADE; + + +-- +-- Name: user_has_notifications user_has_notifications_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."user_has_notifications" + ADD CONSTRAINT "user_has_notifications_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE CASCADE; + + +-- +-- Name: user_has_unread_notifications user_has_unread_notifications_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "public"."user_has_unread_notifications" + ADD CONSTRAINT "user_has_unread_notifications_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE CASCADE; + + +-- +-- PostgreSQL database dump complete +-- + +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 10.5 +-- Dumped by pg_dump version 10.5 + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Data for Name: settings; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +INSERT INTO public.settings (option, value, changed_by, changed_on) VALUES ('board_highlight_period', '172800', 0, 0); +INSERT INTO public.settings (option, value, changed_by, changed_on) VALUES ('board_public_refresh_interval', '60', 0, 0); +INSERT INTO public.settings (option, value, changed_by, changed_on) VALUES ('board_private_refresh_interval', '10', 0, 0); +INSERT INTO public.settings (option, value, changed_by, changed_on) VALUES ('board_columns', '', 0, 0); +INSERT INTO public.settings (option, value, changed_by, changed_on) VALUES ('webhook_token', '1a9fe6b6651d4f17db363279ec08b6b44c8ee4f205d0c9527848a648436c', 0, 0); +INSERT INTO public.settings (option, value, changed_by, changed_on) VALUES ('api_token', '8e6d6c81e25529d4d83e8b30385922ce1a99af3908743e888aa87344f8f3', 0, 0); +INSERT INTO public.settings (option, value, changed_by, changed_on) VALUES ('application_language', 'en_US', 0, 0); +INSERT INTO public.settings (option, value, changed_by, changed_on) VALUES ('application_timezone', 'UTC', 0, 0); +INSERT INTO public.settings (option, value, changed_by, changed_on) VALUES ('application_url', '', 0, 0); +INSERT INTO public.settings (option, value, changed_by, changed_on) VALUES ('application_date_format', 'm/d/Y', 0, 0); +INSERT INTO public.settings (option, value, changed_by, changed_on) VALUES ('project_categories', '', 0, 0); +INSERT INTO public.settings (option, value, changed_by, changed_on) VALUES ('subtask_restriction', '0', 0, 0); +INSERT INTO public.settings (option, value, changed_by, changed_on) VALUES ('application_stylesheet', '', 0, 0); +INSERT INTO public.settings (option, value, changed_by, changed_on) VALUES ('application_currency', 'USD', 0, 0); +INSERT INTO public.settings (option, value, changed_by, changed_on) VALUES ('integration_gravatar', '0', 0, 0); +INSERT INTO public.settings (option, value, changed_by, changed_on) VALUES ('calendar_user_subtasks_time_tracking', '0', 0, 0); +INSERT INTO public.settings (option, value, changed_by, changed_on) VALUES ('calendar_user_tasks', 'date_started', 0, 0); +INSERT INTO public.settings (option, value, changed_by, changed_on) VALUES ('calendar_project_tasks', 'date_started', 0, 0); +INSERT INTO public.settings (option, value, changed_by, changed_on) VALUES ('webhook_url', '', 0, 0); +INSERT INTO public.settings (option, value, changed_by, changed_on) VALUES ('default_color', 'yellow', 0, 0); +INSERT INTO public.settings (option, value, changed_by, changed_on) VALUES ('subtask_time_tracking', '1', 0, 0); +INSERT INTO public.settings (option, value, changed_by, changed_on) VALUES ('cfd_include_closed_tasks', '1', 0, 0); +INSERT INTO public.settings (option, value, changed_by, changed_on) VALUES ('password_reset', '1', 0, 0); + + +-- +-- PostgreSQL database dump complete +-- + +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 10.5 +-- Dumped by pg_dump version 10.5 + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Data for Name: links; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +INSERT INTO public.links (id, label, opposite_id) VALUES (1, 'relates to', 0); +INSERT INTO public.links (id, label, opposite_id) VALUES (2, 'blocks', 3); +INSERT INTO public.links (id, label, opposite_id) VALUES (3, 'is blocked by', 2); +INSERT INTO public.links (id, label, opposite_id) VALUES (4, 'duplicates', 5); +INSERT INTO public.links (id, label, opposite_id) VALUES (5, 'is duplicated by', 4); +INSERT INTO public.links (id, label, opposite_id) VALUES (6, 'is a child of', 7); +INSERT INTO public.links (id, label, opposite_id) VALUES (7, 'is a parent of', 6); +INSERT INTO public.links (id, label, opposite_id) VALUES (8, 'targets milestone', 9); +INSERT INTO public.links (id, label, opposite_id) VALUES (9, 'is a milestone of', 8); +INSERT INTO public.links (id, label, opposite_id) VALUES (10, 'fixes', 11); +INSERT INTO public.links (id, label, opposite_id) VALUES (11, 'is fixed by', 10); + + +-- +-- Name: links_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.links_id_seq', 11, true); + + +-- +-- PostgreSQL database dump complete +-- + +INSERT INTO public.users (username, password, role) VALUES ('admin', '$2y$10$GzDCeQl/GdH.pCZfz4fWdO3qmayutRCmxEIY9U9t1k9q9F89VNDCm', 'app-admin'); +INSERT INTO public.schema_version VALUES ('111'); diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php new file mode 100644 index 0000000..6a89a5b --- /dev/null +++ b/app/Schema/Sqlite.php @@ -0,0 +1,1572 @@ +<?php + +namespace Schema; + +require_once __DIR__.'/Migration.php'; + +use Kanboard\Core\Security\Token; +use Kanboard\Core\Security\Role; +use PDO; + +const VERSION = 128; + +function version_128(PDO $pdo) +{ + $pdo->exec("ALTER TABLE comments ADD COLUMN visibility VARCHAR(25) NOT NULL DEFAULT '".Role::APP_USER."'"); +} + +function version_127(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN theme TEXT DEFAULT 'light' NOT NULL"); +} + +function version_126(PDO $pdo) +{ + $pdo->exec('ALTER TABLE subtask_time_tracking RENAME TO subtask_time_tracking_old'); + + $pdo->exec(' + CREATE TABLE subtask_time_tracking ( + id INTEGER PRIMARY KEY, + user_id INTEGER NOT NULL, + subtask_id INTEGER NOT NULL, + start INTEGER DEFAULT 0, + end INTEGER DEFAULT 0, + time_spent REAL DEFAULT 0, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(subtask_id) REFERENCES subtasks(id) ON DELETE CASCADE + ) + '); + + $pdo->exec('DROP INDEX subtasks_task_idx'); + $pdo->exec('CREATE INDEX subtasks_task_idx ON subtasks(task_id)'); + $pdo->exec('INSERT INTO subtask_time_tracking SELECT * FROM subtask_time_tracking_old'); + $pdo->exec('DROP TABLE subtask_time_tracking_old'); +} + +function version_125(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE tasks_new + ( + id INTEGER PRIMARY KEY, + title TEXT NOCASE NOT NULL, + description TEXT, + date_creation INTEGER, + color_id TEXT, + project_id INTEGER REFERENCES projects(id) ON DELETE CASCADE, + column_id INTEGER REFERENCES columns(id) ON DELETE CASCADE, + owner_id INTEGER DEFAULT '0', + position INTEGER, + is_active INTEGER DEFAULT 1, + date_completed INTEGER, + score INTEGER, + date_due INTEGER, + category_id INTEGER DEFAULT 0, + creator_id INTEGER DEFAULT '0', + date_modification INTEGER DEFAULT '0', + reference TEXT DEFAULT '', + date_started INTEGER, + time_spent NUMERIC DEFAULT 0, + time_estimated NUMERIC DEFAULT 0, + swimlane_id INTEGER REFERENCES swimlanes(id) ON DELETE CASCADE, + date_moved INTEGER DEFAULT 0, + recurrence_status INTEGER DEFAULT 0 NOT NULL, + recurrence_trigger INTEGER DEFAULT 0 NOT NULL, + recurrence_factor INTEGER DEFAULT 0 NOT NULL, + recurrence_timeframe INTEGER DEFAULT 0 NOT NULL, + recurrence_basedate INTEGER DEFAULT 0 NOT NULL, + recurrence_parent INTEGER, + recurrence_child INTEGER, + priority INTEGER DEFAULT 0, + external_provider TEXT, + external_uri TEXT + ) + "); + + $pdo->exec('INSERT INTO tasks_new SELECT * FROM tasks'); + $pdo->exec('DROP TABLE tasks'); + $pdo->exec('ALTER TABLE tasks_new RENAME TO tasks'); +} + +function version_124(PDO $pdo) +{ + $pdo->exec('ALTER TABLE projects ADD COLUMN enable_global_tags INTEGER DEFAULT 1 NOT NULL'); +} + +function version_123(PDO $pdo) +{ + $pdo->exec('ALTER TABLE swimlanes ADD COLUMN task_limit INTEGER DEFAULT 0'); +} + +function version_122(PDO $pdo) +{ + $pdo->exec('ALTER TABLE projects ADD COLUMN task_limit INTEGER DEFAULT 0'); +} + +function version_121(PDO $pdo) +{ + $pdo->exec('ALTER TABLE projects ADD COLUMN per_swimlane_task_limits INTEGER DEFAULT 0 NOT NULL'); +} + +function version_120(PDO $pdo) +{ + $pdo->exec('ALTER TABLE tags ADD COLUMN color_id TEXT DEFAULT NULL'); +} + +function version_119(PDO $pdo) +{ + $pdo->exec('ALTER TABLE project_has_categories ADD COLUMN color_id TEXT DEFAULT NULL'); +} + +function version_118(PDO $pdo) +{ + $pdo->exec('ALTER TABLE users ADD COLUMN filter TEXT'); +} + +function version_117(PDO $pdo) +{ + $pdo->exec("CREATE TABLE sessions ( + id TEXT PRIMARY KEY, + expire_at INTEGER NOT NULL, + data TEXT DEFAULT '' + )"); +} + +function version_116(PDO $pdo) +{ + $pdo->exec('CREATE TABLE predefined_task_descriptions ( + id INTEGER PRIMARY KEY, + project_id INTEGER NOT NULL, + title TEXT NOT NULL, + description TEXT NOT NULL, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + )'); +} + +function version_115(PDO $pdo) +{ + $pdo->exec('ALTER TABLE projects ADD COLUMN predefined_email_subjects TEXT'); +} + +function version_114(PDO $pdo) +{ + $pdo->exec('ALTER TABLE column_has_move_restrictions ADD COLUMN only_assigned INTEGER DEFAULT 0'); +} + +function version_113(PDO $pdo) +{ + $pdo->exec( + 'ALTER TABLE project_activities RENAME TO project_activities_bak' + ); + $pdo->exec(" + CREATE TABLE project_activities ( + id INTEGER PRIMARY KEY, + date_creation INTEGER NOT NULL, + event_name TEXT NOT NULL, + creator_id INTEGER NOT NULL, + project_id INTEGER NOT NULL, + task_id INTEGER NOT NULL, + data TEXT, + FOREIGN KEY(creator_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( + 'INSERT INTO project_activities SELECT * FROM project_activities_bak' + ); + $pdo->exec( + 'DROP TABLE project_activities_bak' + ); +} + +function version_112(PDO $pdo) +{ + migrate_default_swimlane($pdo); +} + +function version_111(PDO $pdo) +{ + $pdo->exec('ALTER TABLE "projects" ADD COLUMN email TEXT'); +} + +function version_110(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE invites ( + email TEXT NOT NULL, + project_id INTEGER NOT NULL, + token TEXT NOT NULL, + PRIMARY KEY(email, token) + ) + "); + + $pdo->exec("DELETE FROM settings WHERE \"option\"='application_datetime_format'"); +} + +function version_109(PDO $pdo) +{ + $pdo->exec('ALTER TABLE comments ADD COLUMN date_modification INTEGER'); + $pdo->exec('UPDATE comments SET date_modification = date_creation WHERE date_modification IS NULL;'); +} + +function version_108(PDO $pdo) +{ + $pdo->exec('ALTER TABLE users ADD COLUMN api_access_token VARCHAR(255) DEFAULT NULL'); +} + +function version_107(PDO $pdo) +{ + $pdo->exec("ALTER TABLE tasks ADD COLUMN external_provider TEXT"); + $pdo->exec("ALTER TABLE tasks ADD COLUMN external_uri TEXT"); +} + +function version_106(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE column_has_restrictions ( + restriction_id INTEGER PRIMARY KEY, + project_id INTEGER NOT NULL, + role_id INTEGER NOT NULL, + column_id INTEGER NOT NULL, + rule VARCHAR(255) NOT NULL, + UNIQUE(role_id, column_id, rule), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(role_id) REFERENCES project_has_roles(role_id) ON DELETE CASCADE, + FOREIGN KEY(column_id) REFERENCES columns(id) ON DELETE CASCADE + ) + "); +} + +function version_105(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_role_has_restrictions ( + restriction_id INTEGER PRIMARY KEY, + project_id INTEGER NOT NULL, + role_id INTEGER NOT NULL, + rule VARCHAR(255) NOT NULL, + UNIQUE(role_id, rule), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(role_id) REFERENCES project_has_roles(role_id) ON DELETE CASCADE + ) + "); +} + +function version_104(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_has_roles ( + role_id INTEGER PRIMARY KEY, + role TEXT NOT NULL, + project_id INTEGER NOT NULL, + UNIQUE(project_id, role), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + ) + "); + + $pdo->exec(" + CREATE TABLE column_has_move_restrictions ( + restriction_id INTEGER PRIMARY KEY, + project_id INTEGER NOT NULL, + role_id INTEGER NOT NULL, + src_column_id INTEGER NOT NULL, + dst_column_id INTEGER NOT NULL, + UNIQUE(role_id, src_column_id, dst_column_id), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(role_id) REFERENCES project_has_roles(role_id) ON DELETE CASCADE, + FOREIGN KEY(src_column_id) REFERENCES columns(id) ON DELETE CASCADE, + FOREIGN KEY(dst_column_id) REFERENCES columns(id) ON DELETE CASCADE + ) + "); +} + +function version_103(PDO $pdo) +{ + $pdo->exec("ALTER TABLE columns ADD COLUMN hide_in_dashboard INTEGER DEFAULT 0 NOT NULL"); +} + +function version_102(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE tags ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + project_id INTEGER NOT NULL, + UNIQUE(project_id, name) + ) + "); + + $pdo->exec(" + CREATE TABLE task_has_tags ( + task_id INTEGER NOT NULL, + tag_id INTEGER NOT NULL, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE, + FOREIGN KEY(tag_id) REFERENCES tags(id) ON DELETE CASCADE, + UNIQUE(tag_id, task_id) + ) + "); +} + +function version_101(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN avatar_path TEXT"); +} + +function version_100(PDO $pdo) +{ + $pdo->exec("ALTER TABLE user_has_metadata ADD COLUMN changed_by INTEGER DEFAULT 0 NOT NULL"); + $pdo->exec("ALTER TABLE user_has_metadata ADD COLUMN changed_on INTEGER DEFAULT 0 NOT NULL"); + + $pdo->exec("ALTER TABLE project_has_metadata ADD COLUMN changed_by INTEGER DEFAULT 0 NOT NULL"); + $pdo->exec("ALTER TABLE project_has_metadata ADD COLUMN changed_on INTEGER DEFAULT 0 NOT NULL"); + + $pdo->exec("ALTER TABLE task_has_metadata ADD COLUMN changed_by INTEGER DEFAULT 0 NOT NULL"); + $pdo->exec("ALTER TABLE task_has_metadata ADD COLUMN changed_on INTEGER DEFAULT 0 NOT NULL"); + + $pdo->exec("ALTER TABLE settings ADD COLUMN changed_by INTEGER DEFAULT 0 NOT NULL"); + $pdo->exec("ALTER TABLE settings ADD COLUMN changed_on INTEGER DEFAULT 0 NOT NULL"); + +} + +function version_99(PDO $pdo) +{ + $pdo->exec("UPDATE project_activities SET event_name='task.file.create' WHERE event_name='file.create'"); +} + +function version_98(PDO $pdo) +{ + $pdo->exec('ALTER TABLE files RENAME TO task_has_files'); + + $pdo->exec( + " + CREATE TABLE project_has_files ( + id INTEGER PRIMARY KEY, + project_id INTEGER NOT NULL, + name TEXT COLLATE NOCASE NOT NULL, + path TEXT NOT NULL, + is_image INTEGER DEFAULT 0, + size INTEGER DEFAULT 0 NOT NULL, + user_id INTEGER DEFAULT 0 NOT NULL, + date INTEGER DEFAULT 0 NOT NULL, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + )" + ); +} + +function version_97(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN is_active INTEGER DEFAULT 1"); +} + +function version_96(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE task_has_external_links ( + id INTEGER PRIMARY KEY, + link_type TEXT NOT NULL, + dependency TEXT NOT NULL, + title TEXT NOT NULL, + url TEXT NOT NULL, + date_creation INTEGER NOT NULL, + date_modification INTEGER NOT NULL, + task_id INTEGER NOT NULL, + creator_id INTEGER DEFAULT 0, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + ) + "); +} + +function version_95(PDO $pdo) +{ + $pdo->exec("ALTER TABLE projects ADD COLUMN priority_default INTEGER DEFAULT 0"); + $pdo->exec("ALTER TABLE projects ADD COLUMN priority_start INTEGER DEFAULT 0"); + $pdo->exec("ALTER TABLE projects ADD COLUMN priority_end INTEGER DEFAULT 3"); + $pdo->exec("ALTER TABLE tasks ADD COLUMN priority INTEGER DEFAULT 0"); +} + +function version_94(PDO $pdo) +{ + $pdo->exec("ALTER TABLE projects ADD COLUMN owner_id INTEGER DEFAULT 0"); +} + +function version_93(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE password_reset ( + token TEXT PRIMARY KEY, + user_id INTEGER NOT NULL, + date_expiration INTEGER NOT NULL, + date_creation INTEGER NOT NULL, + ip TEXT NOT NULL, + user_agent TEXT NOT NULL, + is_active INTEGER NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + ) + "); + + $pdo->exec("INSERT INTO settings VALUES ('password_reset', '1')"); +} + +function version_92(PDO $pdo) +{ + $rq = $pdo->prepare('SELECT * FROM actions'); + $rq->execute(); + $rows = $rq->fetchAll(PDO::FETCH_ASSOC) ?: array(); + + $rq = $pdo->prepare('UPDATE actions SET action_name=? WHERE id=?'); + + foreach ($rows as $row) { + if ($row['action_name'] === 'TaskAssignCurrentUser' && $row['event_name'] === 'task.move.column') { + $row['action_name'] = '\Kanboard\Action\TaskAssignCurrentUserColumn'; + } elseif ($row['action_name'] === 'TaskClose' && $row['event_name'] === 'task.move.column') { + $row['action_name'] = '\Kanboard\Action\TaskCloseColumn'; + } elseif ($row['action_name'] === 'TaskLogMoveAnotherColumn') { + $row['action_name'] = '\Kanboard\Action\CommentCreationMoveTaskColumn'; + } elseif ($row['action_name'][0] !== '\\') { + $row['action_name'] = '\Kanboard\Action\\'.$row['action_name']; + } + + $rq->execute(array($row['action_name'], $row['id'])); + } +} + +function version_91(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN role TEXT NOT NULL DEFAULT '".Role::APP_USER."'"); + + $rq = $pdo->prepare('SELECT * FROM users'); + $rq->execute(); + $rows = $rq->fetchAll(PDO::FETCH_ASSOC) ?: array(); + + $rq = $pdo->prepare('UPDATE users SET "role"=? WHERE "id"=?'); + + foreach ($rows as $row) { + $role = Role::APP_USER; + + if ($row['is_admin'] == 1) { + $role = Role::APP_ADMIN; + } elseif ($row['is_project_admin']) { + $role = Role::APP_MANAGER; + } + + $rq->execute(array($role, $row['id'])); + } +} + +function version_90(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_has_groups ( + group_id INTEGER NOT NULL, + project_id INTEGER NOT NULL, + role TEXT NOT NULL, + FOREIGN KEY(group_id) REFERENCES groups(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + UNIQUE(group_id, project_id) + ) + "); + + $pdo->exec("ALTER TABLE project_has_users ADD COLUMN role TEXT NOT NULL DEFAULT '".Role::PROJECT_VIEWER."'"); + + $rq = $pdo->prepare('SELECT * FROM project_has_users'); + $rq->execute(); + $rows = $rq->fetchAll(PDO::FETCH_ASSOC) ?: array(); + + $rq = $pdo->prepare('UPDATE project_has_users SET "role"=? WHERE "user_id"=?'); + + foreach ($rows as $row) { + $rq->execute(array( + $row['is_owner'] == 1 ? Role::PROJECT_MANAGER : Role::PROJECT_MEMBER, + $row['user_id'], + )); + } +} + +function version_89(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE groups ( + id INTEGER PRIMARY KEY, + external_id TEXT DEFAULT '', + name TEXT NOCASE NOT NULL UNIQUE + ) + "); + + $pdo->exec(" + CREATE TABLE group_has_users ( + group_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + FOREIGN KEY(group_id) REFERENCES groups(id) ON DELETE CASCADE, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + UNIQUE(group_id, user_id) + ) + "); +} + +function version_88(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE user_has_metadata ( + user_id INTEGER NOT NULL, + name TEXT NOT NULL, + value TEXT DEFAULT '', + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + UNIQUE(user_id, name) + ) + "); + + $pdo->exec(" + CREATE TABLE project_has_metadata ( + project_id INTEGER NOT NULL, + name TEXT NOT NULL, + value TEXT DEFAULT '', + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + UNIQUE(project_id, name) + ) + "); + + $pdo->exec(" + CREATE TABLE task_has_metadata ( + task_id INTEGER NOT NULL, + name TEXT NOT NULL, + value TEXT DEFAULT '', + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE, + UNIQUE(task_id, name) + ) + "); + + $pdo->exec("DROP TABLE project_integrations"); + + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber'"); + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber_server'"); + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber_domain'"); + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber_username'"); + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber_password'"); + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber_nickname'"); + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_jabber_room'"); + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_hipchat'"); + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_hipchat_api_url'"); + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_hipchat_room_id'"); + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_hipchat_room_token'"); + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_slack_webhook'"); + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_slack_webhook_url'"); + $pdo->exec("DELETE FROM settings WHERE \"option\"='integration_slack_webhook_channel'"); +} + +function version_87(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_has_notification_types ( + id INTEGER PRIMARY KEY, + project_id INTEGER NOT NULL, + notification_type TEXT NOT NULL, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + UNIQUE(project_id, notification_type) + ) + "); +} + +function version_86(PDO $pdo) +{ + $pdo->exec("ALTER TABLE custom_filters ADD COLUMN append INTEGER DEFAULT 0"); +} + +function version_85(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE user_has_unread_notifications ( + id INTEGER PRIMARY KEY, + user_id INTEGER NOT NULL, + date_creation INTEGER NOT NULL, + event_name TEXT NOT NULL, + event_data TEXT NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + ) + "); + + $pdo->exec(" + CREATE TABLE user_has_notification_types ( + id INTEGER PRIMARY KEY, + user_id INTEGER NOT NULL, + notification_type TEXT, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + ) + "); + + $pdo->exec('CREATE UNIQUE INDEX user_has_notification_types_user_idx ON user_has_notification_types(user_id, notification_type)'); + + // Migrate people who have notification enabled before + $rq = $pdo->prepare('SELECT id FROM users WHERE notifications_enabled=1'); + $rq->execute(); + $user_ids = $rq->fetchAll(PDO::FETCH_COLUMN, 0); + + foreach ($user_ids as $user_id) { + $rq = $pdo->prepare('INSERT INTO user_has_notification_types (user_id, notification_type) VALUES (?, ?)'); + $rq->execute(array($user_id, 'email')); + } +} + +function version_84(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE custom_filters ( + id INTEGER PRIMARY KEY, + filter TEXT NOT NULL, + project_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + name TEXT NOT NULL, + is_shared INTEGER DEFAULT 0 + ) + "); +} + +function version_83(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE plugin_schema_versions ( + plugin TEXT NOT NULL PRIMARY KEY, + version INTEGER NOT NULL DEFAULT 0 + ) + "); +} + +function version_82(PDO $pdo) +{ + $pdo->exec("ALTER TABLE swimlanes ADD COLUMN description TEXT"); +} + +function version_81(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN gitlab_id INTEGER"); +} + +function version_80(PDO $pdo) +{ + $pdo->exec("ALTER TABLE projects ADD COLUMN start_date TEXT DEFAULT ''"); + $pdo->exec("ALTER TABLE projects ADD COLUMN end_date TEXT DEFAULT ''"); +} + +function version_79(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN is_project_admin INTEGER DEFAULT 0"); +} + +function version_78(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN nb_failed_login INTEGER DEFAULT 0"); + $pdo->exec("ALTER TABLE users ADD COLUMN lock_expiration_date INTEGER DEFAULT 0"); +} + +function version_77(PDO $pdo) +{ + $pdo->exec("INSERT INTO settings VALUES ('subtask_time_tracking', '1')"); + $pdo->exec("INSERT INTO settings VALUES ('cfd_include_closed_tasks', '1')"); +} + +function version_76(PDO $pdo) +{ + $pdo->exec("INSERT INTO settings VALUES ('default_color', 'yellow')"); +} + +function version_75(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_daily_stats ( + id INTEGER PRIMARY KEY, + day TEXT NOT NULL, + project_id INTEGER NOT NULL, + avg_lead_time INTEGER NOT NULL DEFAULT 0, + avg_cycle_time INTEGER NOT NULL DEFAULT 0, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + ) + "); + + $pdo->exec('CREATE UNIQUE INDEX project_daily_stats_idx ON project_daily_stats(day, project_id)'); + + $pdo->exec('ALTER TABLE project_daily_summaries RENAME TO project_daily_column_stats'); +} + +function version_74(PDO $pdo) +{ + $pdo->exec("ALTER TABLE project_integrations ADD COLUMN slack_webhook_channel TEXT DEFAULT ''"); + $pdo->exec("INSERT INTO settings VALUES ('integration_slack_webhook_channel', '')"); +} + +function version_73(PDO $pdo) +{ + $pdo->exec("DELETE FROM settings WHERE option='subtask_time_tracking'"); +} + +function version_72(PDO $pdo) +{ + $pdo->exec( + 'ALTER TABLE comments RENAME TO comments_bak' + ); + + $pdo->exec( + 'CREATE TABLE comments ( + id INTEGER PRIMARY KEY, + task_id INTEGER NOT NULL, + user_id INTEGER DEFAULT 0, + date_creation INTEGER NOT NULL, + comment TEXT NOT NULL, + reference VARCHAR(50), + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + )' + ); + + $pdo->exec( + 'INSERT INTO comments SELECT * FROM comments_bak' + ); + + $pdo->exec( + 'DROP TABLE comments_bak' + ); +} + +function version_71(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN notifications_filter INTEGER DEFAULT 4"); +} + +function version_70(PDO $pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('webhook_url', '')); + + $pdo->exec("DELETE FROM settings WHERE option='webhook_url_task_creation'"); + $pdo->exec("DELETE FROM settings WHERE option='webhook_url_task_modification'"); +} + +function version_69(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN token TEXT DEFAULT ''"); +} + +function version_68(PDO $pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('calendar_user_subtasks_time_tracking', 0)); + $rq->execute(array('calendar_user_tasks', 'date_started')); + $rq->execute(array('calendar_project_tasks', 'date_started')); + + $pdo->exec("DELETE FROM settings WHERE option='subtask_forecast'"); +} + +function version_67(PDO $pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('integration_jabber', '0')); + $rq->execute(array('integration_jabber_server', '')); + $rq->execute(array('integration_jabber_domain', '')); + $rq->execute(array('integration_jabber_username', '')); + $rq->execute(array('integration_jabber_password', '')); + $rq->execute(array('integration_jabber_nickname', 'kanboard')); + $rq->execute(array('integration_jabber_room', '')); + + $pdo->exec("ALTER TABLE project_integrations ADD COLUMN jabber INTEGER DEFAULT '0'"); + $pdo->exec("ALTER TABLE project_integrations ADD COLUMN jabber_server TEXT DEFAULT ''"); + $pdo->exec("ALTER TABLE project_integrations ADD COLUMN jabber_domain TEXT DEFAULT ''"); + $pdo->exec("ALTER TABLE project_integrations ADD COLUMN jabber_username TEXT DEFAULT ''"); + $pdo->exec("ALTER TABLE project_integrations ADD COLUMN jabber_password TEXT DEFAULT ''"); + $pdo->exec("ALTER TABLE project_integrations ADD COLUMN jabber_nickname TEXT DEFAULT 'kanboard'"); + $pdo->exec("ALTER TABLE project_integrations ADD COLUMN jabber_room TEXT DEFAULT ''"); +} + +function version_66(PDO $pdo) +{ + $pdo->exec('ALTER TABLE tasks ADD COLUMN recurrence_status INTEGER NOT NULL DEFAULT 0'); + $pdo->exec('ALTER TABLE tasks ADD COLUMN recurrence_trigger INTEGER NOT NULL DEFAULT 0'); + $pdo->exec('ALTER TABLE tasks ADD COLUMN recurrence_factor INTEGER NOT NULL DEFAULT 0'); + $pdo->exec('ALTER TABLE tasks ADD COLUMN recurrence_timeframe INTEGER NOT NULL DEFAULT 0'); + $pdo->exec('ALTER TABLE tasks ADD COLUMN recurrence_basedate INTEGER NOT NULL DEFAULT 0'); + $pdo->exec('ALTER TABLE tasks ADD COLUMN recurrence_parent INTEGER'); + $pdo->exec('ALTER TABLE tasks ADD COLUMN recurrence_child INTEGER'); +} + +function version_65(PDO $pdo) +{ + $pdo->exec("ALTER TABLE projects ADD COLUMN identifier TEXT DEFAULT ''"); +} + +function version_64(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_integrations ( + id INTEGER PRIMARY KEY, + project_id INTEGER NOT NULL UNIQUE, + hipchat INTEGER DEFAULT 0, + hipchat_api_url TEXT DEFAULT 'https://api.hipchat.com', + hipchat_room_id TEXT, + hipchat_room_token TEXT, + slack INTEGER DEFAULT 0, + slack_webhook_url TEXT, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + ) + "); +} + +function version_63(PDO $pdo) +{ + $pdo->exec('ALTER TABLE project_daily_summaries ADD COLUMN score INTEGER NOT NULL DEFAULT 0'); +} + +function version_62(PDO $pdo) +{ + $pdo->exec('ALTER TABLE project_has_categories ADD COLUMN description TEXT'); +} + +function version_61(PDO $pdo) +{ + $pdo->exec('ALTER TABLE files ADD COLUMN "date" INTEGER NOT NULL DEFAULT 0'); + $pdo->exec('ALTER TABLE files ADD COLUMN "user_id" INTEGER NOT NULL DEFAULT 0'); + $pdo->exec('ALTER TABLE files ADD COLUMN "size" INTEGER NOT NULL DEFAULT 0'); +} + +function version_60(PDO $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 $pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('integration_gravatar', '0')); +} + +function version_58(PDO $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 $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) +{ + $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) +{ + $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 $pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('subtask_forecast', '0')); +} + +function version_53(PDO $pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('application_stylesheet', '')); +} + +function version_52(PDO $pdo) +{ + $pdo->exec("ALTER TABLE subtask_time_tracking ADD COLUMN time_spent REAL DEFAULT 0"); +} + +function version_48(PDO $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) +{ + $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) +{ + $pdo->exec('ALTER TABLE projects ADD COLUMN description TEXT'); +} + +function version_45(PDO $pdo) +{ + $pdo->exec("CREATE TABLE links ( + id INTEGER PRIMARY KEY, + label TEXT NOT NULL, + opposite_id INTEGER DEFAULT 0, + UNIQUE(label) + )"); + + $pdo->exec("CREATE TABLE task_has_links ( + id INTEGER PRIMARY KEY, + link_id INTEGER NOT NULL, + task_id INTEGER NOT NULL, + opposite_task_id INTEGER NOT NULL, + FOREIGN KEY(link_id) REFERENCES links(id) ON DELETE CASCADE, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE, + FOREIGN KEY(opposite_task_id) REFERENCES tasks(id) ON DELETE CASCADE + )"); + + $pdo->exec("CREATE INDEX task_has_links_task_index ON task_has_links(task_id)"); + $pdo->exec("CREATE UNIQUE INDEX task_has_links_unique ON task_has_links(link_id, task_id, opposite_task_id)"); + + $rq = $pdo->prepare('INSERT INTO links (label, opposite_id) VALUES (?, ?)'); + $rq->execute(array('relates to', 0)); + $rq->execute(array('blocks', 3)); + $rq->execute(array('is blocked by', 2)); + $rq->execute(array('duplicates', 5)); + $rq->execute(array('is duplicated by', 4)); + $rq->execute(array('is a child of', 7)); + $rq->execute(array('is a parent of', 6)); + $rq->execute(array('targets milestone', 9)); + $rq->execute(array('is a milestone of', 8)); + $rq->execute(array('fixes', 11)); + $rq->execute(array('is fixed by', 10)); +} + +function version_44(PDO $pdo) +{ + $pdo->exec('ALTER TABLE tasks ADD COLUMN date_moved INTEGER DEFAULT 0'); + + /* Update tasks.date_moved from project_activities table if tasks.date_moved = null or 0. + * We take max project_activities.date_creation where event_name in task.create','task.move.column + * since creation date is always less than task moves + */ + $pdo->exec("UPDATE tasks + SET date_moved = ( + SELECT md + FROM ( + SELECT task_id, max(date_creation) md + FROM project_activities + WHERE event_name IN ('task.create', 'task.move.column') + GROUP BY task_id + ) src + WHERE id = src.task_id + ) + WHERE (date_moved IS NULL OR date_moved = 0) AND id IN ( + SELECT task_id + FROM ( + SELECT task_id, max(date_creation) md + FROM project_activities + WHERE event_name IN ('task.create', 'task.move.column') + GROUP BY task_id + ) src + )"); + + // If there is no activities for some tasks use the date_creation + $pdo->exec("UPDATE tasks SET date_moved = date_creation WHERE date_moved IS NULL OR date_moved = 0"); +} + +function version_43(PDO $pdo) +{ + $pdo->exec('ALTER TABLE users ADD COLUMN disable_login_form INTEGER DEFAULT 0'); +} + +function version_42(PDO $pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('subtask_restriction', '0')); + $rq->execute(array('subtask_time_tracking', '0')); + + $pdo->exec(" + CREATE TABLE subtask_time_tracking ( + id INTEGER PRIMARY KEY, + user_id INTEGER NOT NULL, + subtask_id INTEGER NOT NULL, + start INTEGER DEFAULT 0, + end INTEGER DEFAULT 0, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(subtask_id) REFERENCES task_has_subtasks(id) ON DELETE CASCADE + ) + "); +} + +function version_41(PDO $pdo) +{ + $pdo->exec('ALTER TABLE columns ADD COLUMN description TEXT'); +} + +function version_40(PDO $pdo) +{ + $pdo->exec('ALTER TABLE users ADD COLUMN timezone TEXT'); + $pdo->exec('ALTER TABLE users ADD COLUMN language TEXT'); +} + +function version_39(PDO $pdo) +{ + // Avoid some full table scans + $pdo->exec('CREATE INDEX users_admin_idx ON users(is_admin)'); + $pdo->exec('CREATE INDEX columns_project_idx ON columns(project_id)'); + $pdo->exec('CREATE INDEX tasks_project_idx ON tasks(project_id)'); + $pdo->exec('CREATE INDEX swimlanes_project_idx ON swimlanes(project_id)'); + $pdo->exec('CREATE INDEX categories_project_idx ON project_has_categories(project_id)'); + $pdo->exec('CREATE INDEX subtasks_task_idx ON task_has_subtasks(task_id)'); + $pdo->exec('CREATE INDEX files_task_idx ON task_has_files(task_id)'); + $pdo->exec('CREATE INDEX comments_task_idx ON comments(task_id)'); + + // Set the ownership for all private projects + $rq = $pdo->prepare('SELECT id FROM projects WHERE is_private=1'); + $rq->execute(); + $project_ids = $rq->fetchAll(PDO::FETCH_COLUMN, 0); + + $rq = $pdo->prepare('UPDATE project_has_users SET is_owner=1 WHERE project_id=?'); + + foreach ($project_ids as $project_id) { + $rq->execute(array($project_id)); + } +} + +function version_38(PDO $pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('project_categories', '')); +} + +function version_37(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE swimlanes ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + position INTEGER DEFAULT 1, + is_active INTEGER DEFAULT 1, + project_id INTEGER NOT NULL, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + UNIQUE (name, project_id) + ) + "); + + $pdo->exec('ALTER TABLE tasks ADD COLUMN swimlane_id INTEGER DEFAULT 0'); + $pdo->exec("ALTER TABLE projects ADD COLUMN default_swimlane TEXT DEFAULT 'Default swimlane'"); + $pdo->exec("ALTER TABLE projects ADD COLUMN show_default_swimlane INTEGER DEFAULT 1"); +} + +function version_36(PDO $pdo) +{ + $pdo->exec('ALTER TABLE project_has_users ADD COLUMN is_owner INTEGER DEFAULT "0"'); +} + +function version_35(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_daily_summaries ( + id INTEGER PRIMARY KEY, + day TEXT NOT NULL, + project_id INTEGER NOT NULL, + column_id INTEGER NOT NULL, + total INTEGER NOT NULL DEFAULT 0, + FOREIGN KEY(column_id) REFERENCES columns(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + ) + "); + + $pdo->exec('CREATE UNIQUE INDEX project_daily_column_stats_idx ON project_daily_summaries(day, project_id, column_id)'); +} + +function version_34(PDO $pdo) +{ + $pdo->exec('ALTER TABLE projects ADD COLUMN is_everybody_allowed INTEGER DEFAULT "0"'); +} + +function version_33(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_activities ( + id INTEGER PRIMARY KEY, + date_creation INTEGER NOT NULL, + event_name TEXT NOT NULL, + creator_id INTEGER NOT NULL, + project_id INTEGER NOT NULL, + task_id INTEGER NOT NULL, + data TEXT, + FOREIGN KEY(creator_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('DROP TABLE task_has_events'); + $pdo->exec('DROP TABLE comment_has_events'); + $pdo->exec('DROP TABLE subtask_has_events'); +} + +function version_32(PDO $pdo) +{ + $pdo->exec("ALTER TABLE tasks ADD COLUMN date_started INTEGER"); + $pdo->exec("ALTER TABLE tasks ADD COLUMN time_spent NUMERIC DEFAULT 0"); + $pdo->exec("ALTER TABLE tasks ADD COLUMN time_estimated NUMERIC DEFAULT 0"); +} + +function version_31(PDO $pdo) +{ + $pdo->exec('ALTER TABLE projects ADD COLUMN is_private INTEGER DEFAULT "0"'); +} + +function version_30(PDO $pdo) +{ + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('application_date_format', 'm/d/Y')); +} + +function version_29(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE settings ( + option TEXT PRIMARY KEY, + value TEXT DEFAULT '' + ) + "); + + // Migrate old config parameters + $rq = $pdo->prepare('SELECT * FROM config'); + $rq->execute(); + $parameters = $rq->fetch(PDO::FETCH_ASSOC); + + $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)'); + $rq->execute(array('board_highlight_period', defined('RECENT_TASK_PERIOD') ? RECENT_TASK_PERIOD : 48*60*60)); + $rq->execute(array('board_public_refresh_interval', defined('BOARD_PUBLIC_CHECK_INTERVAL') ? BOARD_PUBLIC_CHECK_INTERVAL : 60)); + $rq->execute(array('board_private_refresh_interval', defined('BOARD_CHECK_INTERVAL') ? BOARD_CHECK_INTERVAL : 10)); + $rq->execute(array('board_columns', $parameters['default_columns'])); + $rq->execute(array('webhook_url_task_creation', $parameters['webhooks_url_task_creation'])); + $rq->execute(array('webhook_url_task_modification', $parameters['webhooks_url_task_modification'])); + $rq->execute(array('webhook_token', $parameters['webhooks_token'])); + $rq->execute(array('api_token', $parameters['api_token'])); + $rq->execute(array('application_language', $parameters['language'])); + $rq->execute(array('application_timezone', $parameters['timezone'])); + $rq->execute(array('application_url', defined('KANBOARD_URL') ? KANBOARD_URL : '')); + + $pdo->exec('DROP TABLE config'); +} + +function version_28(PDO $pdo) +{ + $pdo->exec("ALTER TABLE tasks ADD COLUMN reference TEXT DEFAULT ''"); + $pdo->exec("ALTER TABLE comments ADD COLUMN reference TEXT DEFAULT ''"); + + $pdo->exec('CREATE INDEX tasks_reference_idx ON tasks(reference)'); + $pdo->exec('CREATE INDEX comments_reference_idx ON comments(reference)'); +} + +function version_27(PDO $pdo) +{ + $pdo->exec('CREATE UNIQUE INDEX users_username_idx ON users(username)'); +} + +function version_26(PDO $pdo) +{ + $pdo->exec("ALTER TABLE config ADD COLUMN default_columns TEXT DEFAULT ''"); +} + +function version_25(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE task_has_events ( + id INTEGER PRIMARY KEY, + date_creation INTEGER NOT NULL, + event_name TEXT NOT NULL, + creator_id INTEGER, + project_id INTEGER, + task_id INTEGER, + data TEXT, + FOREIGN KEY(creator_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 TABLE subtask_has_events ( + id INTEGER PRIMARY KEY, + date_creation INTEGER NOT NULL, + event_name TEXT NOT NULL, + creator_id INTEGER, + project_id INTEGER, + subtask_id INTEGER, + task_id INTEGER, + data TEXT, + FOREIGN KEY(creator_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(subtask_id) REFERENCES task_has_subtasks(id) ON DELETE CASCADE, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + ); + "); + + $pdo->exec(" + CREATE TABLE comment_has_events ( + id INTEGER PRIMARY KEY, + date_creation INTEGER NOT NULL, + event_name TEXT NOT NULL, + creator_id INTEGER, + project_id INTEGER, + comment_id INTEGER, + task_id INTEGER, + data TEXT, + FOREIGN KEY(creator_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(comment_id) REFERENCES comments(id) ON DELETE CASCADE, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + ); + "); +} + +function version_24(PDO $pdo) +{ + $pdo->exec('ALTER TABLE projects ADD COLUMN is_public INTEGER DEFAULT "0"'); +} + +function version_23(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN notifications_enabled INTEGER DEFAULT '0'"); + + $pdo->exec(" + CREATE TABLE user_has_notifications ( + user_id INTEGER NOT NULL, + project_id INTEGER NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + UNIQUE(project_id, user_id) + ); + "); +} + +function version_22(PDO $pdo) +{ + $pdo->exec("ALTER TABLE config ADD COLUMN webhooks_url_task_modification TEXT"); + $pdo->exec("ALTER TABLE config ADD COLUMN webhooks_url_task_creation TEXT"); +} + +function version_21(PDO $pdo) +{ + $pdo->exec("ALTER TABLE tasks ADD COLUMN creator_id INTEGER DEFAULT '0'"); + $pdo->exec("ALTER TABLE tasks ADD COLUMN date_modification INTEGER DEFAULT '0'"); +} + +function version_20(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN github_id TEXT"); +} + +function version_19(PDO $pdo) +{ + $pdo->exec("ALTER TABLE config ADD COLUMN api_token TEXT DEFAULT ''"); + $pdo->exec("UPDATE config SET api_token='".Token::getToken()."'"); +} + +function version_18(PDO $pdo) +{ + $pdo->exec( + " + CREATE TABLE task_has_subtasks ( + id INTEGER PRIMARY KEY, + title TEXT COLLATE NOCASE NOT NULL, + status INTEGER DEFAULT 0, + time_estimated NUMERIC DEFAULT 0, + time_spent NUMERIC DEFAULT 0, + task_id INTEGER NOT NULL, + user_id INTEGER, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + )" + ); +} + +function version_17(PDO $pdo) +{ + $pdo->exec( + " + CREATE TABLE task_has_files ( + id INTEGER PRIMARY KEY, + name TEXT COLLATE NOCASE NOT NULL, + path TEXT, + is_image INTEGER DEFAULT 0, + task_id INTEGER NOT NULL, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + )" + ); +} + +function version_16(PDO $pdo) +{ + $pdo->exec( + " + CREATE TABLE project_has_categories ( + id INTEGER PRIMARY KEY, + name TEXT COLLATE NOCASE NOT NULL, + project_id INTEGER NOT NULL, + UNIQUE (project_id, name), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + )" + ); + + $pdo->exec("ALTER TABLE tasks ADD COLUMN category_id INTEGER DEFAULT 0"); +} + +function version_15(PDO $pdo) +{ + $pdo->exec("ALTER TABLE projects ADD COLUMN last_modified INTEGER DEFAULT 0"); +} + +function version_14(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN name TEXT"); + $pdo->exec("ALTER TABLE users ADD COLUMN email TEXT"); + $pdo->exec("ALTER TABLE users ADD COLUMN google_id TEXT"); +} + +function version_13(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN is_ldap_user INTEGER DEFAULT 0"); +} + +function version_12(PDO $pdo) +{ + $pdo->exec( + 'CREATE TABLE remember_me ( + id INTEGER PRIMARY KEY, + user_id INTEGER NOT NULL, + ip TEXT, + user_agent TEXT, + token TEXT, + sequence TEXT, + expiration INTEGER, + date_creation INTEGER, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )' + ); + + $pdo->exec( + 'CREATE TABLE last_logins ( + id INTEGER PRIMARY KEY, + auth_type TEXT, + user_id INTEGER NOT NULL, + ip TEXT, + user_agent TEXT, + date_creation INTEGER, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )' + ); + + $pdo->exec('CREATE INDEX last_logins_user_idx ON last_logins(user_id)'); +} + +function version_11(PDO $pdo) +{ + $pdo->exec( + 'ALTER TABLE comments RENAME TO comments_bak' + ); + + $pdo->exec( + 'CREATE TABLE comments ( + id INTEGER PRIMARY KEY, + task_id INTEGER, + user_id INTEGER, + date INTEGER, + comment TEXT, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )' + ); + + $pdo->exec( + 'INSERT INTO comments SELECT * FROM comments_bak' + ); + + $pdo->exec( + 'DROP TABLE comments_bak' + ); +} + +function version_10(PDO $pdo) +{ + $pdo->exec( + 'CREATE TABLE actions ( + id INTEGER PRIMARY KEY, + project_id INTEGER NOT NULL, + event_name TEXT NOT NULL, + action_name TEXT NOT NULL, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + )' + ); + + $pdo->exec( + 'CREATE TABLE action_has_params ( + id INTEGER PRIMARY KEY, + action_id INTEGER NOT NULL, + name TEXT NOT NULL, + value TEXT NOT NULL, + FOREIGN KEY(action_id) REFERENCES actions(id) ON DELETE CASCADE + )' + ); +} + +function version_9(PDO $pdo) +{ + $pdo->exec("ALTER TABLE tasks ADD COLUMN date_due INTEGER"); +} + +function version_8(PDO $pdo) +{ + $pdo->exec( + 'CREATE TABLE comments ( + id INTEGER PRIMARY KEY, + task_id INTEGER, + user_id INTEGER, + date INTEGER, + comment TEXT, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE, + FOREIGN KEY(user_id) REFERENCES tasks(id) ON DELETE CASCADE + )' + ); +} + +function version_7(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE project_has_users ( + project_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + UNIQUE(project_id, user_id) + ) + "); +} + +function version_6(PDO $pdo) +{ + $pdo->exec("ALTER TABLE columns ADD COLUMN task_limit INTEGER DEFAULT '0'"); +} + +function version_5(PDO $pdo) +{ + $pdo->exec("ALTER TABLE tasks ADD COLUMN score INTEGER"); +} + +function version_4(PDO $pdo) +{ + $pdo->exec("ALTER TABLE config ADD COLUMN timezone TEXT DEFAULT 'UTC'"); +} + +function version_3(PDO $pdo) +{ + $pdo->exec('ALTER TABLE projects ADD COLUMN token TEXT'); +} + +function version_2(PDO $pdo) +{ + $pdo->exec('ALTER TABLE tasks ADD COLUMN date_completed INTEGER'); + $pdo->exec('UPDATE tasks SET date_completed=date_creation WHERE is_active=0'); +} + +function version_1(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE config ( + language TEXT DEFAULT 'en_US', + webhooks_token TEXT DEFAULT '' + ) + "); + + $pdo->exec(" + CREATE TABLE users ( + id INTEGER PRIMARY KEY, + username TEXT NOT NULL, + password TEXT, + is_admin INTEGER DEFAULT 0 + ) + "); + + $pdo->exec(" + CREATE TABLE projects ( + id INTEGER PRIMARY KEY, + name TEXT NOCASE NOT NULL, + is_active INTEGER DEFAULT 1 + ) + "); + + $pdo->exec(" + CREATE TABLE columns ( + id INTEGER PRIMARY KEY, + title TEXT NOT NULL, + position INTEGER, + project_id INTEGER NOT NULL, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + UNIQUE (title, project_id) + ) + "); + + $pdo->exec(" + CREATE TABLE tasks ( + id INTEGER PRIMARY KEY, + title TEXT NOCASE NOT NULL, + description TEXT, + date_creation INTEGER, + color_id TEXT, + project_id INTEGER, + column_id INTEGER, + owner_id INTEGER DEFAULT '0', + position INTEGER, + is_active INTEGER DEFAULT 1, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + FOREIGN KEY(column_id) REFERENCES columns(id) ON DELETE CASCADE + ) + "); + + $pdo->exec(" + INSERT INTO users + (username, password, is_admin) + VALUES ('admin', '".\password_hash('admin', PASSWORD_BCRYPT)."', '1') + "); + + $pdo->exec(" + INSERT INTO config + (webhooks_token) + VALUES ('".Token::getToken()."') + "); +} diff --git a/app/ServiceProvider/ActionProvider.php b/app/ServiceProvider/ActionProvider.php new file mode 100644 index 0000000..5fb6abf --- /dev/null +++ b/app/ServiceProvider/ActionProvider.php @@ -0,0 +1,128 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Kanboard\Action\TaskAssignCategorySwimlaneChange; +use Kanboard\Action\TaskAssignColorOnDueDate; +use Kanboard\Action\TaskAssignColorOnStartDate; +use Kanboard\Action\TaskAssignColorPriority; +use Kanboard\Action\TaskAssignDueDateOnCreation; +use Kanboard\Action\TaskMoveColumnClosed; +use Kanboard\Action\TaskMoveColumnNotMovedPeriod; +use Kanboard\Action\TaskMoveColumnOnDueDate; +use Kanboard\Core\Action\ActionManager; +use Kanboard\Action\CommentCreation; +use Kanboard\Action\CommentCreationMoveTaskColumn; +use Kanboard\Action\TaskAssignCategoryColor; +use Kanboard\Action\TaskAssignCategoryLabel; +use Kanboard\Action\TaskAssignCategoryLink; +use Kanboard\Action\TaskAssignColorCategory; +use Kanboard\Action\TaskAssignColorColumn; +use Kanboard\Action\TaskAssignColorLink; +use Kanboard\Action\TaskAssignColorUser; +use Kanboard\Action\TaskAssignCreator; +use Kanboard\Action\TaskAssignCurrentUser; +use Kanboard\Action\TaskAssignCurrentUserColumn; +use Kanboard\Action\TaskAssignSpecificUser; +use Kanboard\Action\TaskAssignUser; +use Kanboard\Action\TaskAssignUserSwimlaneChange; +use Kanboard\Action\TaskClose; +use Kanboard\Action\TaskCloseColumn; +use Kanboard\Action\TaskCreation; +use Kanboard\Action\TaskDuplicateAnotherProject; +use Kanboard\Action\TaskEmail; +use Kanboard\Action\TaskEmailNoActivity; +use Kanboard\Action\TaskMoveAnotherProject; +use Kanboard\Action\TaskMoveColumnAssigned; +use Kanboard\Action\TaskMoveSwimlaneAssigned; +use Kanboard\Action\TaskMoveColumnCategoryChange; +use Kanboard\Action\TaskMoveColumnUnAssigned; +use Kanboard\Action\TaskMoveSwimlaneCategoryChange; +use Kanboard\Action\TaskOpen; +use Kanboard\Action\TaskUpdateStartDate; +use Kanboard\Action\TaskUpdateStartDateOnMoveColumn; +use Kanboard\Action\TaskCloseNoActivity; +use Kanboard\Action\TaskCloseNoActivityColumn; +use Kanboard\Action\TaskCloseNotMovedColumn; +use Kanboard\Action\TaskAssignColorSwimlane; +use Kanboard\Action\TaskAssignPrioritySwimlane; +use Kanboard\Action\SubtaskTimerMoveTaskColumn; +use Kanboard\Action\StopSubtaskTimerMoveTaskColumn; +use Kanboard\Action\TaskMoveColumnOnStartDate; +use Kanboard\Action\TaskAssignDueDateOnMoveColumn; +use Kanboard\Action\TaskAssignToUserOnCreationInColumn; +use Kanboard\Action\TaskAssignCurrentUserColumnIfNoUserAlreadySet; + +/** + * Action Provider + * + * @package Kanboard\ServiceProvider + * @author Frederic Guillot + */ +class ActionProvider implements ServiceProviderInterface +{ + /** + * Register providers + * + * @access public + * @param \Pimple\Container $container + * @return \Pimple\Container + */ + public function register(Container $container) + { + $container['actionManager'] = new ActionManager($container); + $container['actionManager']->register(new CommentCreation($container)); + $container['actionManager']->register(new CommentCreationMoveTaskColumn($container)); + $container['actionManager']->register(new TaskAssignCategorySwimlaneChange($container)); + $container['actionManager']->register(new TaskAssignCategoryColor($container)); + $container['actionManager']->register(new TaskAssignCategoryLabel($container)); + $container['actionManager']->register(new TaskAssignCategoryLink($container)); + $container['actionManager']->register(new TaskAssignColorCategory($container)); + $container['actionManager']->register(new TaskAssignColorColumn($container)); + $container['actionManager']->register(new TaskAssignColorLink($container)); + $container['actionManager']->register(new TaskAssignColorUser($container)); + $container['actionManager']->register(new TaskAssignColorPriority($container)); + $container['actionManager']->register(new TaskAssignCreator($container)); + $container['actionManager']->register(new TaskAssignCurrentUser($container)); + $container['actionManager']->register(new TaskAssignCurrentUserColumn($container)); + $container['actionManager']->register(new TaskAssignSpecificUser($container)); + $container['actionManager']->register(new TaskAssignUser($container)); + $container['actionManager']->register(new TaskAssignUserSwimlaneChange($container)); + $container['actionManager']->register(new TaskClose($container)); + $container['actionManager']->register(new TaskCloseColumn($container)); + $container['actionManager']->register(new TaskCloseNoActivity($container)); + $container['actionManager']->register(new TaskCloseNoActivityColumn($container)); + $container['actionManager']->register(new TaskCloseNotMovedColumn($container)); + $container['actionManager']->register(new TaskCreation($container)); + $container['actionManager']->register(new TaskDuplicateAnotherProject($container)); + $container['actionManager']->register(new TaskEmail($container)); + $container['actionManager']->register(new TaskEmailNoActivity($container)); + $container['actionManager']->register(new TaskMoveAnotherProject($container)); + $container['actionManager']->register(new TaskMoveColumnAssigned($container)); + $container['actionManager']->register(new TaskMoveSwimlaneAssigned($container)); + $container['actionManager']->register(new TaskMoveColumnCategoryChange($container)); + $container['actionManager']->register(new TaskMoveColumnClosed($container)); + $container['actionManager']->register(new TaskMoveColumnNotMovedPeriod($container)); + $container['actionManager']->register(new TaskMoveColumnOnDueDate($container)); + $container['actionManager']->register(new TaskMoveColumnUnAssigned($container)); + $container['actionManager']->register(new TaskMoveSwimlaneCategoryChange($container)); + $container['actionManager']->register(new TaskOpen($container)); + $container['actionManager']->register(new TaskUpdateStartDate($container)); + $container['actionManager']->register(new TaskUpdateStartDateOnMoveColumn($container)); + $container['actionManager']->register(new TaskAssignDueDateOnCreation($container)); + $container['actionManager']->register(new TaskAssignColorSwimlane($container)); + $container['actionManager']->register(new TaskAssignPrioritySwimlane($container)); + $container['actionManager']->register(new TaskAssignColorOnDueDate($container)); + $container['actionManager']->register(new SubtaskTimerMoveTaskColumn($container)); + $container['actionManager']->register(new StopSubtaskTimerMoveTaskColumn($container)); + $container['actionManager']->register(new TaskMoveColumnOnStartDate($container)); + $container['actionManager']->register(new TaskAssignColorOnStartDate($container)); + $container['actionManager']->register(new TaskAssignDueDateOnMoveColumn($container)); + $container['actionManager']->register(new TaskAssignToUserOnCreationInColumn($container)); + $container['actionManager']->register(new TaskAssignCurrentUserColumnIfNoUserAlreadySet($container)); + + return $container; + } +} diff --git a/app/ServiceProvider/ApiProvider.php b/app/ServiceProvider/ApiProvider.php new file mode 100644 index 0000000..1bcb580 --- /dev/null +++ b/app/ServiceProvider/ApiProvider.php @@ -0,0 +1,89 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use JsonRPC\Server; +use Kanboard\Api\Procedure\ActionProcedure; +use Kanboard\Api\Procedure\AppProcedure; +use Kanboard\Api\Procedure\BoardProcedure; +use Kanboard\Api\Procedure\CategoryProcedure; +use Kanboard\Api\Procedure\ColumnProcedure; +use Kanboard\Api\Procedure\CommentProcedure; +use Kanboard\Api\Procedure\ProjectFileProcedure; +use Kanboard\Api\Procedure\ProjectMetadataProcedure; +use Kanboard\Api\Procedure\TagProcedure; +use Kanboard\Api\Procedure\TaskExternalLinkProcedure; +use Kanboard\Api\Procedure\TaskFileProcedure; +use Kanboard\Api\Procedure\GroupProcedure; +use Kanboard\Api\Procedure\GroupMemberProcedure; +use Kanboard\Api\Procedure\LinkProcedure; +use Kanboard\Api\Procedure\MeProcedure; +use Kanboard\Api\Middleware\AuthenticationMiddleware; +use Kanboard\Api\Procedure\ProjectProcedure; +use Kanboard\Api\Procedure\ProjectPermissionProcedure; +use Kanboard\Api\Procedure\SubtaskProcedure; +use Kanboard\Api\Procedure\SubtaskTimeTrackingProcedure; +use Kanboard\Api\Procedure\SwimlaneProcedure; +use Kanboard\Api\Procedure\TaskMetadataProcedure; +use Kanboard\Api\Procedure\TaskProcedure; +use Kanboard\Api\Procedure\TaskLinkProcedure; +use Kanboard\Api\Procedure\TaskTagProcedure; +use Kanboard\Api\Procedure\UserProcedure; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ApiProvider + * + * @package Kanboard\ServiceProvider + * @author Frederic Guillot + */ +class ApiProvider implements ServiceProviderInterface +{ + /** + * Registers services on the given container. + * + * @param Container $container + * @return Container + */ + public function register(Container $container) + { + $server = new Server(); + $server->setAuthenticationHeader(API_AUTHENTICATION_HEADER); + $server->getMiddlewareHandler() + ->withMiddleware(new AuthenticationMiddleware($container)) + ; + + $server->getProcedureHandler() + ->withObject(new MeProcedure($container)) + ->withObject(new ActionProcedure($container)) + ->withObject(new AppProcedure($container)) + ->withObject(new BoardProcedure($container)) + ->withObject(new ColumnProcedure($container)) + ->withObject(new CategoryProcedure($container)) + ->withObject(new CommentProcedure($container)) + ->withObject(new TaskFileProcedure($container)) + ->withObject(new ProjectFileProcedure($container)) + ->withObject(new LinkProcedure($container)) + ->withObject(new ProjectProcedure($container)) + ->withObject(new ProjectPermissionProcedure($container)) + ->withObject(new ProjectMetadataProcedure($container)) + ->withObject(new SubtaskProcedure($container)) + ->withObject(new SubtaskTimeTrackingProcedure($container)) + ->withObject(new SwimlaneProcedure($container)) + ->withObject(new TaskProcedure($container)) + ->withObject(new TaskLinkProcedure($container)) + ->withObject(new TaskExternalLinkProcedure($container)) + ->withObject(new TaskMetadataProcedure($container)) + ->withObject(new TaskTagProcedure($container)) + ->withObject(new UserProcedure($container)) + ->withObject(new GroupProcedure($container)) + ->withObject(new GroupMemberProcedure($container)) + ->withObject(new TagProcedure($container)) + ->withBeforeMethod('beforeProcedure') + ; + + $container['api'] = $server; + return $container; + } +} diff --git a/app/ServiceProvider/AuthenticationProvider.php b/app/ServiceProvider/AuthenticationProvider.php new file mode 100644 index 0000000..9824f79 --- /dev/null +++ b/app/ServiceProvider/AuthenticationProvider.php @@ -0,0 +1,239 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Kanboard\Core\Security\AuthenticationManager; +use Kanboard\Core\Security\AccessMap; +use Kanboard\Core\Security\Authorization; +use Kanboard\Core\Security\Role; +use Kanboard\Auth\ApiAccessTokenAuth; +use Kanboard\Auth\RememberMeAuth; +use Kanboard\Auth\DatabaseAuth; +use Kanboard\Auth\LdapAuth; +use Kanboard\Auth\TotpAuth; +use Kanboard\Auth\ReverseProxyAuth; + +/** + * Authentication Provider + * + * @package Kanboard\ServiceProvider + * @author Frederic Guillot + */ +class AuthenticationProvider implements ServiceProviderInterface +{ + /** + * Register providers + * + * @access public + * @param \Pimple\Container $container + * @return \Pimple\Container + */ + public function register(Container $container) + { + $container['authenticationManager'] = new AuthenticationManager($container); + $container['authenticationManager']->register(new TotpAuth($container)); + + if (REMEMBER_ME_AUTH) { + $container['authenticationManager']->register(new RememberMeAuth($container)); + } + + $container['authenticationManager']->register(new DatabaseAuth($container)); + + if (REVERSE_PROXY_AUTH && ! empty(TRUSTED_PROXY_NETWORKS)) { + $container['authenticationManager']->register(new ReverseProxyAuth($container)); + } + + $container['authenticationManager']->register(new ApiAccessTokenAuth($container)); + + if (LDAP_AUTH) { + $container['authenticationManager']->register(new LdapAuth($container)); + } + + $container['projectAccessMap'] = $this->getProjectAccessMap(); + $container['applicationAccessMap'] = $this->getApplicationAccessMap(); + $container['apiAccessMap'] = $this->getApiAccessMap(); + $container['apiProjectAccessMap'] = $this->getApiProjectAccessMap(); + + $container['projectAuthorization'] = new Authorization($container['projectAccessMap']); + $container['applicationAuthorization'] = new Authorization($container['applicationAccessMap']); + $container['apiAuthorization'] = new Authorization($container['apiAccessMap']); + $container['apiProjectAuthorization'] = new Authorization($container['apiProjectAccessMap']); + + return $container; + } + + /** + * Get ACL for projects + * + * @access public + * @return AccessMap + */ + public function getProjectAccessMap() + { + $acl = new AccessMap; + $acl->setDefaultRole(Role::PROJECT_VIEWER); + $acl->setRoleHierarchy(Role::PROJECT_MANAGER, array(Role::PROJECT_MEMBER, Role::PROJECT_VIEWER)); + $acl->setRoleHierarchy(Role::PROJECT_MEMBER, array(Role::PROJECT_VIEWER)); + + $acl->add('ActionController', '*', Role::PROJECT_MANAGER); + $acl->add('ProjectActionDuplicationController', '*', Role::PROJECT_MANAGER); + $acl->add('ActionCreationController', '*', Role::PROJECT_MANAGER); + $acl->add('AnalyticController', '*', Role::PROJECT_MANAGER); + $acl->add('BoardAjaxController', 'save', Role::PROJECT_MEMBER); + $acl->add('BoardPopoverController', '*', Role::PROJECT_MEMBER); + $acl->add('TaskPopoverController', '*', Role::PROJECT_MEMBER); + $acl->add('CalendarController', 'save', Role::PROJECT_MEMBER); + $acl->add('CategoryController', '*', Role::PROJECT_MANAGER); + $acl->add('ColumnController', '*', Role::PROJECT_MANAGER); + $acl->add('ColumnMoveRestrictionController', '*', Role::PROJECT_MANAGER); + $acl->add('ColumnRestrictionController', '*', Role::PROJECT_MANAGER); + $acl->add('CommentController', array('create', 'save', 'edit', 'update', 'confirm', 'remove'), Role::PROJECT_MEMBER); + $acl->add('CommentListController', array('save'), Role::PROJECT_MEMBER); + $acl->add('CommentMailController', '*', Role::PROJECT_MEMBER); + $acl->add('CustomFilterController', '*', Role::PROJECT_MEMBER); + $acl->add('ExportController', '*', Role::PROJECT_MANAGER); + $acl->add('ExternalTaskCreationController', '*', Role::PROJECT_MEMBER); + $acl->add('TaskFileController', array('screenshot', 'create', 'save', 'remove', 'confirm'), Role::PROJECT_MEMBER); + $acl->add('ProjectViewController', array('share', 'updateSharing', 'integrations', 'updateIntegrations', 'notifications', 'updateNotifications', 'duplicate', 'doDuplication', 'importTasks', 'doTasksImport'), Role::PROJECT_MANAGER); + $acl->add('ProjectPermissionController', '*', Role::PROJECT_MANAGER); + $acl->add('ProjectEditController', '*', Role::PROJECT_MANAGER); + $acl->add('ProjectPredefinedContentController', '*', Role::PROJECT_MANAGER); + $acl->add('ProjectRoleController', '*', Role::PROJECT_MANAGER); + $acl->add('ProjectRoleRestrictionController', '*', Role::PROJECT_MANAGER); + $acl->add('PredefinedTaskDescriptionController', '*', Role::PROJECT_MANAGER); + $acl->add('ProjectFileController', '*', Role::PROJECT_MEMBER); + $acl->add('ProjectUserOverviewController', '*', Role::PROJECT_MANAGER); + $acl->add('ProjectStatusController', '*', Role::PROJECT_MANAGER); + $acl->add('ProjectTagController', '*', Role::PROJECT_MANAGER); + $acl->add('SubtaskController', '*', Role::PROJECT_MEMBER); + $acl->add('SubtaskConverterController', '*', Role::PROJECT_MEMBER); + $acl->add('SubtaskRestrictionController', '*', Role::PROJECT_MEMBER); + $acl->add('SubtaskStatusController', '*', Role::PROJECT_MEMBER); + $acl->add('SwimlaneController', '*', Role::PROJECT_MANAGER); + $acl->add('TaskSuppressionController', '*', Role::PROJECT_MEMBER); + $acl->add('TaskCreationController', '*', Role::PROJECT_MEMBER); + $acl->add('TaskBulkController', '*', Role::PROJECT_MEMBER); + $acl->add('TaskBulkMoveColumnController', '*', Role::PROJECT_MEMBER); + $acl->add('TaskBulkChangePropertyController', '*', Role::PROJECT_MEMBER); + $acl->add('TaskDuplicationController', '*', Role::PROJECT_MEMBER); + $acl->add('TaskRecurrenceController', '*', Role::PROJECT_MEMBER); + $acl->add('TaskImportController', '*', Role::PROJECT_MANAGER); + $acl->add('TaskInternalLinkController', '*', Role::PROJECT_MEMBER); + $acl->add('TaskExternalLinkController', '*', Role::PROJECT_MEMBER); + $acl->add('TaskModificationController', '*', Role::PROJECT_MEMBER); + $acl->add('TaskMovePositionController', '*', Role::PROJECT_MEMBER); + $acl->add('TaskReorderController', '*', Role::PROJECT_MEMBER); + $acl->add('TaskStatusController', '*', Role::PROJECT_MEMBER); + $acl->add('TaskMailController', '*', Role::PROJECT_MEMBER); + $acl->add('UserAjaxController', array('mention'), Role::PROJECT_MEMBER); + + return $acl; + } + + /** + * Get ACL for the application + * + * @access public + * @return AccessMap + */ + public function getApplicationAccessMap() + { + $acl = new AccessMap; + $acl->setDefaultRole(Role::APP_USER); + $acl->setRoleHierarchy(Role::APP_ADMIN, array(Role::APP_MANAGER, Role::APP_USER, Role::APP_PUBLIC)); + $acl->setRoleHierarchy(Role::APP_MANAGER, array(Role::APP_USER, Role::APP_PUBLIC)); + $acl->setRoleHierarchy(Role::APP_USER, array(Role::APP_PUBLIC)); + + $acl->add('AuthController', array('login', 'check'), Role::APP_PUBLIC); + $acl->add('CaptchaController', '*', Role::APP_PUBLIC); + $acl->add('PasswordResetController', '*', Role::APP_PUBLIC); + $acl->add('TaskViewController', 'readonly', Role::APP_PUBLIC); + $acl->add('BoardViewController', 'readonly', Role::APP_PUBLIC); + $acl->add('ICalendarController', '*', Role::APP_PUBLIC); + $acl->add('FeedController', '*', Role::APP_PUBLIC); + $acl->add('AvatarFileController', array('show', 'image'), Role::APP_PUBLIC); + $acl->add('UserInviteController', array('signup', 'register'), Role::APP_PUBLIC); + $acl->add('CronjobController', array('run'), Role::APP_PUBLIC); + + $acl->add('ConfigController', '*', Role::APP_ADMIN); + $acl->add('TagController', '*', Role::APP_ADMIN); + $acl->add('PluginController', '*', Role::APP_ADMIN); + $acl->add('CurrencyController', '*', Role::APP_ADMIN); + $acl->add('GroupListController', '*', Role::APP_ADMIN); + $acl->add('GroupCreationController', '*', Role::APP_ADMIN); + $acl->add('GroupModificationController', '*', Role::APP_ADMIN); + $acl->add('LinkController', '*', Role::APP_ADMIN); + $acl->add('ProjectCreationController', 'create', Role::APP_MANAGER); + $acl->add('ProjectUserOverviewController', '*', Role::APP_MANAGER); + $acl->add('TwoFactorController', 'disable', Role::APP_ADMIN); + $acl->add('UserImportController', '*', Role::APP_ADMIN); + $acl->add('UserCreationController', '*', Role::APP_ADMIN); + $acl->add('UserListController', '*', Role::APP_ADMIN); + $acl->add('UserStatusController', '*', Role::APP_ADMIN); + $acl->add('UserCredentialController', array('changeAuthentication', 'saveAuthentication', 'unlock'), Role::APP_ADMIN); + $acl->add('UserInviteController', array('show', 'save'), Role::APP_ADMIN); + + return $acl; + } + + /** + * Get ACL for the API + * + * @access public + * @return AccessMap + */ + public function getApiAccessMap() + { + $acl = new AccessMap; + $acl->setDefaultRole(Role::APP_USER); + $acl->setRoleHierarchy(Role::APP_ADMIN, array(Role::APP_MANAGER, Role::APP_USER, Role::APP_PUBLIC)); + $acl->setRoleHierarchy(Role::APP_MANAGER, array(Role::APP_USER, Role::APP_PUBLIC)); + + $acl->add('UserProcedure', '*', Role::APP_ADMIN); + $acl->add('GroupMemberProcedure', '*', Role::APP_ADMIN); + $acl->add('GroupProcedure', '*', Role::APP_ADMIN); + $acl->add('LinkProcedure', '*', Role::APP_ADMIN); + $acl->add('TaskProcedure', array('getOverdueTasks'), Role::APP_ADMIN); + $acl->add('ProjectProcedure', array('getAllProjects'), Role::APP_ADMIN); + $acl->add('ProjectProcedure', array('createProject'), Role::APP_MANAGER); + + return $acl; + } + + /** + * Get ACL for the API + * + * @access public + * @return AccessMap + */ + public function getApiProjectAccessMap() + { + $acl = new AccessMap; + $acl->setDefaultRole(Role::PROJECT_VIEWER); + $acl->setRoleHierarchy(Role::PROJECT_MANAGER, array(Role::PROJECT_MEMBER, Role::PROJECT_VIEWER)); + $acl->setRoleHierarchy(Role::PROJECT_MEMBER, array(Role::PROJECT_VIEWER)); + + $acl->add('ActionProcedure', array('removeAction', 'getActions', 'createAction'), Role::PROJECT_MANAGER); + $acl->add('CategoryProcedure', array('removeCategory', 'createCategory', 'updateCategory'), Role::PROJECT_MANAGER); + $acl->add('ColumnProcedure', array('updateColumn', 'addColumn', 'removeColumn', 'changeColumnPosition'), Role::PROJECT_MANAGER); + $acl->add('CommentProcedure', array('removeComment', 'createComment', 'updateComment'), Role::PROJECT_MEMBER); + $acl->add('ProjectPermissionProcedure', array('addProjectUser', 'addProjectGroup', 'removeProjectUser', 'removeProjectGroup', 'changeProjectUserRole', 'changeProjectGroupRole'), Role::PROJECT_MANAGER); + $acl->add('ProjectProcedure', array('updateProject', 'removeProject', 'enableProject', 'disableProject', 'enableProjectPublicAccess', 'disableProjectPublicAccess'), Role::PROJECT_MANAGER); + $acl->add('SubtaskProcedure', array('removeSubtask', 'createSubtask', 'updateSubtask'), Role::PROJECT_MEMBER); + $acl->add('SubtaskTimeTrackingProcedure', array('setSubtaskStartTime', 'setSubtaskEndTime'), Role::PROJECT_MEMBER); + $acl->add('SwimlaneProcedure', array('addSwimlane', 'updateSwimlane', 'removeSwimlane', 'disableSwimlane', 'enableSwimlane', 'changeSwimlanePosition'), Role::PROJECT_MANAGER); + $acl->add('ProjectFileProcedure', array('createProjectFile', 'removeProjectFile', 'removeAllProjectFiles'), Role::PROJECT_MEMBER); + $acl->add('TaskFileProcedure', array('createTaskFile', 'removeTaskFile', 'removeAllTaskFiles'), Role::PROJECT_MEMBER); + $acl->add('TaskLinkProcedure', array('createTaskLink', 'updateTaskLink', 'removeTaskLink'), Role::PROJECT_MEMBER); + $acl->add('TaskExternalLinkProcedure', array('createExternalTaskLink', 'updateExternalTaskLink', 'removeExternalTaskLink'), Role::PROJECT_MEMBER); + $acl->add('TaskProcedure', array('openTask', 'closeTask', 'removeTask', 'moveTaskPosition', 'moveTaskToProject', 'duplicateTaskToProject', 'createTask', 'updateTask'), Role::PROJECT_MEMBER); + $acl->add('TaskTagProcedure', array('setTaskTags'), Role::PROJECT_MEMBER); + $acl->add('TagProcedure', array('createTag', 'updateTag', 'removeTag'), Role::PROJECT_MEMBER); + $acl->add('ProjectMetaDataProcedure', array('saveProjectMetadata', 'removeProjectMetadata'), Role::PROJECT_MEMBER); + $acl->add('TaskMetadataProcedure', array('saveTaskMetadata', 'removeTaskMetadata'), Role::PROJECT_MEMBER); + + return $acl; + } +} diff --git a/app/ServiceProvider/AvatarProvider.php b/app/ServiceProvider/AvatarProvider.php new file mode 100644 index 0000000..e03a047 --- /dev/null +++ b/app/ServiceProvider/AvatarProvider.php @@ -0,0 +1,33 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Kanboard\Core\User\Avatar\AvatarManager; +use Kanboard\User\Avatar\AvatarFileProvider; +use Kanboard\User\Avatar\LetterAvatarProvider; + +/** + * Avatar Provider + * + * @package Kanboard\ServiceProvider + * @author Frederic Guillot + */ +class AvatarProvider implements ServiceProviderInterface +{ + /** + * Register providers + * + * @access public + * @param \Pimple\Container $container + * @return \Pimple\Container + */ + public function register(Container $container) + { + $container['avatarManager'] = new AvatarManager; + $container['avatarManager']->register(new LetterAvatarProvider($container)); + $container['avatarManager']->register(new AvatarFileProvider($container)); + return $container; + } +} diff --git a/app/ServiceProvider/CacheProvider.php b/app/ServiceProvider/CacheProvider.php new file mode 100644 index 0000000..9d29638 --- /dev/null +++ b/app/ServiceProvider/CacheProvider.php @@ -0,0 +1,76 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use Kanboard\Core\Cache\MemoryCache; +use Kanboard\Decorator\ColumnMoveRestrictionCacheDecorator; +use Kanboard\Decorator\ColumnRestrictionCacheDecorator; +use Kanboard\Decorator\MetadataCacheDecorator; +use Kanboard\Decorator\ProjectRoleRestrictionCacheDecorator; +use Kanboard\Decorator\UserCacheDecorator; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Cache Provider + * + * @package Kanboard\ServiceProvider + * @author Frederic Guillot + */ +class CacheProvider implements ServiceProviderInterface +{ + /** + * Register providers + * + * @access public + * @param \Pimple\Container $container + * @return \Pimple\Container + */ + public function register(Container $container) + { + $container['memoryCache'] = function () { + return new MemoryCache(); + }; + + $container['cacheDriver'] = $container['memoryCache']; + + $container['userCacheDecorator'] = function ($c) { + return new UserCacheDecorator( + $c['memoryCache'], + $c['userModel'] + ); + }; + + $container['userMetadataCacheDecorator'] = function ($c) { + return new MetadataCacheDecorator( + $c['cacheDriver'], + $c['userMetadataModel'], + 'user.metadata.', + $c['userSession']->getId() + ); + }; + + $container['columnMoveRestrictionCacheDecorator'] = function ($c) { + return new ColumnMoveRestrictionCacheDecorator( + $c['memoryCache'], + $c['columnMoveRestrictionModel'] + ); + }; + + $container['columnRestrictionCacheDecorator'] = function ($c) { + return new ColumnRestrictionCacheDecorator( + $c['memoryCache'], + $c['columnRestrictionModel'] + ); + }; + + $container['projectRoleRestrictionCacheDecorator'] = function ($c) { + return new ProjectRoleRestrictionCacheDecorator( + $c['memoryCache'], + $c['projectRoleRestrictionModel'] + ); + }; + + return $container; + } +} diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php new file mode 100644 index 0000000..7bcda1a --- /dev/null +++ b/app/ServiceProvider/ClassProvider.php @@ -0,0 +1,195 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Kanboard\Core\Paginator; +use Kanboard\Core\Http\OAuth2; +use Kanboard\Core\Tool; +use Kanboard\Core\Http\Client as HttpClient; + +/** + * Class ClassProvider + * + * @package Kanboard\ServiceProvider + * @author Frederic Guillot + */ +class ClassProvider implements ServiceProviderInterface +{ + private $classes = array( + 'Analytic' => array( + 'TaskDistributionAnalytic', + 'UserDistributionAnalytic', + 'EstimatedTimeComparisonAnalytic', + 'AverageLeadCycleTimeAnalytic', + 'AverageTimeSpentColumnAnalytic', + 'EstimatedActualColumnAnalytic', + ), + 'Model' => array( + 'ActionModel', + 'ActionParameterModel', + 'AvatarFileModel', + 'BoardModel', + 'CaptchaModel', + 'CategoryModel', + 'ColorModel', + 'ColumnModel', + 'ColumnRestrictionModel', + 'ColumnMoveRestrictionModel', + 'CommentModel', + 'ConfigModel', + 'CurrencyModel', + 'CustomFilterModel', + 'GroupModel', + 'GroupMemberModel', + 'InviteModel', + 'LanguageModel', + 'LastLoginModel', + 'LinkModel', + 'NotificationModel', + 'PasswordResetModel', + 'PredefinedTaskDescriptionModel', + 'ProjectModel', + 'ProjectFileModel', + 'ProjectActivityModel', + 'ProjectDuplicationModel', + 'ProjectDailyColumnStatsModel', + 'ProjectDailyStatsModel', + 'ProjectPermissionModel', + 'ProjectNotificationModel', + 'ProjectMetadataModel', + 'ProjectGroupRoleModel', + 'ProjectRoleModel', + 'ProjectRoleRestrictionModel', + 'ProjectTaskDuplicationModel', + 'ProjectTaskPriorityModel', + 'ProjectUserRoleModel', + 'RememberMeSessionModel', + 'SubtaskModel', + 'SubtaskPositionModel', + 'SubtaskStatusModel', + 'SubtaskTaskConversionModel', + 'SubtaskTimeTrackingModel', + 'SwimlaneModel', + 'TagDuplicationModel', + 'TagModel', + 'TaskModel', + 'TaskAnalyticModel', + 'TaskCreationModel', + 'TaskDuplicationModel', + 'TaskProjectDuplicationModel', + 'TaskProjectMoveModel', + 'TaskRecurrenceModel', + 'TaskExternalLinkModel', + 'TaskFinderModel', + 'TaskFileModel', + 'TaskLinkModel', + 'TaskModificationModel', + 'TaskPositionModel', + 'TaskReorderModel', + 'TaskStatusModel', + 'TaskTagModel', + 'TaskMetadataModel', + 'ThemeModel', + 'TimezoneModel', + 'TransitionModel', + 'UserModel', + 'UserLockingModel', + 'UserNotificationModel', + 'UserNotificationFilterModel', + 'UserUnreadNotificationModel', + 'UserMetadataModel', + ), + 'Validator' => array( + 'ActionValidator', + 'AuthValidator', + 'CategoryValidator', + 'ColumnMoveRestrictionValidator', + 'ColumnRestrictionValidator', + 'ColumnValidator', + 'CommentValidator', + 'ConfigValidator', + 'CurrencyValidator', + 'CustomFilterValidator', + 'ExternalLinkValidator', + 'GroupValidator', + 'LinkValidator', + 'PasswordResetValidator', + 'ProjectValidator', + 'ProjectRoleValidator', + 'SubtaskValidator', + 'SwimlaneValidator', + 'TagValidator', + 'TaskLinkValidator', + 'TaskValidator', + 'UserValidator', + 'PredefinedTaskDescriptionValidator', + ), + 'Import' => array( + 'UserImport', + ), + 'Export' => array( + 'SubtaskExport', + 'TaskExport', + 'TransitionExport', + ), + 'Pagination' => array( + 'DashboardPagination', + 'ProjectPagination', + 'SubtaskPagination', + 'TaskPagination', + 'UserPagination', + ), + 'Core' => array( + 'DateParser', + 'Lexer', + ), + 'Core\Event' => array( + 'EventManager', + ), + 'Core\Http' => array( + 'Request', + 'Response', + 'RememberMeCookie', + ), + 'Core\Plugin' => array( + 'Hook', + ), + 'Core\Security' => array( + 'Token', + 'Role', + ), + 'Core\User' => array( + 'GroupSync', + 'UserSync', + 'UserSession', + 'UserProfile', + ) + ); + + public function register(Container $container) + { + Tool::buildDIC($container, $this->classes); + + $container['paginator'] = $container->factory(function ($c) { + return new Paginator($c); + }); + + $container['oauth'] = $container->factory(function ($c) { + return new OAuth2($c); + }); + + $container['httpClient'] = function ($c) { + return new HttpClient($c); + }; + + $container['cspRules'] = array( + 'default-src' => "'self'", + 'style-src' => "'self' 'unsafe-inline'", + 'img-src' => '* data:', + ); + + return $container; + } +} diff --git a/app/ServiceProvider/CommandProvider.php b/app/ServiceProvider/CommandProvider.php new file mode 100644 index 0000000..f08f76c --- /dev/null +++ b/app/ServiceProvider/CommandProvider.php @@ -0,0 +1,78 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use Kanboard\Console\CronjobCommand; +use Kanboard\Console\DatabaseMigrationCommand; +use Kanboard\Console\DatabaseVersionCommand; +use Kanboard\Console\JobCommand; +use Kanboard\Console\LocaleComparatorCommand; +use Kanboard\Console\LocaleSyncCommand; +use Kanboard\Console\PluginInstallCommand; +use Kanboard\Console\PluginUninstallCommand; +use Kanboard\Console\PluginUpgradeCommand; +use Kanboard\Console\ProjectActivityArchiveCommand; +use Kanboard\Console\ProjectArchiveCommand; +use Kanboard\Console\ProjectDailyColumnStatsExportCommand; +use Kanboard\Console\ProjectDailyStatsCalculationCommand; +use Kanboard\Console\ResetPasswordCommand; +use Kanboard\Console\ResetTwoFactorCommand; +use Kanboard\Console\SubtaskExportCommand; +use Kanboard\Console\TaskExportCommand; +use Kanboard\Console\TaskOverdueNotificationCommand; +use Kanboard\Console\TaskTriggerCommand; +use Kanboard\Console\TransitionExportCommand; +use Kanboard\Console\VersionCommand; +use Kanboard\Console\WorkerCommand; +use Kanboard\Console\CssCommand; +use Kanboard\Console\JsCommand; +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Symfony\Component\Console\Application; + +/** + * Class CommandProvider + * + * @package Kanboard\ServiceProvider + * @author Frederic Guillot + */ +class CommandProvider implements ServiceProviderInterface +{ + /** + * Registers services on the given container. + * + * @param Container $container + * @return Container + */ + public function register(Container $container) + { + $application = new Application('Kanboard', APP_VERSION); + $application->add(new TaskOverdueNotificationCommand($container)); + $application->add(new SubtaskExportCommand($container)); + $application->add(new TaskExportCommand($container)); + $application->add(new ProjectArchiveCommand($container)); + $application->add(new ProjectActivityArchiveCommand($container)); + $application->add(new ProjectDailyStatsCalculationCommand($container)); + $application->add(new ProjectDailyColumnStatsExportCommand($container)); + $application->add(new TransitionExportCommand($container)); + $application->add(new LocaleSyncCommand($container)); + $application->add(new LocaleComparatorCommand($container)); + $application->add(new TaskTriggerCommand($container)); + $application->add(new CronjobCommand($container)); + $application->add(new WorkerCommand($container)); + $application->add(new JobCommand($container)); + $application->add(new ResetPasswordCommand($container)); + $application->add(new ResetTwoFactorCommand($container)); + $application->add(new PluginUpgradeCommand($container)); + $application->add(new PluginInstallCommand($container)); + $application->add(new PluginUninstallCommand($container)); + $application->add(new DatabaseMigrationCommand($container)); + $application->add(new DatabaseVersionCommand($container)); + $application->add(new VersionCommand($container)); + $application->add(new CssCommand($container)); + $application->add(new JsCommand($container)); + + $container['cli'] = $application; + return $container; + } +} diff --git a/app/ServiceProvider/DatabaseProvider.php b/app/ServiceProvider/DatabaseProvider.php new file mode 100644 index 0000000..dfa5e06 --- /dev/null +++ b/app/ServiceProvider/DatabaseProvider.php @@ -0,0 +1,196 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use LogicException; +use RuntimeException; +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use PicoDb\Database; + +/** + * Class DatabaseProvider + * + * @package Kanboard\ServiceProvider + * @author Frederic Guillot + */ +class DatabaseProvider implements ServiceProviderInterface +{ + /** + * Register provider + * + * @access public + * @param Container $container + * @return Container + */ + public function register(Container $container) + { + $container['db'] = $this->getInstance(); + + if (DB_RUN_MIGRATIONS) { + self::runMigrations($container['db']); + } + + if (DEBUG) { + $container['db']->getStatementHandler() + ->withLogging() + ->withStopWatch() + ; + } + + return $container; + } + + /** + * Setup the database driver + * + * @access public + * @return \PicoDb\Database + */ + public function getInstance() + { + switch (DB_DRIVER) { + case 'sqlite': + $db = $this->getSqliteInstance(); + break; + case 'mysql': + $db = $this->getMysqlInstance(); + break; + case 'postgres': + $db = $this->getPostgresInstance(); + break; + case 'dblib': + $db = $this->getMssqlInstance(); + break; + case 'mssql': + $db = $this->getMssqlInstance(); + break; + case 'odbc': + $db = $this->getMssqlInstance(); + break; + default: + throw new LogicException('Database driver not supported'); + } + + return $db; + } + + /** + * Get current database version + * + * @static + * @access public + * @param Database $db + * @return int + */ + public static function getSchemaVersion(Database $db) + { + return $db->getDriver()->getSchemaVersion(); + } + + /** + * Execute database migrations + * + * @static + * @access public + * @throws RuntimeException + * @param Database $db + * @return bool + */ + public static function runMigrations(Database $db) + { + if (! $db->schema()->check(\Schema\VERSION)) { + $messages = $db->getLogMessages(); + throw new RuntimeException('Unable to run SQL migrations: '.implode(', ', $messages).' (You may have to fix it manually)'); + } + + return true; + } + + /** + * Setup the Sqlite database driver + * + * @access private + * @return \PicoDb\Database + */ + private function getSqliteInstance() + { + require_once __DIR__.'/../Schema/Sqlite.php'; + + return new Database([ + 'driver' => 'sqlite', + 'filename' => DB_FILENAME, + 'wal_mode' => DB_WAL_MODE, + ]); + } + + /** + * Setup the Mysql database driver + * + * @access private + * @return \PicoDb\Database + */ + private function getMysqlInstance() + { + require_once __DIR__.'/../Schema/Mysql.php'; + + return new Database(array( + 'driver' => 'mysql', + 'hostname' => DB_HOSTNAME, + 'username' => DB_USERNAME, + 'password' => DB_PASSWORD, + 'database' => DB_NAME, + 'charset' => 'utf8mb4', + 'port' => DB_PORT, + 'ssl_key' => DB_SSL_KEY, + 'ssl_ca' => DB_SSL_CA, + 'ssl_cert' => DB_SSL_CERT, + 'verify_server_cert' => DB_VERIFY_SERVER_CERT, + 'timeout' => DB_TIMEOUT, + )); + } + + /** + * Setup the Postgres database driver + * + * @access private + * @return \PicoDb\Database + */ + private function getPostgresInstance() + { + require_once __DIR__.'/../Schema/Postgres.php'; + + return new Database(array( + 'driver' => 'postgres', + 'hostname' => DB_HOSTNAME, + 'username' => DB_USERNAME, + 'password' => DB_PASSWORD, + 'database' => DB_NAME, + 'port' => DB_PORT, + 'timeout' => DB_TIMEOUT, + )); + } + + /** + * Setup the MSSQL database driver + * + * @access private + * @return \PicoDb\Database + */ + private function getMssqlInstance() + { + require_once __DIR__.'/../Schema/Mssql.php'; + + return new Database(array( + 'driver' => DB_DRIVER, + 'hostname' => DB_HOSTNAME, + 'username' => DB_USERNAME, + 'password' => DB_PASSWORD, + 'database' => DB_NAME, + 'port' => DB_PORT, + 'odbc-dsn' => DB_ODBC_DSN, + 'timeout' => DB_TIMEOUT, + 'appname' => 'Kanboard', + )); + } +} diff --git a/app/ServiceProvider/EventDispatcherProvider.php b/app/ServiceProvider/EventDispatcherProvider.php new file mode 100644 index 0000000..ebf42cb --- /dev/null +++ b/app/ServiceProvider/EventDispatcherProvider.php @@ -0,0 +1,42 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use Kanboard\Subscriber\LdapUserPhotoSubscriber; +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Kanboard\Subscriber\AuthSubscriber; +use Kanboard\Subscriber\BootstrapSubscriber; +use Kanboard\Subscriber\NotificationSubscriber; +use Kanboard\Subscriber\ProjectDailySummarySubscriber; +use Kanboard\Subscriber\ProjectModificationDateSubscriber; +use Kanboard\Subscriber\TransitionSubscriber; +use Kanboard\Subscriber\RecurringTaskSubscriber; + +/** + * Class EventDispatcherProvider + * + * @package Kanboard\ServiceProvider + * @author Frederic Guillot + */ +class EventDispatcherProvider implements ServiceProviderInterface +{ + public function register(Container $container) + { + $container['dispatcher'] = new EventDispatcher; + $container['dispatcher']->addSubscriber(new BootstrapSubscriber($container)); + $container['dispatcher']->addSubscriber(new AuthSubscriber($container)); + $container['dispatcher']->addSubscriber(new ProjectDailySummarySubscriber($container)); + $container['dispatcher']->addSubscriber(new ProjectModificationDateSubscriber($container)); + $container['dispatcher']->addSubscriber(new NotificationSubscriber($container)); + $container['dispatcher']->addSubscriber(new TransitionSubscriber($container)); + $container['dispatcher']->addSubscriber(new RecurringTaskSubscriber($container)); + + if (LDAP_AUTH && LDAP_USER_ATTRIBUTE_PHOTO !== '') { + $container['dispatcher']->addSubscriber(new LdapUserPhotoSubscriber($container)); + } + + return $container; + } +} diff --git a/app/ServiceProvider/ExternalLinkProvider.php b/app/ServiceProvider/ExternalLinkProvider.php new file mode 100644 index 0000000..2cec768 --- /dev/null +++ b/app/ServiceProvider/ExternalLinkProvider.php @@ -0,0 +1,36 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Kanboard\Core\ExternalLink\ExternalLinkManager; +use Kanboard\ExternalLink\WebLinkProvider; +use Kanboard\ExternalLink\AttachmentLinkProvider; +use Kanboard\ExternalLink\FileLinkProvider; + +/** + * External Link Provider + * + * @package Kanboard\ServiceProvider + * @author Frederic Guillot + */ +class ExternalLinkProvider implements ServiceProviderInterface +{ + /** + * Register providers + * + * @access public + * @param \Pimple\Container $container + * @return \Pimple\Container + */ + public function register(Container $container) + { + $container['externalLinkManager'] = new ExternalLinkManager($container); + $container['externalLinkManager']->register(new WebLinkProvider($container)); + $container['externalLinkManager']->register(new AttachmentLinkProvider($container)); + $container['externalLinkManager']->register(new FileLinkProvider($container)); + + return $container; + } +} diff --git a/app/ServiceProvider/ExternalTaskProvider.php b/app/ServiceProvider/ExternalTaskProvider.php new file mode 100644 index 0000000..52484ae --- /dev/null +++ b/app/ServiceProvider/ExternalTaskProvider.php @@ -0,0 +1,29 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use Kanboard\Core\ExternalTask\ExternalTaskManager; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ExternalTaskProvider + * + * @package Kanboard\ServiceProvider + * @author Frederic Guillot + */ +class ExternalTaskProvider implements ServiceProviderInterface +{ + /** + * Register providers + * + * @access public + * @param \Pimple\Container $container + * @return \Pimple\Container + */ + public function register(Container $container) + { + $container['externalTaskManager'] = new ExternalTaskManager(); + return $container; + } +} diff --git a/app/ServiceProvider/FilterProvider.php b/app/ServiceProvider/FilterProvider.php new file mode 100644 index 0000000..fb94097 --- /dev/null +++ b/app/ServiceProvider/FilterProvider.php @@ -0,0 +1,231 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use Kanboard\Core\Filter\LexerBuilder; +use Kanboard\Core\Filter\QueryBuilder; +use Kanboard\Filter\ProjectActivityCreationDateFilter; +use Kanboard\Filter\ProjectActivityCreatorFilter; +use Kanboard\Filter\ProjectActivityProjectNameFilter; +use Kanboard\Filter\ProjectActivityTaskStatusFilter; +use Kanboard\Filter\ProjectActivityTaskTitleFilter; +use Kanboard\Filter\TaskAssigneeFilter; +use Kanboard\Filter\TaskCategoryFilter; +use Kanboard\Filter\TaskColorFilter; +use Kanboard\Filter\TaskColumnFilter; +use Kanboard\Filter\TaskCommentFilter; +use Kanboard\Filter\TaskCompletionDateFilter; +use Kanboard\Filter\TaskCompletionDateRangeFilter; +use Kanboard\Filter\TaskCreationDateFilter; +use Kanboard\Filter\TaskCreationDateRangeFilter; +use Kanboard\Filter\TaskCreatorFilter; +use Kanboard\Filter\TaskDescriptionFilter; +use Kanboard\Filter\TaskDueDateFilter; +use Kanboard\Filter\TaskStartDateFilter; +use Kanboard\Filter\TaskIdFilter; +use Kanboard\Filter\TaskLinkFilter; +use Kanboard\Filter\TaskModificationDateFilter; +use Kanboard\Filter\TaskModificationDateRangeFilter; +use Kanboard\Filter\TaskMovedDateFilter; +use Kanboard\Filter\TaskMovedDateRangeFilter; +use Kanboard\Filter\TaskPriorityFilter; +use Kanboard\Filter\TaskProjectFilter; +use Kanboard\Filter\TaskReferenceFilter; +use Kanboard\Filter\TaskScoreFilter; +use Kanboard\Filter\TaskStatusFilter; +use Kanboard\Filter\TaskSubtaskAssigneeFilter; +use Kanboard\Filter\TaskSwimlaneFilter; +use Kanboard\Filter\TaskTagFilter; +use Kanboard\Filter\TaskTitleFilter; +use Kanboard\Model\ProjectModel; +use Kanboard\Model\ProjectGroupRoleModel; +use Kanboard\Model\ProjectUserRoleModel; +use Kanboard\Model\UserModel; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Filter Provider + * + * @package Kanboard\ServiceProvider + * @author Frederic Guillot + */ +class FilterProvider implements ServiceProviderInterface +{ + /** + * Register providers + * + * @access public + * @param \Pimple\Container $container + * @return \Pimple\Container + */ + public function register(Container $container) + { + $this->createUserFilter($container); + $this->createProjectFilter($container); + $this->createTaskFilter($container); + return $container; + } + + public function createUserFilter(Container $container) + { + $container['userQuery'] = $container->factory(function ($c) { + $builder = new QueryBuilder(); + $builder->withQuery($c['db']->table(UserModel::TABLE)); + return $builder; + }); + + return $container; + } + + public function createProjectFilter(Container $container) + { + $container['projectGroupRoleQuery'] = $container->factory(function ($c) { + $builder = new QueryBuilder(); + $builder->withQuery($c['db']->table(ProjectGroupRoleModel::TABLE)); + return $builder; + }); + + $container['projectUserRoleQuery'] = $container->factory(function ($c) { + $builder = new QueryBuilder(); + $builder->withQuery($c['db']->table(ProjectUserRoleModel::TABLE)); + return $builder; + }); + + $container['projectQuery'] = $container->factory(function ($c) { + $builder = new QueryBuilder(); + $builder->withQuery($c['db']->table(ProjectModel::TABLE)); + return $builder; + }); + + $container['projectActivityLexer'] = $container->factory(function ($c) { + $builder = new LexerBuilder(); + $builder + ->withQuery($c['projectActivityModel']->getQuery()) + ->withFilter(new ProjectActivityTaskTitleFilter(), true) + ->withFilter(new ProjectActivityTaskStatusFilter()) + ->withFilter(new ProjectActivityProjectNameFilter()) + ->withFilter( + ProjectActivityCreationDateFilter::getInstance() + ->setDateParser($c['dateParser']) + ) + ->withFilter( + ProjectActivityCreatorFilter::getInstance() + ->setCurrentUserId($c['userSession']->getId()) + ) + ; + + return $builder; + }); + + $container['projectActivityQuery'] = $container->factory(function ($c) { + $builder = new QueryBuilder(); + $builder->withQuery($c['projectActivityModel']->getQuery()); + + return $builder; + }); + + return $container; + } + + public function createTaskFilter(Container $container) + { + $container['taskQuery'] = $container->factory(function ($c) { + $builder = new QueryBuilder(); + $builder->withQuery($c['taskFinderModel']->getExtendedQuery()); + return $builder; + }); + + $container['taskLexer'] = $container->factory(function ($c) { + $builder = new LexerBuilder(); + + $builder + ->withQuery($c['taskFinderModel']->getExtendedQuery()) + ->withFilter( + TaskAssigneeFilter::getInstance() + ->setCurrentUserId($c['userSession']->getId()) + ) + ->withFilter(new TaskCategoryFilter()) + ->withFilter( + TaskColorFilter::getInstance() + ->setColorModel($c['colorModel']) + ) + ->withFilter(new TaskPriorityFilter()) + ->withFilter(new TaskColumnFilter()) + ->withFilter( + TaskCommentFilter::getInstance() + ->setDatabase($c['db']) + ) + ->withFilter( + TaskCreationDateFilter::getInstance() + ->setDateParser($c['dateParser']) + ) + ->withFilter( + TaskCreationDateRangeFilter::getInstance() + ->setDateParser($c['dateParser']) + ) + ->withFilter( + TaskCreatorFilter::getInstance() + ->setCurrentUserId($c['userSession']->getId()) + ) + ->withFilter(new TaskDescriptionFilter()) + ->withFilter( + TaskDueDateFilter::getInstance() + ->setDateParser($c['dateParser']) + ) + ->withFilter( + TaskStartDateFilter::getInstance() + ->setDateParser($c['dateParser']) + ) + ->withFilter( + TaskCompletionDateFilter::getInstance() + ->setDateparser($c['dateParser']) + ) + ->withFilter( + TaskCompletionDateRangeFilter::getInstance() + ->setDateparser($c['dateParser']) + ) + ->withFilter(new TaskIdFilter()) + ->withFilter( + TaskLinkFilter::getInstance() + ->setDatabase($c['db']) + ) + ->withFilter( + TaskModificationDateFilter::getInstance() + ->setDateParser($c['dateParser']) + ) + ->withFilter( + TaskModificationDateRangeFilter::getInstance() + ->setDateParser($c['dateParser']) + ) + ->withFilter( + TaskMovedDateFilter::getInstance() + ->setDateParser($c['dateParser']) + ) + ->withFilter( + TaskMovedDateRangeFilter::getInstance() + ->setDateParser($c['dateParser']) + ) + ->withFilter(new TaskProjectFilter()) + ->withFilter(new TaskReferenceFilter()) + ->withFilter(new TaskScoreFilter()) + ->withFilter(new TaskStatusFilter()) + ->withFilter( + TaskSubtaskAssigneeFilter::getInstance() + ->setCurrentUserId($c['userSession']->getId()) + ->setDatabase($c['db']) + ) + ->withFilter(new TaskSwimlaneFilter()) + ->withFilter( + TaskTagFilter::getInstance() + ->setDatabase($c['db']) + ) + ->withFilter(new TaskTitleFilter(), true) + ; + + return $builder; + }); + + return $container; + } +} diff --git a/app/ServiceProvider/FormatterProvider.php b/app/ServiceProvider/FormatterProvider.php new file mode 100644 index 0000000..efc85d0 --- /dev/null +++ b/app/ServiceProvider/FormatterProvider.php @@ -0,0 +1,53 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use Kanboard\Core\Tool; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class FormatterProvider + * + * @package Kanboard\ServiceProvider + * @author Frederic Guillot + */ +class FormatterProvider implements ServiceProviderInterface +{ + protected $formatters = array( + 'Formatter' => array( + 'BoardColumnFormatter', + 'BoardFormatter', + 'BoardSwimlaneFormatter', + 'BoardTaskFormatter', + 'GroupAutoCompleteFormatter', + 'ProjectActivityEventFormatter', + 'ProjectApiFormatter', + 'ProjectsApiFormatter', + 'SubtaskListFormatter', + 'SubtaskTimeTrackingCalendarFormatter', + 'TaskApiFormatter', + 'TasksApiFormatter', + 'TaskAutoCompleteFormatter', + 'TaskICalFormatter', + 'TaskListFormatter', + 'TaskListSubtaskFormatter', + 'TaskListSubtaskAssigneeFormatter', + 'TaskSuggestMenuFormatter', + 'UserAutoCompleteFormatter', + 'UserMentionFormatter', + ) + ); + + /** + * Registers services on the given container. + * + * @param Container $container + * @return Container + */ + public function register(Container $container) + { + Tool::buildFactories($container, $this->formatters); + return $container; + } +} diff --git a/app/ServiceProvider/GroupProvider.php b/app/ServiceProvider/GroupProvider.php new file mode 100644 index 0000000..86f5d11 --- /dev/null +++ b/app/ServiceProvider/GroupProvider.php @@ -0,0 +1,40 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Kanboard\Core\Group\GroupManager; +use Kanboard\Group\DatabaseBackendGroupProvider; +use Kanboard\Group\LdapBackendGroupProvider; + +/** + * Group Provider + * + * @package Kanboard\ServiceProvider + * @author Frederic Guillot + */ +class GroupProvider implements ServiceProviderInterface +{ + /** + * Register providers + * + * @access public + * @param \Pimple\Container $container + * @return \Pimple\Container + */ + public function register(Container $container) + { + $container['groupManager'] = new GroupManager(); + + if (DB_GROUP_PROVIDER) { + $container['groupManager']->register(new DatabaseBackendGroupProvider($container)); + } + + if (LDAP_AUTH && LDAP_GROUP_PROVIDER) { + $container['groupManager']->register(new LdapBackendGroupProvider($container)); + } + + return $container; + } +} diff --git a/app/ServiceProvider/HelperProvider.php b/app/ServiceProvider/HelperProvider.php new file mode 100644 index 0000000..f0f7ac0 --- /dev/null +++ b/app/ServiceProvider/HelperProvider.php @@ -0,0 +1,47 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use Kanboard\Core\Helper; +use Kanboard\Core\Template; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class HelperProvider + * + * @package Kanboard\ServiceProvider + * @author Frederic Guillot + */ +class HelperProvider implements ServiceProviderInterface +{ + public function register(Container $container) + { + $container['helper'] = new Helper($container); + $container['helper']->register('app', '\Kanboard\Helper\AppHelper'); + $container['helper']->register('asset', '\Kanboard\Helper\AssetHelper'); + $container['helper']->register('board', '\Kanboard\Helper\BoardHelper'); + $container['helper']->register('comment', '\Kanboard\Helper\CommentHelper'); + $container['helper']->register('dt', '\Kanboard\Helper\DateHelper'); + $container['helper']->register('file', '\Kanboard\Helper\FileHelper'); + $container['helper']->register('form', '\Kanboard\Helper\FormHelper'); + $container['helper']->register('hook', '\Kanboard\Helper\HookHelper'); + $container['helper']->register('layout', '\Kanboard\Helper\LayoutHelper'); + $container['helper']->register('model', '\Kanboard\Helper\ModelHelper'); + $container['helper']->register('subtask', '\Kanboard\Helper\SubtaskHelper'); + $container['helper']->register('task', '\Kanboard\Helper\TaskHelper'); + $container['helper']->register('text', '\Kanboard\Helper\TextHelper'); + $container['helper']->register('url', '\Kanboard\Helper\UrlHelper'); + $container['helper']->register('user', '\Kanboard\Helper\UserHelper'); + $container['helper']->register('avatar', '\Kanboard\Helper\AvatarHelper'); + $container['helper']->register('projectRole', '\Kanboard\Helper\ProjectRoleHelper'); + $container['helper']->register('projectHeader', '\Kanboard\Helper\ProjectHeaderHelper'); + $container['helper']->register('projectActivity', '\Kanboard\Helper\ProjectActivityHelper'); + $container['helper']->register('mail', '\Kanboard\Helper\MailHelper'); + $container['helper']->register('modal', '\Kanboard\Helper\ModalHelper'); + + $container['template'] = new Template($container['helper']); + + return $container; + } +} diff --git a/app/ServiceProvider/JobProvider.php b/app/ServiceProvider/JobProvider.php new file mode 100644 index 0000000..4e5e0f1 --- /dev/null +++ b/app/ServiceProvider/JobProvider.php @@ -0,0 +1,72 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use Kanboard\Job\CommentEventJob; +use Kanboard\Job\NotificationJob; +use Kanboard\Job\ProjectFileEventJob; +use Kanboard\Job\ProjectMetricJob; +use Kanboard\Job\SubtaskEventJob; +use Kanboard\Job\TaskEventJob; +use Kanboard\Job\TaskFileEventJob; +use Kanboard\Job\TaskLinkEventJob; +use Kanboard\Job\UserMentionJob; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class JobProvider + * + * @package Kanboard\ServiceProvider + * @author Frederic Guillot + */ +class JobProvider implements ServiceProviderInterface +{ + /** + * Register providers + * + * @access public + * @param \Pimple\Container $container + * @return \Pimple\Container + */ + public function register(Container $container) + { + $container['commentEventJob'] = $container->factory(function ($c) { + return new CommentEventJob($c); + }); + + $container['subtaskEventJob'] = $container->factory(function ($c) { + return new SubtaskEventJob($c); + }); + + $container['taskEventJob'] = $container->factory(function ($c) { + return new TaskEventJob($c); + }); + + $container['taskFileEventJob'] = $container->factory(function ($c) { + return new TaskFileEventJob($c); + }); + + $container['taskLinkEventJob'] = $container->factory(function ($c) { + return new TaskLinkEventJob($c); + }); + + $container['projectFileEventJob'] = $container->factory(function ($c) { + return new ProjectFileEventJob($c); + }); + + $container['notificationJob'] = $container->factory(function ($c) { + return new NotificationJob($c); + }); + + $container['projectMetricJob'] = $container->factory(function ($c) { + return new ProjectMetricJob($c); + }); + + $container['userMentionJob'] = $container->factory(function ($c) { + return new UserMentionJob($c); + }); + + return $container; + } +} diff --git a/app/ServiceProvider/LoggingProvider.php b/app/ServiceProvider/LoggingProvider.php new file mode 100644 index 0000000..2ff6ba4 --- /dev/null +++ b/app/ServiceProvider/LoggingProvider.php @@ -0,0 +1,57 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use Psr\Log\LogLevel; +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Kanboard\Core\Log\Logger; +use Kanboard\Core\Log\Stderr; +use Kanboard\Core\Log\Stdout; +use Kanboard\Core\Log\Syslog; +use Kanboard\Core\Log\File; +use Kanboard\Core\Log\System; + +/** + * Class LoggingProvider + * + * @package Kanboard\ServiceProvider + * @author Frederic Guillot + */ +class LoggingProvider implements ServiceProviderInterface +{ + public function register(Container $container) + { + $logger = new Logger(); + $driver = null; + + switch (LOG_DRIVER) { + case 'syslog': + $driver = new Syslog('kanboard'); + break; + case 'stdout': + $driver = new Stdout(); + break; + case 'stderr': + $driver = new Stderr(); + break; + case 'file': + $driver = new File(LOG_FILE); + break; + case 'system': + $driver = new System(); + break; + } + + if ($driver !== null) { + if (! DEBUG) { + $driver->setLevel(LogLevel::INFO); + } + + $logger->setLogger($driver); + } + + $container['logger'] = $logger; + return $container; + } +} diff --git a/app/ServiceProvider/MailProvider.php b/app/ServiceProvider/MailProvider.php new file mode 100644 index 0000000..685709e --- /dev/null +++ b/app/ServiceProvider/MailProvider.php @@ -0,0 +1,34 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use Kanboard\Core\Mail\Client as EmailClient; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Mail Provider + * + * @package Kanboard\ServiceProvider + * @author Frederic Guillot + */ +class MailProvider implements ServiceProviderInterface +{ + /** + * Registers services on the given container. + * + * @param Container $container + */ + public function register(Container $container) + { + $container['emailClient'] = function ($container) { + $mailer = new EmailClient($container); + $mailer->setTransport('smtp', '\Kanboard\Core\Mail\Transport\Smtp'); + $mailer->setTransport('sendmail', '\Kanboard\Core\Mail\Transport\Sendmail'); + $mailer->setTransport('mail', '\Kanboard\Core\Mail\Transport\Mail'); + return $mailer; + }; + + return $container; + } +} diff --git a/app/ServiceProvider/NotificationProvider.php b/app/ServiceProvider/NotificationProvider.php new file mode 100644 index 0000000..a057120 --- /dev/null +++ b/app/ServiceProvider/NotificationProvider.php @@ -0,0 +1,45 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Kanboard\Model\UserNotificationTypeModel; +use Kanboard\Model\ProjectNotificationTypeModel; +use Kanboard\Notification\MailNotification as MailNotification; +use Kanboard\Notification\WebNotification as WebNotification; + +/** + * Notification Provider + * + * @package Kanboard\ServiceProvider + * @author Frederic Guillot + */ +class NotificationProvider implements ServiceProviderInterface +{ + /** + * Register providers + * + * @access public + * @param \Pimple\Container $container + * @return \Pimple\Container + */ + public function register(Container $container) + { + $container['userNotificationTypeModel'] = function ($container) { + $type = new UserNotificationTypeModel($container); + $type->setType(MailNotification::TYPE, t('Email'), '\Kanboard\Notification\MailNotification'); + $type->setType(WebNotification::TYPE, t('Web'), '\Kanboard\Notification\WebNotification'); + return $type; + }; + + $container['projectNotificationTypeModel'] = function ($container) { + $type = new ProjectNotificationTypeModel($container); + $type->setType('webhook', 'Webhook', '\Kanboard\Notification\WebhookNotification', true); + $type->setType('activity_stream', 'ActivityStream', '\Kanboard\Notification\ActivityStreamNotification', true); + return $type; + }; + + return $container; + } +} diff --git a/app/ServiceProvider/ObjectStorageProvider.php b/app/ServiceProvider/ObjectStorageProvider.php new file mode 100644 index 0000000..d9a79ca --- /dev/null +++ b/app/ServiceProvider/ObjectStorageProvider.php @@ -0,0 +1,51 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use Kanboard\Core\ObjectStorage\FileStorage; +use LogicException; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class ObjectStorageProvider + * + * @package Kanboard\ServiceProvider + * @author Frederic Guillot + */ +class ObjectStorageProvider implements ServiceProviderInterface +{ + public function register(Container $container) + { + $container['objectStorage'] = function () { + if (file_exists(FILES_DIR)) { + if (! is_writable(FILES_DIR)) { + $stat = stat(FILES_DIR); + + throw new LogicException(sprintf( + 'The folder to store uploaded files is not writeable by your webserver user (file=%s; mode=%o; uid=%d; gid=%d)', + FILES_DIR, + $stat['mode'], + $stat['uid'], + $stat['gid'] + )); + } + } elseif (! @mkdir(FILES_DIR)) { + $folder = dirname(FILES_DIR); + $stat = stat($folder); + + throw new LogicException(sprintf( + 'Unable to create folder to store uploaded files, check the permissions of the parent directory (file=%s; mode=%o; uid=%d; gid=%d)', + $folder, + $stat['mode'], + $stat['uid'], + $stat['gid'] + )); + } + + return new FileStorage(FILES_DIR); + }; + + return $container; + } +} diff --git a/app/ServiceProvider/PluginProvider.php b/app/ServiceProvider/PluginProvider.php new file mode 100644 index 0000000..4cf5725 --- /dev/null +++ b/app/ServiceProvider/PluginProvider.php @@ -0,0 +1,31 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Kanboard\Core\Plugin\Loader; + +/** + * Plugin Provider + * + * @package Kanboard\ServiceProvider + * @author Frederic Guillot + */ +class PluginProvider implements ServiceProviderInterface +{ + /** + * Register providers + * + * @access public + * @param \Pimple\Container $container + * @return \Pimple\Container + */ + public function register(Container $container) + { + $container['pluginLoader'] = new Loader($container); + $container['pluginLoader']->scan(); + + return $container; + } +} diff --git a/app/ServiceProvider/QueueProvider.php b/app/ServiceProvider/QueueProvider.php new file mode 100644 index 0000000..570f2e7 --- /dev/null +++ b/app/ServiceProvider/QueueProvider.php @@ -0,0 +1,29 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use Kanboard\Core\Queue\QueueManager; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class QueueProvider + * + * @package Kanboard\ServiceProvider + * @author Frederic Guillot + */ +class QueueProvider implements ServiceProviderInterface +{ + /** + * Register providers + * + * @access public + * @param \Pimple\Container $container + * @return \Pimple\Container + */ + public function register(Container $container) + { + $container['queueManager'] = new QueueManager($container); + return $container; + } +} diff --git a/app/ServiceProvider/RouteProvider.php b/app/ServiceProvider/RouteProvider.php new file mode 100644 index 0000000..a5bc6e0 --- /dev/null +++ b/app/ServiceProvider/RouteProvider.php @@ -0,0 +1,285 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Kanboard\Core\Http\Route; +use Kanboard\Core\Http\Router; + +/** + * Route Provider + * + * @package Kanboard\ServiceProvider + * @author Frederic Guillot + */ +class RouteProvider implements ServiceProviderInterface +{ + /** + * Register providers + * + * @access public + * @param \Pimple\Container $container + * @return \Pimple\Container + */ + public function register(Container $container) + { + $container['router'] = new Router($container); + $container['route'] = new Route($container); + + if (ENABLE_URL_REWRITE) { + $container['route']->enable(); + + // Dashboard + $container['route']->addRoute('dashboard', 'DashboardController', 'show'); + $container['route']->addRoute('dashboard/:user_id', 'DashboardController', 'show'); + $container['route']->addRoute('dashboard/:user_id/projects', 'DashboardController', 'projects'); + $container['route']->addRoute('dashboard/:user_id/tasks', 'DashboardController', 'tasks'); + $container['route']->addRoute('dashboard/:user_id/subtasks', 'DashboardController', 'subtasks'); + $container['route']->addRoute('dashboard/:user_id/activity', 'DashboardController', 'activity'); + $container['route']->addRoute('dashboard/:user_id/notifications', 'DashboardController', 'notifications'); + $container['route']->addRoute('my-activity', 'ActivityController', 'user'); + + // Search routes + $container['route']->addRoute('search', 'SearchController', 'index'); + $container['route']->addRoute('search/activity', 'SearchController', 'activity'); + + // ProjectCreation routes + $container['route']->addRoute('project/create', 'ProjectCreationController', 'create'); + $container['route']->addRoute('project/create/personal', 'ProjectCreationController', 'createPrivate'); + + // Project routes + $container['route']->addRoute('projects', 'ProjectListController', 'show'); + $container['route']->addRoute('project/:project_id', 'ProjectViewController', 'show'); + $container['route']->addRoute('p/:project_id', 'ProjectViewController', 'show'); + $container['route']->addRoute('project/:project_id/customer-filters', 'CustomFilterController', 'index'); + $container['route']->addRoute('project/:project_id/customer-filters/create', 'CustomFilterController', 'create'); + $container['route']->addRoute('project/:project_id/share', 'ProjectViewController', 'share'); + $container['route']->addRoute('project/:project_id/notifications', 'ProjectViewController', 'notifications'); + $container['route']->addRoute('project/:project_id/integrations', 'ProjectViewController', 'integrations'); + $container['route']->addRoute('project/:project_id/duplicate', 'ProjectViewController', 'duplicate'); + $container['route']->addRoute('project/:project_id/permissions', 'ProjectPermissionController', 'index'); + $container['route']->addRoute('project/:project_id/activity', 'ActivityController', 'project'); + $container['route']->addRoute('project/:project_id/tags', 'ProjectTagController', 'index'); + $container['route']->addRoute('project/:project_id/task/create', 'TaskCreationController', 'show'); + $container['route']->addRoute('project/:project_id/predefined-contents', 'ProjectPredefinedContentController', 'show'); + $container['route']->addRoute('project/:project_id/predefined-contents/create', 'PredefinedTaskDescriptionController', 'create'); + $container['route']->addRoute('project/:project_id/predefined-contents/save', 'PredefinedTaskDescriptionController', 'save'); + $container['route']->addRoute('project/:project_id/predefined-contents/edit/:id', 'PredefinedTaskDescriptionController', 'edit'); + $container['route']->addRoute('project/:project_id/predefined-contents/remove/:id', 'PredefinedTaskDescriptionController', 'confirm'); + $container['route']->addRoute('project/:project_id/custom-roles', 'ProjectRoleController', 'show'); + $container['route']->addRoute('project/:project_id/import/tasks', 'ProjectViewController', 'importTasks'); + $container['route']->addRoute('project/:project_id/enable', 'ProjectStatusController', 'confirmEnable'); + $container['route']->addRoute('project/:project_id/disable', 'ProjectStatusController', 'confirmDisable'); + $container['route']->addRoute('project/:project_id/remove', 'ProjectStatusController', 'confirmRemove'); + $container['route']->addRoute('project/:project_id/file/:file_id/thumbnail/:etag', 'FileViewerController', 'thumbnail'); + $container['route']->addRoute('project/:project_id/file/:file_id/image/:etag', 'FileViewerController', 'image'); + $container['route']->addRoute('project/:project_id/file/:file_id/download/:etag', 'FileViewerController', 'download'); + $container['route']->addRoute('project/:project_id/file/:file_id/show/:etag', 'FileViewerController', 'show'); + $container['route']->addRoute('project/:project_id/file/:file_id/remove', 'ProjectFileController', 'confirm'); + $container['route']->addRoute('project/:project_id/file/:file_id/view', 'FileViewerController', 'browser'); + + // Project Overview + $container['route']->addRoute('project/:project_id/overview', 'ProjectOverviewController', 'show'); + $container['route']->addRoute('project/:project_id/overview/:search', 'ProjectOverviewController', 'show'); + + // ProjectEdit routes + $container['route']->addRoute('project/:project_id/edit', 'ProjectEditController', 'show'); + + // ProjectUser routes + $container['route']->addRoute('projects/managers/:user_id', 'ProjectUserOverviewController', 'managers'); + $container['route']->addRoute('projects/members/:user_id', 'ProjectUserOverviewController', 'members'); + $container['route']->addRoute('projects/tasks/:user_id/opens', 'ProjectUserOverviewController', 'opens'); + $container['route']->addRoute('projects/tasks/:user_id/closed', 'ProjectUserOverviewController', 'closed'); + $container['route']->addRoute('projects/managers', 'ProjectUserOverviewController', 'managers'); + + // Action routes + $container['route']->addRoute('project/:project_id/actions', 'ActionController', 'index'); + $container['route']->addRoute('project/:project_id/action/:action_id/confirm', 'ActionController', 'confirm'); + $container['route']->addRoute('project/:project_id/action/:action_id/remove', 'ActionController', 'remove'); + $container['route']->addRoute('project/:project_id/action/create', 'ActionCreationController', 'create'); + $container['route']->addRoute('project/:project_id/action/event', 'ActionCreationController', 'event'); + $container['route']->addRoute('project/:project_id/action/params', 'ActionCreationController', 'params'); + $container['route']->addRoute('project/:project_id/action/save', 'ActionCreationController', 'save'); + + // Column routes + $container['route']->addRoute('project/:project_id/columns', 'ColumnController', 'index'); + + // Swimlane routes + $container['route']->addRoute('project/:project_id/swimlanes', 'SwimlaneController', 'index'); + + // Category routes + $container['route']->addRoute('project/:project_id/categories', 'CategoryController', 'index'); + + // Import routes + $container['route']->addRoute('project/:project_id/import', 'TaskImportController', 'show'); + + // Task routes + $container['route']->addRoute('task/:task_id', 'TaskViewController', 'show'); + $container['route']->addRoute('t/:task_id', 'TaskViewController', 'show'); + $container['route']->addRoute('public/task/:task_id/:token', 'TaskViewController', 'readonly'); + + $container['route']->addRoute('task/:task_id/activity', 'ActivityController', 'task'); + $container['route']->addRoute('task/:task_id/transitions', 'TaskViewController', 'transitions'); + $container['route']->addRoute('task/:task_id/analytics', 'TaskViewController', 'analytics'); + $container['route']->addRoute('task/:task_id/time-tracking', 'TaskViewController', 'timetracking'); + $container['route']->addRoute('task/:task_id/position/show', 'TaskMovePositionController', 'show'); + $container['route']->addRoute('task/:task_id/position/save', 'TaskMovePositionController', 'save'); + $container['route']->addRoute('task/:task_id/edit', 'TaskModificationController', 'edit'); + $container['route']->addRoute('task/:task_id/update', 'TaskModificationController', 'update'); + $container['route']->addRoute('task/:task_id/assign-to-me/:csrf_token', 'TaskModificationController', 'assignToMe'); + $container['route']->addRoute('task/:task_id/start/:csrf_token', 'TaskModificationController', 'start'); + $container['route']->addRoute('task/:task_id/assign-to-me/redirect/:redirect/:csrf_token', 'TaskModificationController', 'assignToMe'); + $container['route']->addRoute('task/:task_id/start/redirect/:redirect/:csrf_token', 'TaskModificationController', 'start'); + $container['route']->addRoute('task/:task_id/close', 'TaskStatusController', 'close'); + $container['route']->addRoute('task/:task_id/open', 'TaskStatusController', 'open'); + $container['route']->addRoute('task/:task_id/email/create', 'TaskMailController', 'create'); + $container['route']->addRoute('task/:task_id/email/send', 'TaskMailController', 'send'); + $container['route']->addRoute('task/:task_id/duplicate', 'TaskDuplicationController', 'duplicate'); + $container['route']->addRoute('task/:task_id/move-to-project/:project_id', 'TaskDuplicationController', 'move'); + $container['route']->addRoute('task/:task_id/copy-to-project/:project_id', 'TaskDuplicationController', 'copy'); + $container['route']->addRoute('task/:task_id/screenshot', 'TaskPopoverController', 'screenshot'); + $container['route']->addRoute('task/:task_id/file/screenshot', 'TaskFileController', 'screenshot'); + $container['route']->addRoute('task/:task_id/file/create', 'TaskFileController', 'create'); + $container['route']->addRoute('task/:task_id/file/save', 'TaskFileController', 'save'); + $container['route']->addRoute('task/:task_id/file/:file_id/remove', 'TaskFileController', 'remove'); + $container['route']->addRoute('task/:task_id/file/:file_id/confirm', 'TaskFileController', 'confirm'); + $container['route']->addRoute('task/:task_id/file/:file_id/view', 'FileViewerController', 'browser'); + $container['route']->addRoute('task/:task_id/file/:file_id/thumbnail/:etag', 'FileViewerController', 'thumbnail'); + $container['route']->addRoute('task/:task_id/file/:file_id/image/:etag', 'FileViewerController', 'image'); + $container['route']->addRoute('task/:task_id/file/:file_id/download/:etag', 'FileViewerController', 'download'); + $container['route']->addRoute('task/:task_id/file/:file_id/show/:etag', 'FileViewerController', 'show'); + $container['route']->addRoute('task/:task_id/external-link/find', 'TaskExternalLinkController', 'find'); + $container['route']->addRoute('task/:task_id/external-link/create', 'TaskExternalLinkController', 'create'); + $container['route']->addRoute('task/:task_id/external-link/save', 'TaskExternalLinkController', 'save'); + $container['route']->addRoute('task/:task_id/internal-link/create', 'TaskInternalLinkController', 'create'); + $container['route']->addRoute('task/:task_id/internal-link/save', 'TaskInternalLinkController', 'save'); + $container['route']->addRoute('task/:task_id/comment/create', 'CommentController', 'create'); + $container['route']->addRoute('task/:task_id/comment/save', 'CommentController', 'save'); + $container['route']->addRoute('task/:task_id/comment/:comment_id/edit', 'CommentController', 'edit'); + $container['route']->addRoute('task/:task_id/comment/:comment_id/update', 'CommentController', 'update'); + $container['route']->addRoute('task/:task_id/comment/:comment_id/confirm', 'CommentController', 'confirm'); + $container['route']->addRoute('task/:task_id/comment/:comment_id/remove/:csrf_token', 'CommentController', 'remove'); + $container['route']->addRoute('task/:task_id/subtask/create', 'SubtaskController', 'create'); + $container['route']->addRoute('task/:task_id/subtask/save', 'SubtaskController', 'save'); + $container['route']->addRoute('task/:task_id/recurrence/edit', 'TaskRecurrenceController', 'edit'); + $container['route']->addRoute('task/:task_id/remove', 'TaskSuppressionController', 'confirm'); + $container['route']->addRoute('task/:task_id/remove/redirect/:redirect', 'TaskSuppressionController', 'confirm'); + + // Exports + $container['route']->addRoute('export/tasks/:project_id', 'ExportController', 'tasks'); + $container['route']->addRoute('export/subtasks/:project_id', 'ExportController', 'subtasks'); + $container['route']->addRoute('export/transitions/:project_id', 'ExportController', 'transitions'); + $container['route']->addRoute('export/summary/:project_id', 'ExportController', 'summary'); + + // Analytics routes + $container['route']->addRoute('analytics/tasks/:project_id', 'AnalyticController', 'taskDistribution'); + $container['route']->addRoute('analytics/users/:project_id', 'AnalyticController', 'userDistribution'); + $container['route']->addRoute('analytics/cfd/:project_id', 'AnalyticController', 'cfd'); + $container['route']->addRoute('analytics/burndown/:project_id', 'AnalyticController', 'burndown'); + $container['route']->addRoute('analytics/average-time-column/:project_id', 'AnalyticController', 'averageTimeByColumn'); + $container['route']->addRoute('analytics/lead-cycle-time/:project_id', 'AnalyticController', 'leadAndCycleTime'); + $container['route']->addRoute('analytics/estimated-spent-time/:project_id', 'AnalyticController', 'compareHours'); + + // Board routes + $container['route']->addRoute('board/:project_id', 'BoardViewController', 'show'); + $container['route']->addRoute('board/:project_id/search/:search', 'BoardViewController', 'show'); + $container['route']->addRoute('board/:project_id/task/create/swimlane/:swimlane_id/column/:column_id', 'TaskCreationController', 'show'); + $container['route']->addRoute('board/:project_id/task/bulk/create/swimlane/:swimlane_id/column/:column_id', 'TaskBulkController', 'show'); + $container['route']->addRoute('board/:project_id/close-tasks/swimlane/:swimlane_id/column/:column_id', 'BoardPopoverController', 'confirmCloseColumnTasks'); + $container['route']->addRoute('board/tooltip/:task_id/tasklinks', 'BoardTooltipController', 'tasklinks'); + $container['route']->addRoute('board/tooltip/:task_id/externallinks', 'BoardTooltipController', 'externallinks'); + $container['route']->addRoute('board/tooltip/:task_id/subtasks', 'BoardTooltipController', 'subtasks'); + $container['route']->addRoute('board/tooltip/:task_id/attachments', 'BoardTooltipController', 'attachments'); + $container['route']->addRoute('board/tooltip/:task_id/description', 'BoardTooltipController', 'description'); + $container['route']->addRoute('board/tooltip/:task_id/recurrence', 'BoardTooltipController', 'recurrence'); + $container['route']->addRoute('board/tooltip/:project_id/swimlane/:swimlane_id', 'BoardTooltipController', 'swimlane'); + $container['route']->addRoute('b/:project_id', 'BoardViewController', 'show'); + $container['route']->addRoute('public/board/:token', 'BoardViewController', 'readonly'); + + // Listing routes + $container['route']->addRoute('list/:project_id', 'TaskListController', 'show'); + $container['route']->addRoute('list/:project_id/search/:search', 'TaskListController', 'show'); + $container['route']->addRoute('l/:project_id', 'TaskListController', 'show'); + + // Feed routes + $container['route']->addRoute('feed/project/:token', 'FeedController', 'project'); + $container['route']->addRoute('feed/user/:token', 'FeedController', 'user'); + + // Ical routes + $container['route']->addRoute('ical/project/:token', 'ICalendarController', 'project'); + $container['route']->addRoute('ical/user/:token', 'ICalendarController', 'user'); + + // Users + $container['route']->addRoute('users', 'UserListController', 'show'); + $container['route']->addRoute('user/profile/:user_id', 'UserViewController', 'profile'); + $container['route']->addRoute('user/show/:user_id', 'UserViewController', 'show'); + $container['route']->addRoute('user/show/:user_id/timesheet', 'UserViewController', 'timesheet'); + $container['route']->addRoute('user/show/:user_id/last-logins', 'UserViewController', 'lastLogin'); + $container['route']->addRoute('user/show/:user_id/sessions', 'UserViewController', 'sessions'); + $container['route']->addRoute('user/show/:user_id/password-reset-history', 'UserViewController', 'password'); + $container['route']->addRoute('user/:user_id/edit', 'UserModificationController', 'show'); + $container['route']->addRoute('user/:user_id/password', 'UserCredentialController', 'changePassword'); + $container['route']->addRoute('user/:user_id/share', 'UserViewController', 'share'); + $container['route']->addRoute('user/:user_id/notifications', 'UserViewController', 'notifications'); + $container['route']->addRoute('user/:user_id/accounts', 'UserViewController', 'external'); + $container['route']->addRoute('user/:user_id/integrations', 'UserViewController', 'integrations'); + $container['route']->addRoute('user/:user_id/authentication', 'UserCredentialController', 'changeAuthentication'); + $container['route']->addRoute('user/:user_id/2fa', 'TwoFactorController', 'index'); + $container['route']->addRoute('user/:user_id/avatar', 'AvatarFileController', 'show'); + $container['route']->addRoute('user/:user_id/api', 'UserApiAccessController', 'show'); + $container['route']->addRoute('user/:user_id/notifications/web', 'WebNotificationController', 'show'); + $container['route']->addRoute('user/:user_id/notifications/web/flush/:csrf_token', 'WebNotificationController', 'flush'); + $container['route']->addRoute('user/:user_id/notifications/web/remove/:notification_id/:csrf_token', 'WebNotificationController', 'remove'); + $container['route']->addRoute('invite/signup/:token', 'UserInviteController', 'signup'); + + // Groups + $container['route']->addRoute('groups', 'GroupListController', 'index'); + $container['route']->addRoute('group/:group_id/members', 'GroupListController', 'users'); + + // Config + $container['route']->addRoute('settings', 'ConfigController', 'index'); + $container['route']->addRoute('settings/application', 'ConfigController', 'application'); + $container['route']->addRoute('settings/email', 'ConfigController', 'email'); + $container['route']->addRoute('settings/project', 'ConfigController', 'project'); + $container['route']->addRoute('settings/project', 'ConfigController', 'project'); + $container['route']->addRoute('settings/board', 'ConfigController', 'board'); + $container['route']->addRoute('settings/integrations', 'ConfigController', 'integrations'); + $container['route']->addRoute('settings/webhook', 'ConfigController', 'webhook'); + $container['route']->addRoute('settings/api', 'ConfigController', 'api'); + $container['route']->addRoute('settings/links', 'LinkController', 'index'); + $container['route']->addRoute('settings/currencies', 'CurrencyController', 'show'); + $container['route']->addRoute('settings/currencies/create', 'CurrencyController', 'create'); + $container['route']->addRoute('settings/currencies/change', 'CurrencyController', 'change'); + $container['route']->addRoute('settings/tags', 'TagController', 'index'); + $container['route']->addRoute('settings/links/labels', 'LinkController', 'show'); + $container['route']->addRoute('settings/links/labels/create', 'LinkController', 'create'); + $container['route']->addRoute('settings/links/labels/edit/:link_id', 'LinkController', 'edit'); + $container['route']->addRoute('settings/links/labels/update/:link_id', 'LinkController', 'update'); + $container['route']->addRoute('settings/links/labels/confirm/:link_id', 'LinkController', 'confirm'); + $container['route']->addRoute('settings/links/labels/remove/:link_id/:csrf_token', 'LinkController', 'remove'); + + // Plugins + $container['route']->addRoute('extensions', 'PluginController', 'show'); + $container['route']->addRoute('extensions/directory', 'PluginController', 'directory'); + + // Doc + $container['route']->addRoute('documentation/:file', 'DocumentationController', 'show'); + $container['route']->addRoute('documentation', 'DocumentationController', 'show'); + + // Auth routes + $container['route']->addRoute('login', 'AuthController', 'login'); + $container['route']->addRoute('login/check', 'AuthController', 'check'); + $container['route']->addRoute('logout', 'AuthController', 'logout'); + + // PasswordReset + $container['route']->addRoute('forgot-password', 'PasswordResetController', 'create'); + $container['route']->addRoute('forgot-password/change/:token', 'PasswordResetController', 'change'); + + // Cronjob + $container['route']->addRoute('cronjob', 'CronjobController', 'run'); + } + + return $container; + } +} diff --git a/app/ServiceProvider/SessionProvider.php b/app/ServiceProvider/SessionProvider.php new file mode 100644 index 0000000..6334e51 --- /dev/null +++ b/app/ServiceProvider/SessionProvider.php @@ -0,0 +1,37 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Kanboard\Core\Session\SessionManager; +use Kanboard\Core\Session\FlashMessage; + +/** + * Session Provider + * + * @package Kanboard\ServiceProvider + * @author Frederic Guillot + */ +class SessionProvider implements ServiceProviderInterface +{ + /** + * Register providers + * + * @access public + * @param \Pimple\Container $container + * @return \Pimple\Container + */ + public function register(Container $container) + { + $container['sessionManager'] = function ($c) { + return new SessionManager($c); + }; + + $container['flash'] = function ($c) { + return new FlashMessage($c); + }; + + return $container; + } +} diff --git a/app/ServiceProvider/UserProvider.php b/app/ServiceProvider/UserProvider.php new file mode 100644 index 0000000..c80a2ae --- /dev/null +++ b/app/ServiceProvider/UserProvider.php @@ -0,0 +1,35 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use Kanboard\Core\User\UserManager; +use Kanboard\User\DatabaseBackendUserProvider; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * User Provider + * + * @package Kanboard\ServiceProvider + * @author Frederic Guillot + */ +class UserProvider implements ServiceProviderInterface +{ + /** + * Register providers + * + * @access public + * @param \Pimple\Container $container + * @return \Pimple\Container + */ + public function register(Container $container) + { + $container['userManager'] = new UserManager(); + + if (DB_USER_PROVIDER) { + $container['userManager']->register(new DatabaseBackendUserProvider($container)); + } + + return $container; + } +} diff --git a/app/Subscriber/AuthSubscriber.php b/app/Subscriber/AuthSubscriber.php new file mode 100644 index 0000000..b2b4d46 --- /dev/null +++ b/app/Subscriber/AuthSubscriber.php @@ -0,0 +1,117 @@ +<?php + +namespace Kanboard\Subscriber; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Kanboard\Core\Security\AuthenticationManager; +use Kanboard\Core\Session\SessionManager; +use Kanboard\Event\AuthSuccessEvent; +use Kanboard\Event\AuthFailureEvent; + +/** + * Authentication Subscriber + * + * @package subscriber + * @author Frederic Guillot + */ +class AuthSubscriber extends BaseSubscriber implements EventSubscriberInterface +{ + /** + * Get event listeners + * + * @static + * @access public + * @return array + */ + public static function getSubscribedEvents() + { + return array( + AuthenticationManager::EVENT_SUCCESS => 'afterLogin', + AuthenticationManager::EVENT_FAILURE => 'onLoginFailure', + SessionManager::EVENT_DESTROY => 'afterLogout', + ); + } + + /** + * After Login callback + * + * @access public + * @param AuthSuccessEvent $event + */ + public function afterLogin(AuthSuccessEvent $event) + { + $this->logger->debug('Subscriber executed: '.__METHOD__); + + $userAgent = $this->request->getUserAgent(); + $ipAddress = $this->request->getIpAddress(); + + $this->userLockingModel->resetFailedLogin($this->userSession->getUsername()); + $this->captchaModel->resetFailedLogin($ipAddress); + + $this->lastLoginModel->create( + $event->getAuthType(), + $this->userSession->getId(), + $ipAddress, + $userAgent + ); + + if ($event->getAuthType() === 'RememberMe') { + $this->userSession->setPostAuthenticationAsValidated(); + } + + if (REMEMBER_ME_AUTH && session_is_true('hasRememberMe') && ! $this->userSession->hasPostAuthentication()) { + $session = $this->rememberMeSessionModel->create($this->userSession->getId(), $ipAddress, $userAgent); + $this->rememberMeCookie->write($session['token'], $session['sequence'], $session['expiration']); + } + } + + /** + * Destroy RememberMe session on logout + * + * @access public + */ + public function afterLogout() + { + $this->logger->debug('Subscriber executed: '.__METHOD__); + $credentials = $this->rememberMeCookie->read(); + + if ($credentials !== false) { + $session = $this->rememberMeSessionModel->find($credentials['token'], $credentials['sequence']); + + if (! empty($session)) { + $this->rememberMeSessionModel->remove($session['id']); + } + + $this->rememberMeCookie->remove(); + } + } + + /** + * Increment failed login counter + * + * @access public + * @param AuthFailureEvent $event + */ + public function onLoginFailure(AuthFailureEvent $event) + { + $this->logger->debug('Subscriber executed: '.__METHOD__); + $username = $event->getUsername(); + $ipAddress = $this->request->getIpAddress(); + + // IP-based captcha + $this->captchaModel->incrementFailedLogin($ipAddress); + + if (! empty($username)) { + // log login failure in web server log to allow fail2ban usage + error_log('Kanboard: user '.$username.' authentication failure with IP address: '.$ipAddress); + $this->userLockingModel->incrementFailedLogin($username); + + if ($this->userLockingModel->getFailedLogin($username) > BRUTEFORCE_LOCKDOWN) { + $this->userLockingModel->lock($username, BRUTEFORCE_LOCKDOWN_DURATION); + } + } else { + // log login failure in web server log to allow fail2ban usage + error_log('Kanboard: user Unknown authentication failure with IP address: '.$ipAddress); + } + } +} diff --git a/app/Subscriber/BaseSubscriber.php b/app/Subscriber/BaseSubscriber.php new file mode 100644 index 0000000..9244196 --- /dev/null +++ b/app/Subscriber/BaseSubscriber.php @@ -0,0 +1,15 @@ +<?php + +namespace Kanboard\Subscriber; + +use Kanboard\Core\Base; + +/** + * Base class for subscribers + * + * @package subscriber + * @author Frederic Guillot + */ +class BaseSubscriber extends Base +{ +} diff --git a/app/Subscriber/BootstrapSubscriber.php b/app/Subscriber/BootstrapSubscriber.php new file mode 100644 index 0000000..432f837 --- /dev/null +++ b/app/Subscriber/BootstrapSubscriber.php @@ -0,0 +1,42 @@ +<?php + +namespace Kanboard\Subscriber; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +class BootstrapSubscriber extends BaseSubscriber implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array( + 'app.bootstrap' => 'execute', + ); + } + + public function execute() + { + $this->logger->debug('Subscriber executed: '.__METHOD__); + $this->languageModel->loadCurrentLanguage(); + $this->timezoneModel->setCurrentTimezone(); + $this->actionManager->attachEvents(); + + if ($this->userSession->isLogged()) { + session_set('hasSubtaskInProgress', $this->subtaskStatusModel->hasSubtaskInProgress($this->userSession->getId())); + } + } + + public function __destruct() + { + if (DEBUG) { + foreach ($this->db->getLogMessages() as $message) { + $this->logger->debug('SQL: ' . $message); + } + + $this->logger->debug('APP: nb_queries={nb}', array('nb' => $this->db->getStatementHandler()->getNbQueries())); + $this->logger->debug('APP: rendering_time={time}', array('time' => microtime(true) - $this->request->getStartTime())); + $this->logger->debug('APP: memory_usage='.$this->helper->text->bytes(memory_get_usage())); + $this->logger->debug('APP: uri='.$this->request->getUri()); + $this->logger->debug('###############################################'); + } + } +} diff --git a/app/Subscriber/LdapUserPhotoSubscriber.php b/app/Subscriber/LdapUserPhotoSubscriber.php new file mode 100644 index 0000000..93672cd --- /dev/null +++ b/app/Subscriber/LdapUserPhotoSubscriber.php @@ -0,0 +1,49 @@ +<?php + +namespace Kanboard\Subscriber; + +use Kanboard\Core\User\UserProfile; +use Kanboard\Event\UserProfileSyncEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Class LdapUserPhotoSubscriber + * + * @package Kanboard\Subscriber + * @author Frederic Guillot + */ +class LdapUserPhotoSubscriber extends BaseSubscriber implements EventSubscriberInterface +{ + /** + * Get event listeners + * + * @static + * @access public + * @return array + */ + public static function getSubscribedEvents() + { + return array( + UserProfile::EVENT_USER_PROFILE_AFTER_SYNC => 'syncUserPhoto', + ); + } + + /** + * Save the user profile photo from LDAP to the object storage + * + * @access public + * @param UserProfileSyncEvent $event + */ + public function syncUserPhoto(UserProfileSyncEvent $event) + { + if (is_a($event->getUser(), 'Kanboard\User\LdapUserProvider')) { + $profile = $event->getProfile(); + $photo = $event->getUser()->getPhoto(); + + if (empty($profile['avatar_path']) && ! empty($photo)) { + $this->logger->info('Saving user photo from LDAP profile'); + $this->avatarFileModel->uploadImageContent($profile['id'], $photo); + } + } + } +} diff --git a/app/Subscriber/NotificationSubscriber.php b/app/Subscriber/NotificationSubscriber.php new file mode 100644 index 0000000..8f9b086 --- /dev/null +++ b/app/Subscriber/NotificationSubscriber.php @@ -0,0 +1,47 @@ +<?php + +namespace Kanboard\Subscriber; + +use Kanboard\Event\GenericEvent; +use Kanboard\Model\TaskLinkModel; +use Kanboard\Model\TaskModel; +use Kanboard\Model\CommentModel; +use Kanboard\Model\SubtaskModel; +use Kanboard\Model\TaskFileModel; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +class NotificationSubscriber extends BaseSubscriber implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array( + TaskModel::EVENT_USER_MENTION => 'handleEvent', + TaskModel::EVENT_CREATE => 'handleEvent', + TaskModel::EVENT_UPDATE => 'handleEvent', + TaskModel::EVENT_CLOSE => 'handleEvent', + TaskModel::EVENT_OPEN => 'handleEvent', + TaskModel::EVENT_MOVE_COLUMN => 'handleEvent', + TaskModel::EVENT_MOVE_PROJECT => 'handleEvent', + TaskModel::EVENT_MOVE_POSITION => 'handleEvent', + TaskModel::EVENT_MOVE_SWIMLANE => 'handleEvent', + TaskModel::EVENT_ASSIGNEE_CHANGE => 'handleEvent', + SubtaskModel::EVENT_CREATE => 'handleEvent', + SubtaskModel::EVENT_UPDATE => 'handleEvent', + SubtaskModel::EVENT_DELETE => 'handleEvent', + CommentModel::EVENT_CREATE => 'handleEvent', + CommentModel::EVENT_UPDATE => 'handleEvent', + CommentModel::EVENT_DELETE => 'handleEvent', + CommentModel::EVENT_USER_MENTION => 'handleEvent', + TaskFileModel::EVENT_CREATE => 'handleEvent', + TaskFileModel::EVENT_DESTROY => 'handleEvent', + TaskLinkModel::EVENT_CREATE_UPDATE => 'handleEvent', + TaskLinkModel::EVENT_DELETE => 'handleEvent', + ); + } + + public function handleEvent(GenericEvent $event, $eventName) + { + $this->logger->debug('Subscriber executed: ' . __METHOD__); + $this->queueManager->push($this->notificationJob->withParams($event, $eventName)); + } +} diff --git a/app/Subscriber/ProjectDailySummarySubscriber.php b/app/Subscriber/ProjectDailySummarySubscriber.php new file mode 100644 index 0000000..eaa9d46 --- /dev/null +++ b/app/Subscriber/ProjectDailySummarySubscriber.php @@ -0,0 +1,27 @@ +<?php + +namespace Kanboard\Subscriber; + +use Kanboard\Event\TaskEvent; +use Kanboard\Model\TaskModel; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +class ProjectDailySummarySubscriber extends BaseSubscriber implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array( + TaskModel::EVENT_CREATE_UPDATE => 'execute', + TaskModel::EVENT_CLOSE => 'execute', + TaskModel::EVENT_OPEN => 'execute', + TaskModel::EVENT_MOVE_COLUMN => 'execute', + TaskModel::EVENT_MOVE_SWIMLANE => 'execute', + ); + } + + public function execute(TaskEvent $event) + { + $this->logger->debug('Subscriber executed: '.__METHOD__); + $this->queueManager->push($this->projectMetricJob->withParams($event['task']['project_id'])); + } +} diff --git a/app/Subscriber/ProjectModificationDateSubscriber.php b/app/Subscriber/ProjectModificationDateSubscriber.php new file mode 100644 index 0000000..6e447c2 --- /dev/null +++ b/app/Subscriber/ProjectModificationDateSubscriber.php @@ -0,0 +1,33 @@ +<?php + +namespace Kanboard\Subscriber; + +use Kanboard\Event\GenericEvent; +use Kanboard\Model\TaskModel; +use Kanboard\Model\SubtaskModel; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +class ProjectModificationDateSubscriber extends BaseSubscriber implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array( + TaskModel::EVENT_CREATE_UPDATE => 'execute', + TaskModel::EVENT_CLOSE => 'execute', + TaskModel::EVENT_OPEN => 'execute', + TaskModel::EVENT_MOVE_SWIMLANE => 'execute', + TaskModel::EVENT_MOVE_COLUMN => 'execute', + TaskModel::EVENT_MOVE_POSITION => 'execute', + TaskModel::EVENT_MOVE_PROJECT => 'execute', + TaskModel::EVENT_ASSIGNEE_CHANGE => 'execute', + SubtaskModel::EVENT_CREATE_UPDATE => 'execute', + SubtaskModel::EVENT_DELETE => 'execute', + ); + } + + public function execute(GenericEvent $event) + { + $this->logger->debug('Subscriber executed: '.__METHOD__); + $this->projectModel->updateModificationDate($event['task']['project_id']); + } +} diff --git a/app/Subscriber/RecurringTaskSubscriber.php b/app/Subscriber/RecurringTaskSubscriber.php new file mode 100644 index 0000000..3e2848f --- /dev/null +++ b/app/Subscriber/RecurringTaskSubscriber.php @@ -0,0 +1,42 @@ +<?php + +namespace Kanboard\Subscriber; + +use Kanboard\Event\TaskEvent; +use Kanboard\Model\TaskModel; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +class RecurringTaskSubscriber extends BaseSubscriber implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array( + TaskModel::EVENT_MOVE_COLUMN => 'onMove', + TaskModel::EVENT_CLOSE => 'onClose', + ); + } + + public function onMove(TaskEvent $event) + { + $this->logger->debug('Subscriber executed: '.__METHOD__); + $task = $event['task']; + + if ($task['recurrence_status'] == TaskModel::RECURRING_STATUS_PENDING) { + if ($task['recurrence_trigger'] == TaskModel::RECURRING_TRIGGER_FIRST_COLUMN && $this->columnModel->getFirstColumnId($task['project_id']) == $event['src_column_id']) { + $this->taskRecurrenceModel->duplicateRecurringTask($task['id']); + } elseif ($task['recurrence_trigger'] == TaskModel::RECURRING_TRIGGER_LAST_COLUMN && $this->columnModel->getLastColumnId($task['project_id']) == $event['dst_column_id']) { + $this->taskRecurrenceModel->duplicateRecurringTask($task['id']); + } + } + } + + public function onClose(TaskEvent $event) + { + $this->logger->debug('Subscriber executed: '.__METHOD__); + $task = $event['task']; + + if ($task['recurrence_status'] == TaskModel::RECURRING_STATUS_PENDING && $task['recurrence_trigger'] == TaskModel::RECURRING_TRIGGER_CLOSE) { + $this->taskRecurrenceModel->duplicateRecurringTask($event['task_id']); + } + } +} diff --git a/app/Subscriber/TransitionSubscriber.php b/app/Subscriber/TransitionSubscriber.php new file mode 100644 index 0000000..26d08f8 --- /dev/null +++ b/app/Subscriber/TransitionSubscriber.php @@ -0,0 +1,28 @@ +<?php + +namespace Kanboard\Subscriber; + +use Kanboard\Event\TaskEvent; +use Kanboard\Model\TaskModel; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +class TransitionSubscriber extends BaseSubscriber implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array( + TaskModel::EVENT_MOVE_COLUMN => 'execute', + ); + } + + public function execute(TaskEvent $event) + { + $this->logger->debug('Subscriber executed: '.__METHOD__); + + $user_id = $this->userSession->getId(); + + if (! empty($user_id)) { + $this->transitionModel->save($user_id, $event->getAll()); + } + } +} diff --git a/app/Template/action/index.php b/app/Template/action/index.php new file mode 100644 index 0000000..29b6aca --- /dev/null +++ b/app/Template/action/index.php @@ -0,0 +1,80 @@ +<div class="page-header"> + <h2><?= t('Automatic actions for the project "%s"', $project['name']) ?></h2> + <ul> + <li> + <?= $this->modal->medium('plus', t('Add a new action'), 'ActionCreationController', 'create', array('project_id' => $project['id'])) ?> + </li> + <li> + <?= $this->modal->medium('copy', t('Import from another project'), 'ProjectActionDuplicationController', 'show', array('project_id' => $project['id'])) ?> + </li> + </ul> +</div> + +<?php if (empty($actions)): ?> + <p class="alert"><?= t('There is no action at the moment.') ?></p> +<?php else: ?> + <table class="table-scrolling"> + <?php foreach ($actions as $action): ?> + <tr> + <th> + <div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-icon"><i class="fa fa-cog"></i><i class="fa fa-caret-down"></i></a> + <ul> + <li> + <?= $this->modal->confirm('trash-o', t('Remove'), 'ActionController', 'confirm', array('project_id' => $project['id'], 'action_id' => $action['id'])) ?> + </li> + </ul> + </div> + + <?php if (! isset($available_params[$action['action_name']])): ?> + <?= $this->text->e($action['action_name']) ?> + <?php else: ?> + <?= $this->text->in($action['action_name'], $available_actions) ?> + <?php endif ?> + </th> + </tr> + <tr> + <td> + <?php if (! isset($available_params[$action['action_name']])): ?> + <p class="alert alert-error"><?= t('Automatic action not found: "%s"', $action['action_name']) ?></p> + <?php else: ?> + <ul> + <li> + <?= t('Event name') ?> = + <strong><?= $this->text->in($action['event_name'], $available_events) ?></strong> + </li> + <?php foreach ($action['params'] as $param_name => $param_value): ?> + <li> + <?php if (isset($available_params[$action['action_name']][$param_name]) && is_array($available_params[$action['action_name']][$param_name])): ?> + <?= $this->text->e(ucfirst($param_name)) ?> = + <?php else: ?> + <?= $this->text->in($param_name, $available_params[$action['action_name']]) ?> = + <?php endif ?> + <strong> + <?php if ($this->text->contains($param_name, 'column_id')): ?> + <?= $this->text->in($param_value, $columns_list) ?> + <?php elseif ($this->text->contains($param_name, 'user_id')): ?> + <?= $this->text->in($param_value, $users_list) ?> + <?php elseif ($this->text->contains($param_name, 'project_id')): ?> + <?= $this->text->in($param_value, $projects_list) ?> + <?php elseif ($this->text->contains($param_name, 'color_id')): ?> + <?= $this->text->in($param_value, $colors_list) ?> + <?php elseif ($this->text->contains($param_name, 'category_id')): ?> + <?= $this->text->in($param_value, $categories_list) ?> + <?php elseif ($this->text->contains($param_name, 'link_id')): ?> + <?= $this->text->in($param_value, $links_list) ?> + <?php elseif ($this->text->contains($param_name, 'swimlane_id')): ?> + <?= $this->text->in($param_value, $swimlane_list) ?> + <?php else: ?> + <?= $this->text->e($param_value) ?> + <?php endif ?> + </strong> + </li> + <?php endforeach ?> + </ul> + <?php endif ?> + </td> + </tr> + <?php endforeach ?> + </table> +<?php endif ?> diff --git a/app/Template/action/remove.php b/app/Template/action/remove.php new file mode 100644 index 0000000..e3cdb20 --- /dev/null +++ b/app/Template/action/remove.php @@ -0,0 +1,15 @@ +<div class="page-header"> + <h2><?= t('Remove an automatic action') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"> + <?= t('Do you really want to remove this action: "%s"?', $this->text->in($action['event_name'], $available_events).'/'.$this->text->in($action['action_name'], $available_actions)) ?> + </p> + + <?= $this->modal->confirmButtons( + 'ActionController', + 'remove', + array('project_id' => $project['id'], 'action_id' => $action['id']) + ) ?> +</div> diff --git a/app/Template/action_creation/create.php b/app/Template/action_creation/create.php new file mode 100644 index 0000000..a1169dc --- /dev/null +++ b/app/Template/action_creation/create.php @@ -0,0 +1,13 @@ +<div class="page-header"> + <h2><?= t('Add an action') ?></h2> +</div> +<form method="post" action="<?= $this->url->href('ActionCreationController', 'event', array('project_id' => $project['id'])) ?>"> + <?= $this->form->csrf() ?> + + <?= $this->form->label(t('Action'), 'action_name') ?> + <?= $this->form->select('action_name', $available_actions, $values) ?> + + <?= $this->modal->submitButtons(array( + 'submitLabel' => t('Next step') + )) ?> +</form> diff --git a/app/Template/action_creation/event.php b/app/Template/action_creation/event.php new file mode 100644 index 0000000..2ea7261 --- /dev/null +++ b/app/Template/action_creation/event.php @@ -0,0 +1,23 @@ +<div class="page-header"> + <h2><?= t('Choose an event') ?></h2> +</div> + +<form method="post" action="<?= $this->url->href('ActionCreationController', 'params', array('project_id' => $project['id'])) ?>"> + <?= $this->form->csrf() ?> + + <?= $this->form->hidden('action_name', $values) ?> + + <?= $this->form->label(t('Action'), 'action_name') ?> + <?= $this->form->select('action_name', $available_actions, $values, array(), array('disabled')) ?> + + <?= $this->form->label(t('Event'), 'event_name') ?> + <?= $this->form->select('event_name', $events, $values) ?> + + <div class="form-help"> + <?= t('When the selected event occurs execute the corresponding action.') ?> + </div> + + <?= $this->modal->submitButtons(array( + 'submitLabel' => t('Next step') + )) ?> +</form> diff --git a/app/Template/action_creation/params.php b/app/Template/action_creation/params.php new file mode 100644 index 0000000..c92f09f --- /dev/null +++ b/app/Template/action_creation/params.php @@ -0,0 +1,54 @@ +<div class="page-header"> + <h2><?= t('Define action parameters') ?></h2> +</div> + +<form method="post" action="<?= $this->url->href('ActionCreationController', 'save', array('project_id' => $project['id'])) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + + <?= $this->form->hidden('event_name', $values) ?> + <?= $this->form->hidden('action_name', $values) ?> + + <?= $this->form->label(t('Action'), 'action_name') ?> + <?= $this->form->select('action_name', $available_actions, $values, array(), array('disabled')) ?> + + <?= $this->form->label(t('Event'), 'event_name') ?> + <?= $this->form->select('event_name', $events, $values, array(), array('disabled')) ?> + + <?php foreach ($action_params as $param_name => $param_desc): ?> + <?php if ($this->text->contains($param_name, 'column_id')): ?> + <?= $this->form->label($param_desc, $param_name) ?> + <?= $this->form->select('params['.$param_name.']', $columns_list, $values) ?> + <?php elseif ($this->text->contains($param_name, 'user_id')): ?> + <?= $this->form->label($param_desc, $param_name) ?> + <?= $this->form->select('params['.$param_name.']', $users_list, $values) ?> + <?php elseif ($this->text->contains($param_name, 'project_id')): ?> + <?= $this->form->label($param_desc, $param_name) ?> + <?= $this->form->select('params['.$param_name.']', $projects_list, $values) ?> + <?php elseif ($this->text->contains($param_name, 'color_id')): ?> + <?= $this->form->colorSelect('params['.$param_name.']', $values) ?> + <?php elseif ($this->text->contains($param_name, 'category_id')): ?> + <?= $this->form->label($param_desc, $param_name) ?> + <?= $this->form->select('params['.$param_name.']', $categories_list, $values) ?> + <?php elseif ($this->text->contains($param_name, 'link_id')): ?> + <?= $this->form->label($param_desc, $param_name) ?> + <?= $this->form->select('params['.$param_name.']', $links_list, $values) ?> + <?php elseif ($param_name === 'priority'): ?> + <?= $this->form->label($param_desc, $param_name) ?> + <?= $this->form->select('params['.$param_name.']', $priorities_list, $values) ?> + <?php elseif ($this->text->contains($param_name, 'duration')): ?> + <?= $this->form->label($param_desc, $param_name) ?> + <?= $this->form->number('params['.$param_name.']', $values) ?> + <?php elseif ($this->text->contains($param_name, 'swimlane_id')): ?> + <?= $this->form->label($param_desc, $param_name) ?> + <?= $this->form->select('params['.$param_name.']', $swimlane_list, $values) ?> + <?php elseif (is_array($param_desc)): ?> + <?= $this->form->label(ucfirst($param_name), $param_name) ?> + <?= $this->form->select('params['.$param_name.']', $param_desc, $values) ?> + <?php else: ?> + <?= $this->form->label($param_desc, $param_name) ?> + <?= $this->form->text('params['.$param_name.']', $values) ?> + <?php endif ?> + <?php endforeach ?> + + <?= $this->modal->submitButtons() ?> +</form> diff --git a/app/Template/activity/filter_dropdown.php b/app/Template/activity/filter_dropdown.php new file mode 100644 index 0000000..abd7540 --- /dev/null +++ b/app/Template/activity/filter_dropdown.php @@ -0,0 +1,14 @@ +<div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-icon" title="<?= t('Default filters') ?>" aria-label="<?= t('Default filters') ?>"><i class="fa fa-filter fa-fw"></i><i class="fa fa-caret-down"></i></a> + <ul> + <li><a href="#" class="filter-helper filter-reset" data-filter="" title="<?= t('Keyboard shortcut: "%s"', 'r') ?>"><?= t('Reset filters') ?></a></li> + <li><a href="#" class="filter-helper" data-filter="creator:me"><?= t('My activities') ?></a></li> + <li><a href="#" class="filter-helper" data-filter="created:<=<?= date('Y-m-d', strtotime('yesterday')) ?>"><?= t('Activity until yesterday') ?></a></li> + <li><a href="#" class="filter-helper" data-filter="created:<=<?= date('Y-m-d')?>"><?= t('Activity until today') ?></a></li> + <li><a href="#" class="filter-helper" data-filter="status:closed"><?= t('Closed tasks') ?></a></li> + <li><a href="#" class="filter-helper" data-filter="status:open"><?= t('Open tasks') ?></a></li> + <li> + <?= $this->url->doc(t('View advanced search syntax'), 'search') ?> + </li> + </ul> +</div> \ No newline at end of file diff --git a/app/Template/activity/project.php b/app/Template/activity/project.php new file mode 100644 index 0000000..ce1f8bb --- /dev/null +++ b/app/Template/activity/project.php @@ -0,0 +1,12 @@ +<div class="page-header"> + <h2><?= t('%s\'s activity', $project['name']) ?></h2> + + <?php if ($project['is_public']): ?> + <ul> + <li><?= $this->url->icon('rss-square', t('RSS feed'), 'FeedController', 'project', array('token' => $project['token']), false, '', '', true) ?></li> + <li><?= $this->url->icon('calendar', t('iCal feed'), 'ICalendarController', 'project', array('token' => $project['token'])) ?></li> + </ul> + <?php endif ?> +</div> + +<?= $this->render('event/events', array('events' => $events)) ?> diff --git a/app/Template/activity/task.php b/app/Template/activity/task.php new file mode 100644 index 0000000..39953d1 --- /dev/null +++ b/app/Template/activity/task.php @@ -0,0 +1,12 @@ +<?= $this->render('task/details', array( + 'task' => $task, + 'tags' => $tags, + 'project' => $project, + 'editable' => false, +)) ?> + +<div class="page-header"> + <h2><?= t('Activity stream') ?></h2> +</div> + +<?= $this->render('event/events', array('events' => $events)) ?> diff --git a/app/Template/activity/user.php b/app/Template/activity/user.php new file mode 100644 index 0000000..71a67fb --- /dev/null +++ b/app/Template/activity/user.php @@ -0,0 +1,4 @@ +<div class="page-header"> + <h2><?= t('My activity stream') ?></h2> +</div> +<?= $this->render('event/events', array('events' => $events)) ?> \ No newline at end of file diff --git a/app/Template/analytic/avg_time_columns.php b/app/Template/analytic/avg_time_columns.php new file mode 100644 index 0000000..c17e521 --- /dev/null +++ b/app/Template/analytic/avg_time_columns.php @@ -0,0 +1,31 @@ +<?php if (! $is_ajax): ?> + <div class="page-header"> + <h2><?= t('Average time spent in each column') ?></h2> + </div> +<?php endif ?> + +<?php if (empty($metrics)): ?> + <p class="alert"><?= t('Not enough data to show the graph.') ?></p> +<?php else: ?> + <?= $this->app->component('chart-project-avg-time-column', array( + 'metrics' => $metrics, + 'label' => t('Average time spent'), + )) ?> + + <table class="table-striped"> + <tr> + <th><?= t('Column') ?></th> + <th><?= t('Average time spent') ?></th> + </tr> + <?php foreach ($metrics as $column): ?> + <tr> + <td><?= $this->text->e($column['title']) ?></td> + <td><?= $this->dt->duration($column['average']) ?></td> + </tr> + <?php endforeach ?> + </table> + + <p class="alert alert-info"> + <?= t('This chart shows the average time spent in each column for the last %d tasks.', 1000) ?> + </p> +<?php endif ?> diff --git a/app/Template/analytic/burndown.php b/app/Template/analytic/burndown.php new file mode 100644 index 0000000..d62c9ba --- /dev/null +++ b/app/Template/analytic/burndown.php @@ -0,0 +1,26 @@ +<?php if (! $is_ajax): ?> + <div class="page-header"> + <h2><?= t('Burndown chart') ?></h2> + </div> +<?php endif ?> + +<?php if (! $display_graph): ?> + <p class="alert"><?= t('You need at least 2 days of data to show the chart.') ?></p> +<?php else: ?> + <?= $this->app->component('chart-project-burndown', array( + 'metrics' => $metrics, + 'labelTotal' => t('Total for all columns'), + 'dateFormat' => e('%%Y-%%m-%%d'), + )) ?> +<?php endif ?> + +<hr/> + +<form method="post" class="form-inline" action="<?= $this->url->href('AnalyticController', 'burndown', array('project_id' => $project['id'])) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + <?= $this->form->date(t('Start date'), 'from', $values) ?> + <?= $this->form->date(t('End date'), 'to', $values) ?> + <?= $this->modal->submitButtons(array('submitLabel' => t('Execute'))) ?> +</form> + +<p class="alert alert-info"><?= t('This chart show the task complexity over the time (Work Remaining).') ?></p> diff --git a/app/Template/analytic/cfd.php b/app/Template/analytic/cfd.php new file mode 100644 index 0000000..dcd7b58 --- /dev/null +++ b/app/Template/analytic/cfd.php @@ -0,0 +1,23 @@ +<?php if (! $is_ajax): ?> + <div class="page-header"> + <h2><?= t('Cumulative flow diagram') ?></h2> + </div> +<?php endif ?> + +<?php if (! $display_graph): ?> + <p class="alert"><?= t('You need at least 2 days of data to show the chart.') ?></p> +<?php else: ?> + <?= $this->app->component('chart-project-cumulative-flow', array( + 'metrics' => $metrics, + 'dateFormat' => e('%%Y-%%m-%%d'), + )) ?> +<?php endif ?> + +<hr/> + +<form method="post" class="form-inline" action="<?= $this->url->href('AnalyticController', 'cfd', array('project_id' => $project['id'])) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + <?= $this->form->date(t('Start date'), 'from', $values) ?> + <?= $this->form->date(t('End date'), 'to', $values) ?> + <?= $this->modal->submitButtons(array('submitLabel' => t('Execute'))) ?> +</form> diff --git a/app/Template/analytic/estimated_actual_column.php b/app/Template/analytic/estimated_actual_column.php new file mode 100644 index 0000000..c49944e --- /dev/null +++ b/app/Template/analytic/estimated_actual_column.php @@ -0,0 +1,30 @@ +<?php if (! $is_ajax): ?> + <div class="page-header"> + <h2><?= t('Estimated vs actual time per column') ?></h2> + </div> +<?php endif ?> + +<?php if (empty($metrics)): ?> + <p class="alert"><?= t('Not enough data to show the graph.') ?></p> +<?php else: ?> + <?= $this->app->component('chart-project-estimated-actual-column', array( + 'metrics' => $metrics, + 'labelSpent' => t('Hours Spent'), + 'labelEstimated' => t('Hours Estimated'), + )) ?> + + <table class="table-striped"> + <tr> + <th><?= t('Column') ?></th> + <th><?= t('Hours Spent') ?></th> + <th><?= t('Hours Estimated') ?></th> + </tr> + <?php foreach ($metrics as $column): ?> + <tr> + <td><?= $this->text->e($column['title']) ?></td> + <td><?= $this->dt->durationHours($column['hours_spent']) ?></td> + <td><?= $this->dt->durationHours($column['hours_estimated']) ?></td> + </tr> + <?php endforeach ?> + </table> +<?php endif ?> \ No newline at end of file diff --git a/app/Template/analytic/layout.php b/app/Template/analytic/layout.php new file mode 100644 index 0000000..7159094 --- /dev/null +++ b/app/Template/analytic/layout.php @@ -0,0 +1,14 @@ +<?php if ($is_ajax): ?> + <div class="page-header"> + <h2><?= $title ?></h2> + </div> +<?php else: ?> + <?= $this->projectHeader->render($project, 'TaskListController', 'show') ?> +<?php endif ?> +<section class="sidebar-container"> + <?= $this->render($sidebar_template, array('project' => $project)) ?> + + <div class="sidebar-content"> + <?= $content_for_sublayout ?> + </div> +</section> diff --git a/app/Template/analytic/lead_cycle_time.php b/app/Template/analytic/lead_cycle_time.php new file mode 100644 index 0000000..0e87671 --- /dev/null +++ b/app/Template/analytic/lead_cycle_time.php @@ -0,0 +1,33 @@ +<?php if (! $is_ajax): ?> + <div class="page-header"> + <h2><?= t('Average Lead and Cycle time') ?></h2> + </div> +<?php endif ?> + +<div class="panel"> + <ul> + <li><?= t('Average lead time: ').'<strong>'.$this->dt->duration($average['avg_lead_time']) ?></strong></li> + <li><?= t('Average cycle time: ').'<strong>'.$this->dt->duration($average['avg_cycle_time']) ?></strong></li> + </ul> +</div> + +<?php if (empty($metrics)): ?> + <p class="alert"><?= t('Not enough data to show the graph.') ?></p> +<?php else: ?> + <?= $this->app->component('chart-project-lead-cycle-time', array( + 'metrics' => $metrics, + 'labelCycle' => t('Cycle Time'), + 'labelLead' => t('Lead Time'), + )) ?> + + <form method="post" class="form-inline" action="<?= $this->url->href('AnalyticController', 'leadAndCycleTime', array('project_id' => $project['id'])) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + <?= $this->form->date(t('Start date'), 'from', $values) ?> + <?= $this->form->date(t('End date'), 'to', $values) ?> + <?= $this->modal->submitButtons(array('submitLabel' => t('Execute'))) ?> + </form> + + <p class="alert alert-info"> + <?= t('This chart shows the average lead and cycle time for the last %d tasks over the time.', 1000) ?> + </p> +<?php endif ?> diff --git a/app/Template/analytic/sidebar.php b/app/Template/analytic/sidebar.php new file mode 100644 index 0000000..ccd5e59 --- /dev/null +++ b/app/Template/analytic/sidebar.php @@ -0,0 +1,30 @@ +<div class="sidebar"> + <ul> + <li <?= $this->app->checkMenuSelection('AnalyticController', 'taskDistribution') ?>> + <?= $this->modal->replaceLink(t('Task distribution'), 'AnalyticController', 'taskDistribution', array('project_id' => $project['id'])) ?> + </li> + <li <?= $this->app->checkMenuSelection('AnalyticController', 'userDistribution') ?>> + <?= $this->modal->replaceLink(t('User repartition'), 'AnalyticController', 'userDistribution', array('project_id' => $project['id'])) ?> + </li> + <li <?= $this->app->checkMenuSelection('AnalyticController', 'cfd') ?>> + <?= $this->modal->replaceLink(t('Cumulative flow diagram'), 'AnalyticController', 'cfd', array('project_id' => $project['id'])) ?> + </li> + <li <?= $this->app->checkMenuSelection('AnalyticController', 'burndown') ?>> + <?= $this->modal->replaceLink(t('Burndown chart'), 'AnalyticController', 'burndown', array('project_id' => $project['id'])) ?> + </li> + <li <?= $this->app->checkMenuSelection('AnalyticController', 'averageTimeByColumn') ?>> + <?= $this->modal->replaceLink(t('Average time into each column'), 'AnalyticController', 'averageTimeByColumn', array('project_id' => $project['id'])) ?> + </li> + <li <?= $this->app->checkMenuSelection('AnalyticController', 'leadAndCycleTime') ?>> + <?= $this->modal->replaceLink(t('Lead and cycle time'), 'AnalyticController', 'leadAndCycleTime', array('project_id' => $project['id'])) ?> + </li> + <li <?= $this->app->checkMenuSelection('AnalyticController', 'timeComparison') ?>> + <?= $this->modal->replaceLink(t('Estimated vs actual time'), 'AnalyticController', 'timeComparison', array('project_id' => $project['id'])) ?> + </li> + <li <?= $this->app->checkMenuSelection('AnalyticController', 'estimatedVsActualByColumn') ?>> + <?= $this->modal->replaceLink(t('Estimated vs actual time per column'), 'AnalyticController', 'estimatedVsActualByColumn', array('project_id' => $project['id'])) ?> + </li> + + <?= $this->hook->render('template:analytic:sidebar', array('project' => $project)) ?> + </ul> +</div> diff --git a/app/Template/analytic/task_distribution.php b/app/Template/analytic/task_distribution.php new file mode 100644 index 0000000..671d462 --- /dev/null +++ b/app/Template/analytic/task_distribution.php @@ -0,0 +1,34 @@ +<?php if (! $is_ajax): ?> + <div class="page-header"> + <h2><?= t('Task distribution') ?></h2> + </div> +<?php endif ?> + +<?php if (empty($metrics)): ?> + <p class="alert"><?= t('Not enough data to show the graph.') ?></p> +<?php else: ?> + <?= $this->app->component('chart-project-task-distribution', array( + 'metrics' => $metrics, + )) ?> + + <table class="table-striped"> + <tr> + <th><?= t('Column') ?></th> + <th><?= t('Number of tasks') ?></th> + <th><?= t('Percentage') ?></th> + </tr> + <?php foreach ($metrics as $metric): ?> + <tr> + <td> + <?= $this->text->e($metric['column_title']) ?> + </td> + <td> + <?= $metric['nb_tasks'] ?> + </td> + <td> + <?= n($metric['percentage']) ?>% + </td> + </tr> + <?php endforeach ?> + </table> +<?php endif ?> diff --git a/app/Template/analytic/time_comparison.php b/app/Template/analytic/time_comparison.php new file mode 100644 index 0000000..bf2cd12 --- /dev/null +++ b/app/Template/analytic/time_comparison.php @@ -0,0 +1,63 @@ +<?php if (! $is_ajax): ?> + <div class="page-header"> + <h2><?= t('Estimated vs actual time') ?></h2> + </div> +<?php endif ?> + +<div class="panel"> + <ul> + <li><?= t('Estimated hours: ').'<strong>'.$this->text->e($metrics['open']['time_estimated'] + $metrics['closed']['time_estimated']) ?></strong></li> + <li><?= t('Actual hours: ').'<strong>'.$this->text->e($metrics['open']['time_spent'] + $metrics['closed']['time_spent']) ?></strong></li> + </ul> +</div> + +<?php if (empty($metrics)): ?> + <p class="alert"><?= t('Not enough data to show the graph.') ?></p> +<?php else: ?> + <?php if ($paginator->isEmpty()): ?> + <p class="alert"><?= t('No tasks found.') ?></p> + <?php elseif (! $paginator->isEmpty()): ?> + <?= $this->app->component('chart-project-time-comparison', array( + 'metrics' => $metrics, + 'labelSpent' => t('Hours Spent'), + 'labelEstimated' => t('Hours Estimated'), + 'labelClosed' => t('Closed'), + 'labelOpen' => t('Open'), + )) ?> + + <table class="table-fixed table-small table-scrolling"> + <tr> + <th class="column-5"><?= $paginator->order(t('Id'), 'tasks.id') ?></th> + <th><?= $paginator->order(t('Title'), 'tasks.title') ?></th> + <th class="column-10"><?= $paginator->order(t('Status'), 'tasks.is_active') ?></th> + <th class="column-12"><?= $paginator->order(t('Estimated Time'), 'tasks.time_estimated') ?></th> + <th class="column-12"><?= $paginator->order(t('Actual Time'), 'tasks.time_spent') ?></th> + </tr> + <?php foreach ($paginator->getCollection() as $task): ?> + <tr> + <td class="task-table color-<?= $task['color_id'] ?>"> + <?= $this->url->link('#'.$this->text->e($task['id']), 'TaskViewController', 'show', array('task_id' => $task['id']), false, '', t('View this task')) ?> + </td> + <td> + <?= $this->url->link($this->text->e($task['title']), 'TaskViewController', 'show', array('task_id' => $task['id']), false, '', t('View this task')) ?> + </td> + <td> + <?php if ($task['is_active'] == \Kanboard\Model\TaskModel::STATUS_OPEN): ?> + <?= t('Open') ?> + <?php else: ?> + <?= t('Closed') ?> + <?php endif ?> + </td> + <td> + <?= $this->text->e($task['time_estimated']) ?> + </td> + <td> + <?= $this->text->e($task['time_spent']) ?> + </td> + </tr> + <?php endforeach ?> + </table> + + <?= $paginator ?> + <?php endif ?> +<?php endif ?> diff --git a/app/Template/analytic/user_distribution.php b/app/Template/analytic/user_distribution.php new file mode 100644 index 0000000..cae6fa5 --- /dev/null +++ b/app/Template/analytic/user_distribution.php @@ -0,0 +1,34 @@ +<?php if (! $is_ajax): ?> + <div class="page-header"> + <h2><?= t('User repartition') ?></h2> + </div> +<?php endif ?> + +<?php if (empty($metrics)): ?> + <p class="alert"><?= t('Not enough data to show the graph.') ?></p> +<?php else: ?> + <?= $this->app->component('chart-project-user-distribution', array( + 'metrics' => $metrics, + )) ?> + + <table class="table-striped"> + <tr> + <th><?= t('User') ?></th> + <th><?= t('Number of tasks') ?></th> + <th><?= t('Percentage') ?></th> + </tr> + <?php foreach ($metrics as $metric): ?> + <tr> + <td> + <?= $this->text->e($metric['user']) ?> + </td> + <td> + <?= $metric['nb_tasks'] ?> + </td> + <td> + <?= n($metric['percentage']) ?>% + </td> + </tr> + <?php endforeach ?> + </table> +<?php endif ?> diff --git a/app/Template/app/filters_helper.php b/app/Template/app/filters_helper.php new file mode 100644 index 0000000..b3844f3 --- /dev/null +++ b/app/Template/app/filters_helper.php @@ -0,0 +1,21 @@ +<?= $this->hook->render('template:app:filters-helper:before', isset($project) ? array('project' => $project) : array()) ?> +<div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-icon" title="<?= t('Default filters') ?>" aria-label="<?= t('Default filters') ?>"><i class="fa fa-filter fa-fw"></i><i class="fa fa-caret-down"></i></a> + <ul> + <li><a href="#" class="filter-helper filter-reset" data-filter="<?= isset($reset) ? $reset : '' ?>" title="<?= t('Keyboard shortcut: "%s"', 'r') ?>"><?= t('Reset filters') ?></a></li> + <li><a href="#" class="filter-helper" data-filter="status:open assignee:me"><?= t('My tasks') ?></a></li> + <li><a href="#" class="filter-helper" data-filter="status:open assignee:me due:tomorrow"><?= t('My tasks due tomorrow') ?></a></li> + <li><a href="#" class="filter-helper" data-filter="status:open due:today"><?= t('Tasks due today') ?></a></li> + <li><a href="#" class="filter-helper" data-filter="status:open due:tomorrow"><?= t('Tasks due tomorrow') ?></a></li> + <li><a href="#" class="filter-helper" data-filter="status:open due:yesterday"><?= t('Tasks due yesterday') ?></a></li> + <li><a href="#" class="filter-helper" data-filter="status:closed"><?= t('Closed tasks') ?></a></li> + <li><a href="#" class="filter-helper" data-filter="status:open"><?= t('Open tasks') ?></a></li> + <li><a href="#" class="filter-helper" data-filter="status:open assignee:nobody"><?= t('Not assigned') ?></a></li> + <li><a href="#" class="filter-helper" data-filter="status:open assignee:anybody"><?= t('Assigned') ?></a></li> + <li><a href="#" class="filter-helper" data-filter="status:open category:none"><?= t('No category') ?></a></li> + <li> + <?= $this->url->doc(t('View advanced search syntax'), 'search') ?> + </li> + </ul> +</div> +<?= $this->hook->render('template:app:filters-helper:after', isset($project) ? array('project' => $project) : array()) ?> \ No newline at end of file diff --git a/app/Template/app/forbidden.php b/app/Template/app/forbidden.php new file mode 100644 index 0000000..96e7611 --- /dev/null +++ b/app/Template/app/forbidden.php @@ -0,0 +1,5 @@ +<section id="main"> + <p class="alert alert-error"> + <?= t('Access Forbidden') ?> + </p> +</section> \ No newline at end of file diff --git a/app/Template/app/notfound.php b/app/Template/app/notfound.php new file mode 100644 index 0000000..0419902 --- /dev/null +++ b/app/Template/app/notfound.php @@ -0,0 +1,5 @@ +<section id="main"> + <p class="alert alert-error"> + <?= t('Sorry, I didn\'t find this information in my database!') ?> + </p> +</section> \ No newline at end of file diff --git a/app/Template/auth/index.php b/app/Template/auth/index.php new file mode 100644 index 0000000..ebfd281 --- /dev/null +++ b/app/Template/auth/index.php @@ -0,0 +1,53 @@ +<div class="login-page-wrapper"> +<div class="form-login"> + <h2><?= t('Login') ?></h2> + + <?= $this->hook->render('template:auth:login-form:before') ?> + + <?php if (isset($errors['login'])): ?> + <p class="alert alert-error"><?= $this->text->e($errors['login']) ?></p> + <?php endif ?> + + <?php if (! HIDE_LOGIN_FORM): ?> + <form method="post" action="<?= $this->url->href('AuthController', 'check') ?>"> + + <?= $this->form->csrf() ?> + + <div class="input-icon-wrapper"> + <?= $this->form->label(t('Username'), 'username') ?> + <i class="fa fa-user" aria-hidden="true"></i> + <?= $this->form->text('username', $values, $errors, array('autofocus', 'required', 'autocomplete="username"', 'placeholder="'.t('Username').'"')) ?> + </div> + + <div class="input-icon-wrapper"> + <?= $this->form->label(t('Password'), 'password') ?> + <i class="fa fa-lock" aria-hidden="true"></i> + <?= $this->form->password('password', $values, $errors, array('required', 'autocomplete="current-password"', 'placeholder="'.t('Password').'"')) ?> + </div> + + <?php if (isset($captcha) && $captcha): ?> + <?= $this->form->label(t('Enter the text below'), 'captcha') ?> + <img src="<?= $this->url->href('CaptchaController', 'image') ?>" alt="Captcha"> + <?= $this->form->text('captcha', array(), $errors, array('required')) ?> + <?php endif ?> + + <?php if (REMEMBER_ME_AUTH): ?> + <div class="remember-me-wrapper"> + <?= $this->form->checkbox('remember_me', t('Remember Me'), 1, true) ?> + </div> + <?php endif ?> + + <div class="form-actions"> + <button type="submit" class="btn btn-blue"><?= t('Sign in') ?></button> + </div> + <?php if ($this->app->config('password_reset') == 1): ?> + <div class="reset-password"> + <?= $this->url->link(t('Forgot password?'), 'PasswordResetController', 'create') ?> + </div> + <?php endif ?> + </form> + <?php endif ?> + + <?= $this->hook->render('template:auth:login-form:after') ?> +</div> +</div> diff --git a/app/Template/avatar_file/show.php b/app/Template/avatar_file/show.php new file mode 100644 index 0000000..371d09a --- /dev/null +++ b/app/Template/avatar_file/show.php @@ -0,0 +1,23 @@ +<div class="page-header"> + <h2><?= t('Avatar') ?></h2> +</div> + +<?= $this->avatar->render($user['id'], $user['username'], $user['name'], $user['email'], $user['avatar_path'], '') ?> + +<div class="form-actions"> +<?php if (! empty($user['avatar_path'])): ?> + <?= $this->url->link(t('Remove my image'), 'AvatarFileController', 'remove', array('user_id' => $user['id']), true, 'btn btn-red js-modal-replace') ?> +<?php endif ?> +</div> + +<hr> + +<h3><?= t('Upload my avatar image') ?></h3> +<form method="post" enctype="multipart/form-data" action="<?= $this->url->href('AvatarFileController', 'upload', array('user_id' => $user['id']), true) ?>"> + <?= $this->form->label(t('Avatar'), 'avatar', ['class="ui-helper-hidden-accessible"']) ?> + <?= $this->form->file('avatar') ?> + + <div class="form-actions"> + <button type="submit" class="btn btn-blue"><?= t('Upload my avatar image') ?></button> + </div> +</form> diff --git a/app/Template/board/table_column.php b/app/Template/board/table_column.php new file mode 100644 index 0000000..2d15a85 --- /dev/null +++ b/app/Template/board/table_column.php @@ -0,0 +1,127 @@ +<!-- column titles --> + +<?= $this->hook->render('template:board:table:column:before-header-row', array('swimlane' => $swimlane)) ?> + +<tr class="board-swimlane-columns-<?= $swimlane['id'] ?>"> + <?php foreach ($swimlane['columns'] as $column): ?> + <th class="board-column-header board-column-header-<?= $column['id'] ?>" data-column-id="<?= $column['id'] ?>"> + + <!-- column in collapsed mode --> + <div class="board-column-collapsed"> + <small class="board-column-header-task-count" title="<?= t('Task count') ?>"> + <span id="task-number-column-<?= $column['id'] ?>"><span class="ui-helper-hidden-accessible"><?= t('Task count') ?> </span><?= $column['nb_tasks'] ?></span> + </small> + </div> + + <!-- column in expanded mode --> + <div class="board-column-expanded board-column-expanded-header"> + <?php if (! $not_editable && $this->projectRole->canCreateTaskInColumn($column['project_id'], $column['id'])): ?> + <?= $this->task->getNewBoardTaskButton($swimlane, $column) ?> + <?php endif ?> + + <span class="board-column-title"> + <?php if ($not_editable): ?> + <?= $this->text->e($column['title']) ?> + <?php else: ?> + <span class="dropdown"> + <a href="#" class="dropdown-menu"><?= $this->text->e($column['title']) ?> <i class="fa fa-caret-down"></i></a> + <ul> + <li> + <i class="fa fa-minus-square fa-fw"></i> + <a href="#" class="board-toggle-column-view" data-column-id="<?= $column['id'] ?>"><?= t('Hide this column') ?></a> + </li> + <?php if ($this->projectRole->canCreateTaskInColumn($column['project_id'], $column['id'])): ?> + <li> + <?= $this->modal->medium('align-justify', t('Create tasks in bulk'), 'TaskBulkController', 'show', array('project_id' => $column['project_id'], 'column_id' => $column['id'], 'swimlane_id' => $swimlane['id'])) ?> + </li> + <?php endif ?> + + <?php if ($column['nb_tasks'] > 0 && $this->projectRole->canChangeTaskStatusInColumn($column['project_id'], $column['id'])): ?> + <li> + <?= $this->modal->confirm('close', t('Close all tasks in this column and this swimlane'), 'BoardPopoverController', 'confirmCloseColumnTasks', array('project_id' => $column['project_id'], 'column_id' => $column['id'], 'swimlane_id' => $swimlane['id'])) ?> + </li> + <?php endif ?> + </ul> + <?php if ($column['nb_tasks'] > 0 && $this->user->hasProjectAccess('TaskModificationController', 'update', $column['project_id'])): ?> + <span class="dropdown"> + <a href="#" class="dropdown-menu"><i class="fa fa-sort"></i></i></a> + <ul> + <li> + <?= $this->url->icon('sort-numeric-asc', t('Reorder this column by id (ASC)'), 'TaskReorderController', 'reorderColumn', ['sort' => 'id', 'direction' => 'asc', 'project_id' => $column['project_id'], 'column_id' => $column['id'], 'swimlane_id' => $swimlane['id']], true) ?> + </li> + <li> + <?= $this->url->icon('sort-numeric-desc', t('Reorder this column by id (DESC)'), 'TaskReorderController', 'reorderColumn', ['sort' => 'id', 'direction' => 'desc', 'project_id' => $column['project_id'], 'column_id' => $column['id'], 'swimlane_id' => $swimlane['id']], true) ?> + </li> + <li> + <?= $this->url->icon('sort-numeric-asc', t('Reorder this column by priority (ASC)'), 'TaskReorderController', 'reorderColumn', ['sort' => 'priority', 'direction' => 'asc', 'project_id' => $column['project_id'], 'column_id' => $column['id'], 'swimlane_id' => $swimlane['id']], true) ?> + </li> + <li> + <?= $this->url->icon('sort-numeric-desc', t('Reorder this column by priority (DESC)'), 'TaskReorderController', 'reorderColumn', ['sort' => 'priority', 'direction' => 'desc', 'project_id' => $column['project_id'], 'column_id' => $column['id'], 'swimlane_id' => $swimlane['id']], true) ?> + </li> + <li> + <?= $this->url->icon('sort-amount-asc', t('Reorder this column by assignee and priority (ASC)'), 'TaskReorderController', 'reorderColumn', ['sort' => 'assignee-priority', 'direction' => 'asc', 'project_id' => $column['project_id'], 'column_id' => $column['id'], 'swimlane_id' => $swimlane['id']], true) ?> + </li> + <li> + <?= $this->url->icon('sort-amount-desc', t('Reorder this column by assignee and priority (DESC)'), 'TaskReorderController', 'reorderColumn', ['sort' => 'assignee-priority', 'direction' => 'desc', 'project_id' => $column['project_id'], 'column_id' => $column['id'], 'swimlane_id' => $swimlane['id']], true) ?> + </li> + <li> + <?= $this->url->icon('sort-alpha-asc', t('Reorder this column by assignee (A-Z)'), 'TaskReorderController', 'reorderColumn', ['sort' => 'assignee', 'direction' => 'asc', 'project_id' => $column['project_id'], 'column_id' => $column['id'], 'swimlane_id' => $swimlane['id']], true) ?> + </li> + <li> + <?= $this->url->icon('sort-alpha-desc', t('Reorder this column by assignee (Z-A)'), 'TaskReorderController', 'reorderColumn', ['sort' => 'assignee', 'direction' => 'desc', 'project_id' => $column['project_id'], 'column_id' => $column['id'], 'swimlane_id' => $swimlane['id']], true) ?> + </li> + <li> + <?= $this->url->icon('sort-numeric-asc', t('Reorder this column by due date (ASC)'), 'TaskReorderController', 'reorderColumn', ['sort' => 'due-date', 'direction' => 'asc', 'project_id' => $column['project_id'], 'column_id' => $column['id'], 'swimlane_id' => $swimlane['id']], true) ?> + </li> + <li> + <?= $this->url->icon('sort-numeric-desc', t('Reorder this column by due date (DESC)'), 'TaskReorderController', 'reorderColumn', ['sort' => 'due-date', 'direction' => 'desc', 'project_id' => $column['project_id'], 'column_id' => $column['id'], 'swimlane_id' => $swimlane['id']], true) ?> + </li> + </ul> + </span> + <?php endif ?> + + <?= $this->hook->render('template:board:column:dropdown', array('swimlane' => $swimlane, 'column' => $column)) ?> + </span> + <?php endif ?> + </span> + + <span class="pull-right board-column-header-task-count"> + <?php if (! empty($column['score'])): ?> + <span title="<?= t('Score') ?>"> + <span class="ui-helper-hidden-accessible"><?= t('Score') ?> </span><?= $column['score'] ?> + </span> + <?php endif ?> + + <?php if ($swimlane['nb_swimlanes'] > 1 && ! empty($column['cumulative_score_across_swimlane'])): ?> + <span title="<?= t('Total score in this column across all swimlanes') ?>"> + (<span><span class="ui-helper-hidden-accessible"><?= t('Total score in this column across all swimlanes') ?> </span><?= $column['cumulative_score_across_swimlane'] ?></span>)  + </span> + <?php endif ?> + + <?php if (! $not_editable && ! empty($column['description'])): ?> + <?= $this->app->tooltipMarkdown($column['description']) ?>  + <?php endif ?> + + <?php if (! empty($column['nb_tasks'])): ?> + <span title="<?= t('Number of visible tasks in this column and swimlane') ?>"> + <span><span class="ui-helper-hidden-accessible"><?= t('Task count') ?> </span><?= $column['nb_tasks'] ?></span>  + </span> + <?php endif ?> + + <?php if (! empty($column['nb_unfiltered_tasks_across_swimlane'])): ?> + <span title="<?= t('Total number of tasks in this column across all swimlanes') ?>"> + <?php if ($column['task_limit'] > 0): ?> + (<span><span class="ui-helper-hidden-accessible"><?= t('Total number of tasks in this column across all swimlanes') ?> </span><?= $column['nb_unfiltered_tasks_across_swimlane'] ?></span>/<span title="<?= t('Task limit') ?>"><span class="ui-helper-hidden-accessible"><?= t('Task limit') ?> </span><?= $this->text->e($column['task_limit']) ?></span>) + <?php else: ?> + (<span><span class="ui-helper-hidden-accessible"><?= t('Total number of tasks in this column across all swimlanes') ?> </span><?= $column['nb_unfiltered_tasks_across_swimlane'] ?></span>) + <?php endif ?> + </span> + <?php endif ?> + </span> + <?= $this->hook->render('template:board:column:header', array('swimlane' => $swimlane, 'column' => $column)) ?> + </div> + </th> + <?php endforeach ?> +</tr> + +<?= $this->hook->render('template:board:table:column:after-header-row', array('swimlane' => $swimlane)) ?> diff --git a/app/Template/board/table_column_first.php b/app/Template/board/table_column_first.php new file mode 100644 index 0000000..a6e2bf5 --- /dev/null +++ b/app/Template/board/table_column_first.php @@ -0,0 +1,20 @@ +<tr class="board-swimlane-columns-first"> + <?php foreach ($swimlane['columns'] as $column): ?> + <th class="board-column-header board-column-header-first board-column-header-<?= $column['id'] ?>" data-column-id="<?= $column['id'] ?>"> + + <!-- column in collapsed mode --> + <div class="board-column-collapsed"> + </div> + + <!-- column in expanded mode --> + <div class="board-column-expanded board-column-expanded-header"> + <span class="board-column-title"> + </span> + + <span class="pull-right"> + </span> + </div> + + </th> + <?php endforeach ?> +</tr> diff --git a/app/Template/board/table_container.php b/app/Template/board/table_container.php new file mode 100644 index 0000000..cb1533b --- /dev/null +++ b/app/Template/board/table_container.php @@ -0,0 +1,67 @@ +<div id="board-container" + class="<?= ($project['task_limit'] && array_key_exists('nb_active_tasks', $project) && $project['nb_active_tasks'] > $project['task_limit']) ? 'board-task-list-limit' : '' ?>"> + <?php if (empty($swimlanes) || empty($swimlanes[0]['nb_columns'])): ?> + <p class="alert alert-error"><?= t('There is no column or swimlane activated in your project!') ?></p> + <?php else: ?> + + <?php if (isset($not_editable)): ?> + <table id="board" class="board-project-<?= $project['id'] ?>"> + <?php else: ?> + <table id="board" + class="board-project-<?= $project['id'] ?>" + data-project-id="<?= $project['id'] ?>" + data-check-interval="<?= $board_private_refresh_interval ?>" + data-save-url="<?= $this->url->href('BoardAjaxController', 'save', array('project_id' => $project['id'], 'csrf_token' => $this->app->getToken()->getReusableCSRFToken())) ?>" + data-reload-url="<?= $this->url->href('BoardAjaxController', 'reload', array('project_id' => $project['id'], 'csrf_token' => $this->app->getToken()->getReusableCSRFToken())) ?>" + data-check-url="<?= $this->url->href('BoardAjaxController', 'check', array('project_id' => $project['id'], 'timestamp' => time(), 'csrf_token' => $this->app->getToken()->getReusableCSRFToken())) ?>" + data-task-creation-url="<?= $this->url->href('TaskCreationController', 'show', array('project_id' => $project['id'])) ?>" + > + <?php endif ?> + + <?php foreach ($swimlanes as $index => $swimlane): ?> + <?php if (! ($swimlane['nb_tasks'] === 0 && isset($not_editable))): ?> + + <?php if ($index === 0 && $swimlane['nb_swimlanes'] > 1): ?> + <!-- Render empty columns to setup the "grid" for collapsing columns (Only once and only if more than 1 swimlane in project) --> + <?= $this->render('board/table_column_first', array( + 'swimlane' => $swimlane, + 'not_editable' => isset($not_editable), + )) ?> + <?php endif ?> + + <?php if ($index === 0 && $swimlane['nb_swimlanes'] > 1): ?> + <!-- Only show first swimlane-header if project more than 1 swimlanes --> + <?= $this->render('board/table_swimlane', array( + 'project' => $project, + 'swimlane' => $swimlane, + 'not_editable' => isset($not_editable), + )) ?> + <?php endif ?> + + <?php if ($index > 0 && $swimlane['nb_swimlanes'] > 1): ?> + <?= $this->render('board/table_swimlane', array( + 'project' => $project, + 'swimlane' => $swimlane, + 'not_editable' => isset($not_editable), + )) ?> + <?php endif ?> + + <?= $this->render('board/table_column', array( + 'swimlane' => $swimlane, + 'not_editable' => isset($not_editable), + )) ?> + + <?= $this->render('board/table_tasks', array( + 'project' => $project, + 'swimlane' => $swimlane, + 'not_editable' => isset($not_editable), + 'board_highlight_period' => $board_highlight_period, + )) ?> + + <?php endif ?> + <?php endforeach ?> + + </table> + + <?php endif ?> +</div> diff --git a/app/Template/board/table_swimlane.php b/app/Template/board/table_swimlane.php new file mode 100644 index 0000000..287bde3 --- /dev/null +++ b/app/Template/board/table_swimlane.php @@ -0,0 +1,25 @@ +<!-- swimlane --> +<tr id="swimlane-<?= $swimlane['id'] ?>"> + <th class="board-swimlane-header" colspan="<?= $swimlane['nb_columns'] ?>"> + <?php if (! $not_editable): ?> + <a href="#" class="board-swimlane-toggle" data-swimlane-id="<?= $swimlane['id'] ?>"> + <i class="fa fa-chevron-circle-up hide-icon-swimlane-<?= $swimlane['id'] ?>" title="<?= t('Collapse swimlane') ?>" role="button" aria-label="<?= t('Collapse swimlane') ?>"></i> + <i class="fa fa-chevron-circle-down show-icon-swimlane-<?= $swimlane['id'] ?>" title="<?= t('Expand swimlane') ?>" role="button" aria-label="<?= t('Expand swimlane') ?>" style="display: none"></i> + </a> + <?php endif ?> + + <?= $this->text->e($swimlane['name']) ?> + + <?php if (! $not_editable && ! empty($swimlane['description'])): ?> + <?= $this->app->tooltipLink('<i class="fa fa-info-circle"></i>', $this->url->href('BoardTooltipController', 'swimlane', array('swimlane_id' => $swimlane['id'], 'project_id' => $project['id']))) ?> + <?php endif ?> + + <span title="<?= t('Number of tasks in this swimlane') ?>" class="board-column-header-task-count swimlane-task-count-<?= $swimlane['id'] ?>"> + <?php if ($swimlane['task_limit']): ?> + (<span><span class="ui-helper-hidden-accessible"><?= t('Number of tasks in this swimlane') ?> </span><?= $swimlane['nb_tasks'] ?>/<?= $swimlane['task_limit'] ?>) + <?php else: ?> + (<span><span class="ui-helper-hidden-accessible"><?= t('Number of tasks in this swimlane') ?> </span><?= $swimlane['nb_tasks'] ?>) + <?php endif ?> + </span> + </th> +</tr> diff --git a/app/Template/board/table_tasks.php b/app/Template/board/table_tasks.php new file mode 100644 index 0000000..2526591 --- /dev/null +++ b/app/Template/board/table_tasks.php @@ -0,0 +1,40 @@ +<!-- task row --> +<tr class="board-swimlane board-swimlane-tasks-<?= $swimlane['id'] ?><?= $swimlane['task_limit'] && $swimlane['nb_tasks'] > $swimlane['task_limit'] ? ' board-task-list-limit' : '' ?>"> + <?php foreach ($swimlane['columns'] as $column): ?> + <td class=" + board-column-<?= $column['id'] ?> + <?= $column['task_limit'] > 0 && $column['column_nb_open_tasks'] > $column['task_limit'] ? 'board-task-list-limit' : '' ?> + " + > + + <!-- tasks list --> + <div + class="board-task-list board-column-expanded <?= $this->projectRole->isSortableColumn($column['project_id'], $column['id']) ? 'sortable-column' : '' ?>" + data-column-id="<?= $column['id'] ?>" + data-swimlane-id="<?= $swimlane['id'] ?>" + data-task-limit="<?= $column['task_limit'] ?>"> + + <?php foreach ($column['tasks'] as $task): ?> + <?= $this->render($not_editable ? 'board/task_public' : 'board/task_private', array( + 'project' => $project, + 'task' => $task, + 'board_highlight_period' => $board_highlight_period, + 'not_editable' => $not_editable, + )) ?> + <?php endforeach ?> + </div> + + <!-- column in collapsed mode (rotated text) --> + <div class="board-column-collapsed board-task-list sortable-column" + data-column-id="<?= $column['id'] ?>" + data-swimlane-id="<?= $swimlane['id'] ?>" + data-task-limit="<?= $column['task_limit'] ?>"> + <div class="board-rotation-wrapper"> + <div class="board-column-title board-rotation board-toggle-column-view" data-column-id="<?= $column['id'] ?>" title="<?= $this->text->e($column['title']) ?>"> + <i class="fa fa-plus-square" title="<?= t('Show this column') ?>" role="button" aria-label="<?= t('Show this column') ?>"></i> <?= $this->text->e($column['title']) ?> + </div> + </div> + </div> + </td> + <?php endforeach ?> +</tr> diff --git a/app/Template/board/task_avatar.php b/app/Template/board/task_avatar.php new file mode 100644 index 0000000..14dd239 --- /dev/null +++ b/app/Template/board/task_avatar.php @@ -0,0 +1,20 @@ +<?php if (! empty($task['owner_id'])): ?> +<div class="task-board-avatars"> + <span + <?php if ($this->user->hasProjectAccess('TaskModificationController', 'edit', $task['project_id'])): ?> + class="task-board-assignee task-board-change-assignee" + data-url="<?= $this->url->href('TaskModificationController', 'edit', array('task_id' => $task['id'])) ?>"> + <?php else: ?> + class="task-board-assignee"> + <?php endif ?> + <?= $this->avatar->small( + $task['owner_id'], + $task['assignee_username'], + $task['assignee_name'], + $task['assignee_email'], + $task['assignee_avatar_path'], + 'avatar-inline' + ) ?> + </span> +</div> +<?php endif ?> diff --git a/app/Template/board/task_footer.php b/app/Template/board/task_footer.php new file mode 100644 index 0000000..25f3586 --- /dev/null +++ b/app/Template/board/task_footer.php @@ -0,0 +1,141 @@ +<?php if (! empty($task['category_id'])): ?> +<div class="task-board-category-container task-board-category-container-color"> + <span class="task-board-category category-<?= $this->text->e($task['category_name']) ?> <?= $task['category_color_id'] ? "color-{$task['category_color_id']}" : '' ?>"> + <?php if ($not_editable): ?> + <?= $this->text->e($task['category_name']) ?> + <?php else: ?> + <?= $this->url->link( + $this->text->e($task['category_name']), + 'TaskModificationController', + 'edit', + array('task_id' => $task['id']), + false, + 'js-modal-large' . (! empty($task['category_description']) ? ' tooltip' : ''), + t('Change category') + ) ?> + <?php if (! empty($task['category_description'])): ?> + <?= $this->app->tooltipMarkdown($task['category_description']) ?> + <?php endif ?> + <?php endif ?> + </span> +</div> +<?php endif ?> + +<?php if (! empty($task['tags'])): ?> + <div class="task-tags"> + <ul> + <?php foreach ($task['tags'] as $tag): ?> + <li class="task-tag <?= $tag['color_id'] ? "color-{$tag['color_id']}" : '' ?>"><?= $this->text->e($tag['name']) ?></li> + <?php endforeach ?> + </ul> + </div> +<?php endif ?> + +<div class="task-board-icons"> + <div class="task-board-icons-row"> + <?php if ($task['reference']): ?> + <span class="task-board-reference" title="<?= t('Reference') ?>"> + <span class="ui-helper-hidden-accessible"><?= t('Reference') ?> </span><?= $this->task->renderReference($task) ?> + </span> + <?php endif ?> + </div> + <div class="task-board-icons-row"> + <?php if ($task['is_milestone'] == 1): ?> + <span title="<?= t('Milestone') ?>"> + <i class="fa fa-flag flag-milestone" role="img" aria-label="<?= t('Milestone') ?>"></i> + </span> + <?php endif ?> + + <?php if ($task['score']): ?> + <span class="task-score" title="<?= t('Complexity') ?>"> + <i class="fa fa-trophy" role="img" aria-label="<?= t('Complexity') ?>"></i> + <?= $this->text->e($task['score']) ?> + </span> + <?php endif ?> + + <?php if (! empty($task['time_estimated']) || ! empty($task['time_spent'])): ?> + <span class="task-time-estimated" title="<?= t('Time spent and estimated') ?>"> + <span class="ui-helper-hidden-accessible"><?= t('Time spent and estimated') ?> </span><?= $this->text->e($task['time_spent']) ?>/<?= $this->text->e($task['time_estimated']) ?>h + </span> + <?php endif ?> + + <?php if (! empty($task['date_due'])): ?> + <span class="task-date + <?php if (time() > $task['date_due']): ?> + task-date-overdue + <?php elseif (date('Y-m-d') == date('Y-m-d', $task['date_due'])): ?> + task-date-today + <?php endif ?> + "> + <i class="fa fa-calendar"></i> + <?php if (date('Hi', $task['date_due']) === '0000' ): ?> + <?= $this->dt->date($task['date_due']) ?> + <?php else: ?> + <?= $this->dt->datetime($task['date_due']) ?> + <?php endif ?> + </span> + <?php endif ?> + </div> + <div class="task-board-icons-row"> + + <?php if ($task['recurrence_status'] == \Kanboard\Model\TaskModel::RECURRING_STATUS_PENDING): ?> + <?= $this->app->tooltipLink('<i class="fa fa-refresh fa-rotate-90"></i>', $this->url->href('BoardTooltipController', 'recurrence', array('task_id' => $task['id']))) ?> + <?php endif ?> + + <?php if ($task['recurrence_status'] == \Kanboard\Model\TaskModel::RECURRING_STATUS_PROCESSED): ?> + <?= $this->app->tooltipLink('<i class="fa fa-refresh fa-rotate-90 fa-inverse"></i>', $this->url->href('BoardTooltipController', 'recurrence', array('task_id' => $task['id']))) ?> + <?php endif ?> + + <?php if (! empty($task['nb_links'])): ?> + <?= $this->app->tooltipLink('<i class="fa fa-code-fork fa-fw"></i>'.$task['nb_links'], $this->url->href('BoardTooltipController', 'tasklinks', array('task_id' => $task['id']))) ?> + <?php endif ?> + + <?php if (! empty($task['nb_external_links'])): ?> + <?= $this->app->tooltipLink('<i class="fa fa-external-link fa-fw"></i>'.$task['nb_external_links'], $this->url->href('BoardTooltipController', 'externallinks', array('task_id' => $task['id']))) ?> + <?php endif ?> + + <?php if (! empty($task['nb_subtasks'])): ?> + <?php $nb_subtasks = (int) $task['nb_subtasks']; $nb_completed = (int) $task['nb_completed_subtasks']; $percentage = $nb_subtasks > 0 ? round($nb_completed / $nb_subtasks * 100, 0) : 0; ?> + <?= $this->app->tooltipLink('<i class="fa fa-bars fa-fw"></i>'.$percentage.'% ('.$nb_completed.'/'.$nb_subtasks.')', $this->url->href('BoardTooltipController', 'subtasks', array('task_id' => $task['id']))) ?> + <?php endif ?> + + <?php if (! empty($task['nb_files'])): ?> + <?= $this->app->tooltipLink('<i class="fa fa-paperclip fa-fw"></i>'.$task['nb_files'], $this->url->href('BoardTooltipController', 'attachments', array('task_id' => $task['id']))) ?> + <?php endif ?> + + <?php if ($task['nb_comments'] > 0): ?> + <?php if ($not_editable): ?> + <?php $aria_label = $task['nb_comments'] == 1 ? t('%d comment', $task['nb_comments']) : t('%d comments', $task['nb_comments']); ?> + <span title="<?= $aria_label ?>" role="img" aria-label="<?= $aria_label ?>"><i class="fa fa-comments-o"></i> <?= $task['nb_comments'] ?></span> + <?php else: ?> + <?= $this->modal->medium( + 'comments-o', + $task['nb_comments'], + 'CommentListController', + 'show', + array('task_id' => $task['id']), + $task['nb_comments'] == 1 ? t('%d comment', $task['nb_comments']) : t('%d comments', $task['nb_comments']) + ) ?> + <?php endif ?> + <?php endif ?> + + <?php if (! empty($task['description'])): ?> + <?= $this->app->tooltipLink('<i class="fa fa-file-text-o"></i>', $this->url->href('BoardTooltipController', 'description', array('task_id' => $task['id']))) ?> + <?php endif ?> + + <?php if ($task['is_active'] == 1): ?> + <div class="task-icon-age"> + <span title="<?= t('Task age in days')?>" class="task-icon-age-total"><span class="ui-helper-hidden-accessible"><?= t('Task age in days') ?> </span><?= $this->dt->age($task['date_creation']) ?></span> + <span title="<?= t('Days in this column')?>" class="task-icon-age-column"><span class="ui-helper-hidden-accessible"><?= t('Days in this column') ?> </span><?= $this->dt->age($task['date_moved']) ?></span> + </div> + <?php else: ?> + <span class="task-board-closed"><i class="fa fa-ban fa-fw"></i><?= t('Closed') ?></span> + <?php endif ?> + + <?= $this->task->renderPriority($task['priority']) ?> + + <?= $this->hook->render('template:board:task:icons', array('task' => $task)) ?> + </div> +</div> + +<?= $this->hook->render('template:board:task:footer', array('task' => $task)) ?> diff --git a/app/Template/board/task_private.php b/app/Template/board/task_private.php new file mode 100644 index 0000000..276a407 --- /dev/null +++ b/app/Template/board/task_private.php @@ -0,0 +1,71 @@ +<div class=" + task-board + <?= $task['is_draggable'] ? 'draggable-item ' : '' ?> + <?= $task['is_active'] == 1 ? 'task-board-status-open '.($task['date_modification'] > (time() - $board_highlight_period) ? 'task-board-recent' : '') : 'task-board-status-closed' ?> + color-<?= $task['color_id'] ?>" + data-task-id="<?= $task['id'] ?>" + data-column-id="<?= $task['column_id'] ?>" + data-swimlane-id="<?= $task['swimlane_id'] ?>" + data-position="<?= $task['position'] ?>" + data-owner-id="<?= $task['owner_id'] ?>" + data-category-id="<?= $task['category_id'] ?>" + data-due-date="<?= $task['date_due'] ?>" + data-task-url="<?= $this->url->href('TaskViewController', 'show', array('task_id' => $task['id'])) ?>"> + + <div class="task-board-sort-handle" style="display: none;"><i class="fa fa-arrows-alt"></i></div> + + <?php if ($this->board->isCollapsed($task['project_id'])): ?> + <div class="task-board-collapsed"> + <div class="task-board-saving-icon" style="display: none;"><i class="fa fa-spinner fa-pulse"></i></div> + <?php if ($this->user->hasProjectAccess('TaskModificationController', 'edit', $task['project_id'])): ?> + <?= $this->render('task/dropdown', array('task' => $task, 'redirect' => 'board')) ?> + <?php if ($this->projectRole->canUpdateTask($task)): ?> + <?= $this->modal->large('edit', '', 'TaskModificationController', 'edit', array('task_id' => $task['id'])) ?> + <?php endif ?> + <?php else: ?> + <strong><?= '#'.$task['id'] ?></strong> + <?php endif ?> + + <?php if (! empty($task['assignee_username'])): ?> + <span title="<?= $this->text->e($task['assignee_name'] ?: $task['assignee_username']) ?>"> + <?= $this->text->e($this->user->getInitials($task['assignee_name'] ?: $task['assignee_username'])) ?> + </span> - + <?php endif ?> + <?= $this->url->link($this->text->e($task['title']), 'TaskViewController', 'show', array('task_id' => $task['id']), false, '', $this->text->e($task['title'])) ?> + </div> + <?php else: ?> + <div class="task-board-expanded"> + <div class="task-board-saving-icon" style="display: none;"><i class="fa fa-spinner fa-pulse fa-2x"></i></div> + <div class="task-board-header"> + <?php if ($this->user->hasProjectAccess('TaskModificationController', 'edit', $task['project_id'])): ?> + <?= $this->render('task/dropdown', array('task' => $task, 'redirect' => 'board')) ?> + <?php if ($this->projectRole->canUpdateTask($task)): ?> + <?= $this->modal->large('edit', '', 'TaskModificationController', 'edit', array('task_id' => $task['id'])) ?> + <?php endif ?> + <?php else: ?> + <strong><?= '#'.$task['id'] ?></strong> + <?php endif ?> + + <?php if (! empty($task['owner_id'])): ?> + <span class="task-board-assignee"> + <?= $this->text->e($task['assignee_name'] ?: $task['assignee_username']) ?> + </span> + <?php endif ?> + + <?= $this->render('board/task_avatar', array('task' => $task)) ?> + </div> + + <?= $this->hook->render('template:board:private:task:before-title', array('task' => $task)) ?> + <div class="task-board-title"> + <?= $this->url->link($this->text->e($task['title']), 'TaskViewController', 'show', array('task_id' => $task['id'])) ?> + </div> + <?= $this->hook->render('template:board:private:task:after-title', array('task' => $task)) ?> + + <?= $this->render('board/task_footer', array( + 'task' => $task, + 'not_editable' => $not_editable, + 'project' => $project, + )) ?> + </div> + <?php endif ?> +</div> diff --git a/app/Template/board/task_public.php b/app/Template/board/task_public.php new file mode 100644 index 0000000..8e429ea --- /dev/null +++ b/app/Template/board/task_public.php @@ -0,0 +1,25 @@ +<div class="task-board color-<?= $task['color_id'] ?> <?= $task['date_modification'] > time() - $board_highlight_period ? 'task-board-recent' : '' ?>"> + <div class="task-board-header"> + <?= $this->url->link('#'.$task['id'], 'TaskViewController', 'readonly', array('task_id' => $task['id'], 'token' => $project['token'])) ?> + + <?php if (! empty($task['owner_id'])): ?> + <span class="task-board-assignee"> + <?= $this->text->e($task['assignee_name'] ?: $task['assignee_username']) ?> + </span> + <?php endif ?> + + <?= $this->render('board/task_avatar', array('task' => $task)) ?> + </div> + + <?= $this->hook->render('template:board:public:task:before-title', array('task' => $task)) ?> + <div class="task-board-title"> + <?= $this->url->link($this->text->e($task['title']), 'TaskViewController', 'readonly', array('task_id' => $task['id'], 'token' => $project['token'])) ?> + </div> + <?= $this->hook->render('template:board:public:task:after-title', array('task' => $task)) ?> + + <?= $this->render('board/task_footer', array( + 'task' => $task, + 'not_editable' => $not_editable, + 'project' => $project, + )) ?> +</div> diff --git a/app/Template/board/tooltip_description.php b/app/Template/board/tooltip_description.php new file mode 100644 index 0000000..7e0e343 --- /dev/null +++ b/app/Template/board/tooltip_description.php @@ -0,0 +1,5 @@ +<section class="tooltip-large"> +<div class="markdown"> + <?= $this->text->markdown($task['description']) ?> +</div> +</section> \ No newline at end of file diff --git a/app/Template/board/tooltip_external_links.php b/app/Template/board/tooltip_external_links.php new file mode 100644 index 0000000..fd5237a --- /dev/null +++ b/app/Template/board/tooltip_external_links.php @@ -0,0 +1,22 @@ +<div class="tooltip-large"> + <table class="table-small"> + <tr> + <th class="column-20"><?= t('Type') ?></th> + <th class="column-70"><?= t('Title') ?></th> + <th class="column-10"><?= t('Dependency') ?></th> + </tr> + <?php foreach ($links as $link): ?> + <tr> + <td> + <?= $link['type'] ?> + </td> + <td> + <a href="<?= $this->text->e($link['url']) ?>" title="<?= $this->text->e($link['url']) ?>" target="_blank"><?= $this->text->e($link['title']) ?></a> + </td> + <td> + <?= $this->text->e($link['dependency_label']) ?> + </td> + </tr> + <?php endforeach ?> + </table> +</div> diff --git a/app/Template/board/tooltip_files.php b/app/Template/board/tooltip_files.php new file mode 100644 index 0000000..29917f1 --- /dev/null +++ b/app/Template/board/tooltip_files.php @@ -0,0 +1,24 @@ +<div class="tooltip-large"> + <table class="table-small"> + <?php foreach ($files as $file): ?> + <tr> + <th> + <i class="fa <?= $this->file->icon($file['name']) ?> fa-fw"></i> + <?= $this->text->e($file['name']) ?> + </th> + </tr> + <tr> + <td> + <?= $this->url->icon('download', t('Download'), 'FileViewerController', 'download', array('task_id' => $task['id'], 'file_id' => $file['id'], 'etag' => $file['etag'])) ?> + <?php if ($this->file->getPreviewType($file['name']) !== null || $file['is_image'] == 1): ?> +  <?= $this->modal->large('eye', t('View file'), 'FileViewerController', 'show', array('task_id' => $task['id'], 'file_id' => $file['id'], 'etag' => $file['etag'])) ?> +  <?= $this->url->icon('external-link', t('View file'), 'FileViewerController', ($file['is_image'] == 1 ? 'image' : 'show'), array('task_id' => $task['id'], 'file_id' => $file['id'], 'etag' => $file['etag']), false, '', '', true) ?> + <?php elseif ($this->file->getBrowserViewType($file['name']) !== null): ?> + <i class="fa fa-eye fa-fw"></i> + <?= $this->url->link(t('View file'), 'FileViewerController', 'browser', array('task_id' => $task['id'], 'file_id' => $file['id']), false, '', '', true) ?> + <?php endif ?> + </td> + </tr> + <?php endforeach ?> + </table> +</div> diff --git a/app/Template/board/tooltip_subtasks.php b/app/Template/board/tooltip_subtasks.php new file mode 100644 index 0000000..753e491 --- /dev/null +++ b/app/Template/board/tooltip_subtasks.php @@ -0,0 +1,24 @@ +<div class="tooltip-large"> + <table class="table-small"> + <tr> + <th class="column-70"><?= t('Subtask') ?></th> + <?= $this->hook->render('template:board:tooltip:subtasks:header:before-assignee') ?> + <th><?= t('Assignee') ?></th> + </tr> + <?php foreach ($subtasks as $subtask): ?> + <tr> + <td> + <?= $this->subtask->renderToggleStatus($task, $subtask) ?> + </td> + <?= $this->hook->render('template:board:tooltip:subtasks:rows', array('subtask' => $subtask)) ?> + <td> + <?php if (! empty($subtask['username'])): ?> + <?= $this->text->e($subtask['name'] ?: $subtask['username']) ?> + <?php else: ?> + <?= t('Not assigned') ?> + <?php endif ?> + </td> + </tr> + <?php endforeach ?> + </table> +</div> diff --git a/app/Template/board/tooltip_tasklinks.php b/app/Template/board/tooltip_tasklinks.php new file mode 100644 index 0000000..1d8d8d6 --- /dev/null +++ b/app/Template/board/tooltip_tasklinks.php @@ -0,0 +1,34 @@ +<div class="tooltip-large"> + <table class="table-small"> + <?php foreach ($links as $label => $grouped_links): ?> + <tr> + <th colspan="4"><?= t($label) ?></th> + </tr> + <?php foreach ($grouped_links as $link): ?> + <tr> + <td class="column-10"> + <?= $this->task->getProgress($link).'%' ?> + </td> + <td class="column-60"> + <?= $this->url->link( + $this->text->e('#'.$link['task_id'].' '.$link['title']), + 'TaskViewController', 'show', array('task_id' => $link['task_id']), + false, + $link['is_active'] ? '' : 'task-link-closed' + ) ?> + </td> + <td> + <?php if (! empty($link['task_assignee_username'])): ?> + <?= $this->text->e($link['task_assignee_name'] ?: $link['task_assignee_username']) ?> + <?php else: ?> + <?= t('Not assigned') ?> + <?php endif ?> + </td> + <td> + <?= $link['project_name'] ?> + </td> + </tr> + <?php endforeach ?> + <?php endforeach ?> + </table> +</div> diff --git a/app/Template/board/view_private.php b/app/Template/board/view_private.php new file mode 100644 index 0000000..a89e7d2 --- /dev/null +++ b/app/Template/board/view_private.php @@ -0,0 +1,12 @@ +<section id="main"> + + <?= $this->projectHeader->render($project, 'BoardViewController', 'show', true) ?> + + <?= $this->render('board/table_container', array( + 'project' => $project, + 'swimlanes' => $swimlanes, + 'board_private_refresh_interval' => $board_private_refresh_interval, + 'board_highlight_period' => $board_highlight_period, + )) ?> + +</section> diff --git a/app/Template/board/view_public.php b/app/Template/board/view_public.php new file mode 100644 index 0000000..aea7203 --- /dev/null +++ b/app/Template/board/view_public.php @@ -0,0 +1,11 @@ +<section id="main" class="public-board"> + + <?= $this->render('board/table_container', array( + 'project' => $project, + 'swimlanes' => $swimlanes, + 'board_private_refresh_interval' => $board_private_refresh_interval, + 'board_highlight_period' => $board_highlight_period, + 'not_editable' => true, + )) ?> + +</section> \ No newline at end of file diff --git a/app/Template/board_popover/close_all_tasks_column.php b/app/Template/board_popover/close_all_tasks_column.php new file mode 100644 index 0000000..ab7c2d4 --- /dev/null +++ b/app/Template/board_popover/close_all_tasks_column.php @@ -0,0 +1,15 @@ +<div class="page-header"> + <h2><?= t('Do you really want to close all tasks of this column?') ?></h2> +</div> +<form method="post" action="<?= $this->url->href('BoardPopoverController', 'closeColumnTasks', array('project_id' => $project['id'])) ?>"> + <?= $this->form->csrf() ?> + <?= $this->form->hidden('column_id', $values) ?> + <?= $this->form->hidden('swimlane_id', $values) ?> + + <p class="alert"><?= t('%d task(s) in the column "%s" and the swimlane "%s" will be closed.', $nb_tasks, $column, $swimlane) ?></p> + + <?= $this->modal->submitButtons(array( + 'submitLabel' => t('Yes'), + 'color' => 'red', + )) ?> +</form> diff --git a/app/Template/category/create.php b/app/Template/category/create.php new file mode 100644 index 0000000..32501b8 --- /dev/null +++ b/app/Template/category/create.php @@ -0,0 +1,14 @@ +<div class="page-header"> + <h2><?= t('Add a new category') ?></h2> +</div> +<form method="post" action="<?= $this->url->href('CategoryController', 'save', array('project_id' => $project['id'])) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + + <?= $this->form->label(t('Category Name'), 'name') ?> + <?= $this->form->text('name', $values, $errors, array('autofocus', 'required')) ?> + + <?= $this->form->label(t('Color'), 'color_id') ?> + <?= $this->form->select('color_id', array('' => t('No color')) + $colors, $values, $errors, array(), 'color-picker') ?> + + <?= $this->modal->submitButtons() ?> +</form> diff --git a/app/Template/category/edit.php b/app/Template/category/edit.php new file mode 100644 index 0000000..446ce7b --- /dev/null +++ b/app/Template/category/edit.php @@ -0,0 +1,18 @@ +<div class="page-header"> + <h2><?= t('Category modification for the project "%s"', $project['name']) ?></h2> +</div> + +<form method="post" action="<?= $this->url->href('CategoryController', 'update', array('project_id' => $project['id'], 'category_id' => $values['id'])) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + + <?= $this->form->label(t('Category Name'), 'name') ?> + <?= $this->form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="191"', 'tabindex="1"')) ?> + + <?= $this->form->label(t('Description'), 'description') ?> + <?= $this->form->textEditor('description', $values, $errors, array('tabindex' => 2)) ?> + + <?= $this->form->label(t('Color'), 'color_id') ?> + <?= $this->form->select('color_id', array('' => t('No color')) + $colors, $values, $errors, array(), 'color-picker') ?> + + <?= $this->modal->submitButtons() ?> +</form> diff --git a/app/Template/category/index.php b/app/Template/category/index.php new file mode 100644 index 0000000..36f666d --- /dev/null +++ b/app/Template/category/index.php @@ -0,0 +1,42 @@ +<div class="page-header"> + <h2><?= t('Categories') ?></h2> + <ul> + <li> + <?= $this->modal->medium('plus', t('Add a new category'), 'CategoryController', 'create', array('project_id' => $project['id'])) ?> + </li> + </ul> +</div> +<?php if (empty($categories)): ?> + <p class="alert"><?= t('There is no category in this project.') ?></p> +<?php else: ?> + <table class="table-striped"> + <tr> + <th><?= t('Category Name') ?></th> + <th><?= t('Color') ?></th> + </tr> + <?php foreach ($categories as $category): ?> + <tr> + <td> + <div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-icon"><i class="fa fa-cog"></i><i class="fa fa-caret-down"></i></a> + <ul> + <li> + <?= $this->modal->medium('edit', t('Edit'), 'CategoryController', 'edit', array('project_id' => $project['id'], 'category_id' => $category['id'])) ?> + </li> + <li> + <?= $this->modal->confirm('trash-o', t('Remove'), 'CategoryController', 'confirm', array('project_id' => $project['id'], 'category_id' => $category['id'])) ?> + </li> + </ul> + </div> + + <?= $this->text->e($category['name']) ?> + + <?php if (! empty($category['description'])): ?> + <?= $this->app->tooltipMarkdown($category['description']) ?> + <?php endif ?> + </td> + <td><?= $this->text->e($colors[$category['color_id']] ?? '') ?></td> + </tr> + <?php endforeach ?> + </table> +<?php endif ?> diff --git a/app/Template/category/remove.php b/app/Template/category/remove.php new file mode 100644 index 0000000..79e8a56 --- /dev/null +++ b/app/Template/category/remove.php @@ -0,0 +1,15 @@ +<div class="page-header"> + <h2><?= t('Remove a category') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"> + <?= t('Do you really want to remove this category: "%s"?', $category['name']) ?> + </p> + + <?= $this->modal->confirmButtons( + 'CategoryController', + 'remove', + array('project_id' => $project['id'], 'category_id' => $category['id']) + ) ?> +</div> diff --git a/app/Template/column/create.php b/app/Template/column/create.php new file mode 100644 index 0000000..1a91ee0 --- /dev/null +++ b/app/Template/column/create.php @@ -0,0 +1,19 @@ +<div class="page-header"> + <h2><?= t('Add a new column') ?></h2> +</div> +<form method="post" action="<?= $this->url->href('ColumnController', 'save', array('project_id' => $project['id'])) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + + <?= $this->form->label(t('Title'), 'title') ?> + <?= $this->form->text('title', $values, $errors, array('autofocus', 'required', 'maxlength="191"', 'tabindex="1"')) ?> + + <?= $this->form->label(t('Task limit'), 'task_limit') ?> + <?= $this->form->number('task_limit', $values, $errors, array('tabindex="2"', 'min="0"')) ?> + + <?= $this->form->checkbox('hide_in_dashboard', t('Hide tasks in this column in the dashboard'), 1, false, '', array('tabindex' => 3)) ?> + + <?= $this->form->label(t('Description'), 'description') ?> + <?= $this->form->textEditor('description', $values, $errors, array('tabindex' => 4)) ?> + + <?= $this->modal->submitButtons() ?> +</form> diff --git a/app/Template/column/edit.php b/app/Template/column/edit.php new file mode 100644 index 0000000..c5a3ae1 --- /dev/null +++ b/app/Template/column/edit.php @@ -0,0 +1,20 @@ +<div class="page-header"> + <h2><?= t('Edit column "%s"', $column['title']) ?></h2> +</div> + +<form method="post" action="<?= $this->url->href('ColumnController', 'update', array('project_id' => $project['id'], 'column_id' => $column['id'])) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + + <?= $this->form->label(t('Title'), 'title') ?> + <?= $this->form->text('title', $values, $errors, array('autofocus', 'required', 'maxlength="191"')) ?> + + <?= $this->form->label(t('Task limit'), 'task_limit') ?> + <?= $this->form->number('task_limit', $values, $errors, array('min="0"')) ?> + + <?= $this->form->checkbox('hide_in_dashboard', t('Hide tasks in this column in the dashboard'), 1, $values['hide_in_dashboard'] == 1) ?> + + <?= $this->form->label(t('Description'), 'description') ?> + <?= $this->form->textEditor('description', $values, $errors) ?> + + <?= $this->modal->submitButtons() ?> +</form> diff --git a/app/Template/column/index.php b/app/Template/column/index.php new file mode 100644 index 0000000..5e6f468 --- /dev/null +++ b/app/Template/column/index.php @@ -0,0 +1,64 @@ +<div class="page-header"> + <h2><?= t('Edit the board for "%s"', $project['name']) ?></h2> + <ul> + <li> + <?= $this->modal->medium('plus', t('Add a new column'), 'ColumnController', 'create', array('project_id' => $project['id'])) ?> + </li> + </ul> +</div> + +<?php if (empty($columns)): ?> + <p class="alert alert-error"><?= t('Your board doesn\'t have any columns!') ?></p> +<?php else: ?> + <table + class="columns-table table-striped" + data-save-position-url="<?= $this->url->href('ColumnController', 'move', array('project_id' => $project['id'], 'csrf_token' => $this->app->getToken()->getReusableCSRFToken())) ?>"> + <thead> + <tr> + <th><?= t('Column') ?></th> + <th class="column-10"><?= t('Task limit') ?></th> + <th class="column-15"><?= t('Visible on dashboard') ?></th> + <th class="column-12"><?= t('Open tasks') ?></th> + <th class="column-12"><?= t('Closed tasks') ?></th> + </tr> + </thead> + <tbody> + <?php foreach ($columns as $column): ?> + <tr data-column-id="<?= $column['id'] ?>"> + <td> + <i class="fa fa-arrows-alt draggable-row-handle" title="<?= t('Change column position') ?>" role="button" aria-label="<?= t('Change column position') ?>"></i>  + <div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-icon"><i class="fa fa-cog"></i><i class="fa fa-caret-down"></i></a> + <ul> + <li> + <?= $this->modal->medium('edit', t('Edit'), 'ColumnController', 'edit', array('project_id' => $project['id'], 'column_id' => $column['id'])) ?> + </li> + <?php if ($column['nb_open_tasks'] == 0 && $column['nb_closed_tasks'] == 0): ?> + <li> + <?= $this->modal->confirm('trash-o', t('Remove'), 'ColumnController', 'confirm', array('project_id' => $project['id'], 'column_id' => $column['id'])) ?> + </li> + <?php endif ?> + </ul> + </div> + <?= $this->text->e($column['title']) ?> + <?php if (! empty($column['description'])): ?> + <?= $this->app->tooltipMarkdown($column['description']) ?> + <?php endif ?> + </td> + <td> + <?= $column['task_limit'] ?: '∞' ?> + </td> + <td> + <?= $column['hide_in_dashboard'] == 0 ? t('Yes') : t('No') ?> + </td> + <td> + <?= $column['nb_open_tasks'] ?> + </td> + <td> + <?= $column['nb_closed_tasks'] ?> + </td> + </tr> + <?php endforeach ?> + </tbody> + </table> +<?php endif ?> diff --git a/app/Template/column/remove.php b/app/Template/column/remove.php new file mode 100644 index 0000000..b4e7942 --- /dev/null +++ b/app/Template/column/remove.php @@ -0,0 +1,15 @@ +<div class="page-header"> + <h2><?= t('Remove a column') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"> + <?= t('Do you really want to remove this column: "%s"?', $column['title']) ?> + </p> + + <?= $this->modal->confirmButtons( + 'ColumnController', + 'remove', + array('project_id' => $project['id'], 'column_id' => $column['id']) + ) ?> +</div> diff --git a/app/Template/column_move_restriction/create.php b/app/Template/column_move_restriction/create.php new file mode 100644 index 0000000..cd9e1bf --- /dev/null +++ b/app/Template/column_move_restriction/create.php @@ -0,0 +1,20 @@ +<div class="page-header"> + <h2><?= t('New drag and drop restriction for the role "%s"', $role['role']) ?></h2> +</div> +<form method="post" action="<?= $this->url->href('ColumnMoveRestrictionController', 'save', array('project_id' => $project['id'])) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + <?= $this->form->hidden('project_id', $values) ?> + <?= $this->form->hidden('role_id', $values) ?> + + <?= $this->form->label(t('Source column'), 'src_column_id') ?> + <?= $this->form->select('src_column_id', $columns, $values, $errors) ?> + + <?= $this->form->label(t('Destination column'), 'dst_column_id') ?> + <?= $this->form->select('dst_column_id', $columns, $values, $errors) ?> + + <?= $this->form->checkbox('only_assigned', t('Only for tasks assigned to the current user'), 1, isset($values['only_assigned']) && $values['only_assigned'] == 1) ?> + + <?= $this->modal->submitButtons() ?> + + <p class="alert alert-info"><?= t('People belonging to this role will be able to move tasks only between the source and the destination column.') ?></p> +</form> diff --git a/app/Template/column_move_restriction/remove.php b/app/Template/column_move_restriction/remove.php new file mode 100644 index 0000000..4902cd2 --- /dev/null +++ b/app/Template/column_move_restriction/remove.php @@ -0,0 +1,15 @@ +<div class="page-header"> + <h2><?= t('Remove a column restriction') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"> + <?= t('Do you really want to remove this column restriction: "%s" to "%s"?', $restriction['src_column_title'], $restriction['dst_column_title']) ?> + </p> + + <?= $this->modal->confirmButtons( + 'ColumnMoveRestrictionController', + 'remove', + array('project_id' => $project['id'], 'restriction_id' => $restriction['restriction_id']) + ) ?> +</div> diff --git a/app/Template/column_restriction/create.php b/app/Template/column_restriction/create.php new file mode 100644 index 0000000..be158f1 --- /dev/null +++ b/app/Template/column_restriction/create.php @@ -0,0 +1,16 @@ +<div class="page-header"> + <h2><?= t('New column restriction for the role "%s"', $role['role']) ?></h2> +</div> +<form method="post" action="<?= $this->url->href('ColumnRestrictionController', 'save', array('project_id' => $project['id'])) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + <?= $this->form->hidden('project_id', $values) ?> + <?= $this->form->hidden('role_id', $values) ?> + + <?= $this->form->label(t('Rule'), 'rule') ?> + <?= $this->form->select('rule', $rules, $values, $errors) ?> + + <?= $this->form->label(t('Column'), 'column_id') ?> + <?= $this->form->select('column_id', $columns, $values, $errors) ?> + + <?= $this->modal->submitButtons() ?> +</form> diff --git a/app/Template/column_restriction/remove.php b/app/Template/column_restriction/remove.php new file mode 100644 index 0000000..edbd9d6 --- /dev/null +++ b/app/Template/column_restriction/remove.php @@ -0,0 +1,15 @@ +<div class="page-header"> + <h2><?= t('Remove a column restriction') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"> + <?= t('Do you really want to remove this column restriction?') ?> + </p> + + <?= $this->modal->confirmButtons( + 'ColumnRestrictionController', + 'remove', + array('project_id' => $project['id'], 'restriction_id' => $restriction['restriction_id']) + ) ?> +</div> diff --git a/app/Template/comment/create.php b/app/Template/comment/create.php new file mode 100644 index 0000000..4d815c5 --- /dev/null +++ b/app/Template/comment/create.php @@ -0,0 +1,36 @@ +<?php use Kanboard\Core\Security\Role; ?> +<div class="page-header"> + <h2><?= t('Add a comment') ?></h2> + <ul> + <li> + <?= $this->modal->medium('paper-plane', t('Send by email'), 'CommentMailController', 'create', array('task_id' => $task['id'])) ?> + </li> + </ul> +</div> +<form method="post" action="<?= $this->url->href('CommentController', 'save', array('task_id' => $task['id'])) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + + <?= $this->form->textEditor('comment', $values, $errors, array('autofocus' => true, 'required' => true, 'aria-label' => t('New comment'))) ?> + + <?php + $formName = 'visibility'; + $visibilityOptions['app-user'] = t('Standard users'); + $attribute[] = ('hidden'); + ?> + + <?php if ($this->user->getRole() !== Role::APP_USER) { + echo $this->form->label(t('Visibility:'), $formName); + $attribute = []; + $visibilityOptions['app-user'] = t('Standard users'); + $visibilityOptions['app-manager'] = t('Application managers or more'); + } + ?> + + <?php if ($this->user->getRole() === Role::APP_ADMIN) { + $visibilityOptions['app-admin'] = t('Administrators'); + } + ?> + + <?= $this->form->select($formName, $visibilityOptions, array(), array(), $attribute) ?> + <?= $this->modal->submitButtons() ?> +</form> diff --git a/app/Template/comment/edit.php b/app/Template/comment/edit.php new file mode 100644 index 0000000..d2e073e --- /dev/null +++ b/app/Template/comment/edit.php @@ -0,0 +1,11 @@ +<div class="page-header"> + <h2><?= t('Edit a comment') ?></h2> +</div> + +<form method="post" action="<?= $this->url->href('CommentController', 'update', array('task_id' => $task['id'], 'comment_id' => $comment['id'])) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + + <?= $this->form->textEditor('comment', $values, $errors, array('autofocus' => true, 'required' => true, 'aria-label' => t('Comment'))) ?> + + <?= $this->modal->submitButtons() ?> +</form> diff --git a/app/Template/comment/remove.php b/app/Template/comment/remove.php new file mode 100644 index 0000000..14ab478 --- /dev/null +++ b/app/Template/comment/remove.php @@ -0,0 +1,21 @@ +<div class="page-header"> + <h2><?= t('Remove a comment') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"> + <?= t('Do you really want to remove this comment?') ?> + </p> + + <?= $this->render('comment/show', array( + 'comment' => $comment, + 'task' => $task, + 'hide_actions' => true + )) ?> + + <?= $this->modal->confirmButtons( + 'CommentController', + 'remove', + array('task_id' => $task['id'], 'comment_id' => $comment['id']) + ) ?> +</div> diff --git a/app/Template/comment/show.php b/app/Template/comment/show.php new file mode 100644 index 0000000..b0fdab8 --- /dev/null +++ b/app/Template/comment/show.php @@ -0,0 +1,75 @@ +<?php + +use Kanboard\Core\Security\Role; + +$userRole = $this->user->getRole(); + +if ($userRole === '' && $comment['visibility'] !== Role::APP_USER) { + return; +} + +if ($userRole === Role::APP_MANAGER && $comment['visibility'] === Role::APP_ADMIN) { + return; +} + +if ($userRole === Role::APP_USER && $comment['visibility'] !== Role::APP_USER) { + return; +} + +?> + +<div class="comment <?= isset($preview) ? 'comment-preview' : '' ?>" id="comment-<?= $comment['id'] ?>"> + + <?= $this->avatar->render($comment['user_id'], $comment['username'], $comment['name'], $comment['email'], $comment['avatar_path']) ?> + + <div class="comment-title"> + <?php if (! empty($comment['username'])): ?> + <strong class="comment-username"><?= $this->text->e($comment['name'] ?: $comment['username']) ?></strong> + <?php endif ?> + + <small class="comment-date"><?= t('Created at:') ?> <?= $this->dt->datetime($comment['date_creation']) ?></small> + <small class="comment-date"><?= t('Updated at:') ?> <?= $this->dt->datetime($comment['date_modification']) ?></small> + <small class="comment-visibility"><?= t('Visibility:') ?> + <?php if ($comment['visibility'] === Role::APP_USER): ?> + <?= t('Standard users') ?> + <?php elseif ($comment['visibility'] === Role::APP_MANAGER): ?> + <?= t('Application managers or more') ?> + <?php else: ?> + <?= t('Administrators') ?> + <?php endif ?> + </small> + </div> + + <?php if (! isset($hide_actions)): ?> + <div class="comment-actions"> + <div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-icon"><i class="fa fa-cog"></i><i class="fa fa-caret-down"></i></a> + <ul> + <li> + <?= $this->url->icon('link', t('Link'), 'TaskViewController', 'show', array('task_id' => $task['id']), false, '', '', $this->app->isAjax(), 'comment-'.$comment['id']) ?> + </li> + <li data-comment-id="<?= $comment['id'] ?>"> + <?= $this->url->icon('reply', t('Reply'), 'TaskViewController', 'show', array('task_id' => $task['id']), false, 'js-reply-to-comment', '', $this->app->isAjax(), 'form-task_id') ?> + </li> + <?php if ($editable && ($this->user->isAdmin() || $this->user->isCurrentUser($comment['user_id']))): ?> + <li> + <?= $this->modal->medium('edit', t('Edit'), 'CommentController', 'edit', array('task_id' => $task['id'], 'comment_id' => $comment['id'])) ?> + </li> + <li> + <?= $this->modal->confirm('trash-o', t('Remove'), 'CommentController', 'confirm', array('task_id' => $task['id'], 'comment_id' => $comment['id'])) ?> + </li> + <?php endif ?> + </ul> + </div> + </div> + <?php endif ?> + + <div class="comment-content"> + <div class="markdown"> + <?= $this->text->markdown($comment['comment'], isset($is_public) && $is_public) ?> + </div> + <template id="comment-reply-content-<?= $comment['id'] ?>"> + <textarea><?= $this->text->reply($comment['name'] ?: $comment['username'], $comment['comment']) ?></textarea> + </template> + </div> +</div> \ No newline at end of file diff --git a/app/Template/comment_list/create.php b/app/Template/comment_list/create.php new file mode 100644 index 0000000..f5de18b --- /dev/null +++ b/app/Template/comment_list/create.php @@ -0,0 +1,30 @@ +<?php use Kanboard\Core\Security\Role;?> +<div class="page-header"> + <h2><?= t('Add a comment') ?></h2> +</div> +<form method="post" action="<?= $this->url->href('CommentListController', 'save', array('task_id' => $task['id'])) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + <?= $this->form->textEditor('comment', array('project_id' => $task['project_id']), array(), array('required' => true, 'aria-label' => t('New comment'))) ?> + + <?php + $formName = 'visibility'; + $visibilityOptions['app-user'] = t('Standard users'); + $attribute[] = ('hidden'); + ?> + + <?php if ($this->user->getRole() !== Role::APP_USER) { + echo $this->form->label(t('Visibility:'), $formName); + $attribute = []; + $visibilityOptions['app-user'] = t('Standard users'); + $visibilityOptions['app-manager'] = t('Application managers or more'); + } + ?> + + <?php if ($this->user->getRole() === Role::APP_ADMIN) { + $visibilityOptions['app-admin'] = t('Administrators'); + } + ?> + + <?= $this->form->select($formName, $visibilityOptions, array(), array(), $attribute) ?> + <?= $this->modal->submitButtons() ?> +</form> diff --git a/app/Template/comment_list/show.php b/app/Template/comment_list/show.php new file mode 100644 index 0000000..a39891f --- /dev/null +++ b/app/Template/comment_list/show.php @@ -0,0 +1,31 @@ +<div class="page-header"> + <h2><?= $this->text->e($task['title']) ?></h2> + <?php if (!isset($is_public) || !$is_public): ?> + <ul> + <li> + <?= $this->url->icon('sort', t('Change sorting'), 'CommentListController', 'toggleSorting', array('task_id' => $task['id']), false, 'js-modal-replace') ?> + </li> + <?php if ($editable): ?> + <li> + <?= $this->modal->medium('paper-plane', t('Send by email'), 'CommentMailController', 'create', array('task_id' => $task['id'])) ?> + </li> + <?php endif ?> + </ul> + <?php endif ?> +</div> +<div class="comments"> + <?php foreach ($comments as $comment): ?> + <?= $this->render('comment/show', array( + 'comment' => $comment, + 'task' => $task, + 'editable' => $editable, + 'is_public' => isset($is_public) && $is_public, + )) ?> + <?php endforeach ?> + + <?php if ($editable): ?> + <?= $this->render('comment_list/create', array( + 'task' => $task, + )) ?> + <?php endif ?> +</div> diff --git a/app/Template/comment_mail/create.php b/app/Template/comment_mail/create.php new file mode 100644 index 0000000..d841289 --- /dev/null +++ b/app/Template/comment_mail/create.php @@ -0,0 +1,73 @@ +<?php use Kanboard\Core\Security\Role; ?> +<div class="page-header"> + <h2><?= t('Create and send a comment by email') ?></h2> +</div> +<form method="post" action="<?= $this->url->href('CommentMailController', 'save', array('task_id' => $task['id'])) ?>" autocomplete="off" class="js-mail-form"> + <?= $this->form->csrf() ?> + <?= $this->form->hidden('task_id', $values) ?> + <?= $this->form->hidden('user_id', $values) ?> + + <?= $this->form->label(t('Email'), 'emails') ?> + <?= $this->form->text('emails', $values, $errors, array('autofocus', 'required', 'tabindex="1"')) ?> + + <?php if (! empty($members)): ?> + <div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-icon"><i class="fa fa-address-card-o"></i><i class="fa fa-caret-down"></i></a> + <ul> + <?php foreach ($members as $member): ?> + <li data-email="<?= $this->text->e($member['email']) ?>" class="js-autocomplete-email"> + <?= $this->text->e($this->user->getFullname($member)) ?> + </li> + <?php endforeach ?> + </ul> + </div> + <?php endif ?> + + <?= $this->form->label(t('Subject'), 'subject') ?> + <?= $this->form->text('subject', $values, $errors, array('required', 'tabindex="2"')) ?> + + <?php if (! empty($project['predefined_email_subjects'])): ?> + <div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-icon"><i class="fa fa-archive"></i><i class="fa fa-caret-down"></i></a> + <ul> + <?php foreach (explode("\r\n", trim($project['predefined_email_subjects'])) as $subject): ?> + <?php $subject = trim($subject); ?> + + <?php if (! empty($subject)): ?> + <li data-subject="<?= $this->text->e($subject) ?>" class="js-autocomplete-subject"> + <?= $this->text->e($subject) ?> + </li> + <?php endif ?> + <?php endforeach ?> + </ul> + </div> + <?php endif ?> + + <?= $this->form->textEditor('comment', $values, $errors, array('required' => true, 'tabindex' => 3, 'aria-label' => t('New comment'))) ?> + + <?php + $formName = 'visibility'; + $visibilityOptions['app-user'] = t('Standard users'); + $attribute[] = ('hidden'); + ?> + + <?php if ($this->user->getRole() !== Role::APP_USER) { + echo $this->form->label(t('Visibility:'), $formName); + $attribute = []; + $visibilityOptions['app-user'] = t('Standard users'); + $visibilityOptions['app-manager'] = t('Application managers or more'); + } + ?> + + <?php if ($this->user->getRole() === Role::APP_ADMIN) { + $visibilityOptions['app-admin'] = t('Administrators'); + } + ?> + + <?= $this->form->select($formName, $visibilityOptions, array(), array(), $attribute) ?> + + <?= $this->modal->submitButtons(array( + 'submitLabel' => t('Send by email'), + 'tabindex' => 4, + )) ?> +</form> diff --git a/app/Template/comment_mail/email.php b/app/Template/comment_mail/email.php new file mode 100644 index 0000000..612fbb2 --- /dev/null +++ b/app/Template/comment_mail/email.php @@ -0,0 +1,3 @@ +<?= $this->text->markdown($email['comment'], true) ?> + +<?= $this->render('notification/footer', array('task' => $task)) ?> diff --git a/app/Template/config/about.php b/app/Template/config/about.php new file mode 100644 index 0000000..4777bd8 --- /dev/null +++ b/app/Template/config/about.php @@ -0,0 +1,94 @@ +<div class="page-header"> + <h2><?= t('About') ?></h2> +</div> +<div class="panel"> + <ul> + <li> + <?= t('Official website:') ?> + <a href="https://kanboard.org/" target="_blank" rel="noopener noreferrer">https://kanboard.org/</a> + </li> + <li> + <?= t('Author:') ?> + <strong>Frédéric Guillot</strong> (<a href="https://github.com/kanboard/kanboard/graphs/contributors" target="_blank" rel="noopener noreferrer"><?= t('contributors') ?></a>) + </li> + <li> + <?= t('License:') ?> + <strong>MIT</strong> + </li> + </ul> +</div> + +<div class="page-header"> + <h2><?= t('Configuration') ?></h2> +</div> +<div class="panel"> + <ul> + <li> + <?= t('Application version:') ?> + <strong><?= APP_VERSION ?></strong> + </li> + <li> + <?= t('PHP version:') ?> + <strong><?= PHP_VERSION ?></strong> + </li> + <li> + <?= t('PHP SAPI:') ?> + <strong><?= PHP_SAPI ?></strong> + </li> + <li> + <?= t('HTTP Client:') ?> + <strong><?= Kanboard\Core\Http\Client::backend() ?></strong> + </li> + <li> + <?= t('OS version:') ?> + <strong><?= @php_uname('s').' '.@php_uname('r') ?></strong> + </li> + <li> + <?= t('Database driver:') ?> + <strong><?= DB_DRIVER ?></strong> + </li> + <li> + <?= t('Database version:') ?> + <strong><?= $this->text->e($db_version) ?></strong> + </li> + <li> + <?= t('Browser:') ?> + <strong><?= $this->text->e($user_agent) ?></strong> + </li> + </ul> +</div> + +<?php if (DB_DRIVER === 'sqlite'): ?> + <div class="page-header"> + <h2><?= t('Database') ?></h2> + </div> + <div class="panel"> + <ul> + <li> + <?= t('Database size:') ?> + <strong><?= $this->text->bytes($db_size) ?></strong> + </li> + <li> + <?= $this->url->link(t('Download the database'), 'ConfigController', 'downloadDb', array(), true) ?>  + <?= t('(Gzip compressed Sqlite file)') ?> + </li> + <li> + <?= $this->url->link(t('Upload the database'), 'ConfigController', 'uploadDb', array(), false, 'js-modal-medium') ?> + </li> + <li> + <?= $this->url->link(t('Optimize the database'), 'ConfigController', 'optimizeDb', array(), true) ?>  + <?= t('(VACUUM command)') ?> + </li> + <?php foreach ($db_options as $option => $value): ?> + <li><strong><?= $this->text->e($option) ?></strong> = <?= $this->text->e($value) ?></li> + <?php endforeach ?> + </ul> + </div> +<?php endif ?> + +<div class="page-header"> + <h2><?= t('License') ?></h2> +</div> +<div class="panel"> +<?= nl2br(file_get_contents(ROOT_DIR.DIRECTORY_SEPARATOR.'LICENSE')) ?> +</div> diff --git a/app/Template/config/api.php b/app/Template/config/api.php new file mode 100644 index 0000000..3480b57 --- /dev/null +++ b/app/Template/config/api.php @@ -0,0 +1,17 @@ +<div class="page-header"> + <h2><?= t('API') ?></h2> +</div> +<div class="panel"> + <ul> + <li> + <?= t('API token:') ?> + <strong><?= $this->text->e($values['api_token']) ?></strong> + </li> + <li> + <?= t('API endpoint:') ?> + <strong><?= $this->text->e($this->url->base()).'jsonrpc.php' ?></strong> + </li> + </ul> +</div> + +<?= $this->url->link(t('Reset token'), 'ConfigController', 'token', array('type' => 'api'), true, 'btn btn-red') ?> \ No newline at end of file diff --git a/app/Template/config/application.php b/app/Template/config/application.php new file mode 100644 index 0000000..c94a7b7 --- /dev/null +++ b/app/Template/config/application.php @@ -0,0 +1,44 @@ +<div class="page-header"> + <h2><?= t('Application settings') ?></h2> +</div> +<form method="post" action="<?= $this->url->href('ConfigController', 'save', array('redirect' => 'application')) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + + <fieldset> + <?= $this->form->label(t('Application URL'), 'application_url') ?> + <?= $this->form->text('application_url', $values, $errors, array('placeholder="https://example.kanboard.org/"')) ?> + <p class="form-help"><?= t('Example: https://example.kanboard.org/ (used to generate absolute URLs)') ?></p> + + <?= $this->form->label(t('Language'), 'application_language') ?> + <?= $this->form->select('application_language', $languages, $values, $errors) ?> + + <?= $this->form->checkbox('password_reset', t('Enable "Forget Password"'), 1, $values['password_reset'] == 1) ?> + </fieldset> + + <fieldset> + <?= $this->form->label(t('Timezone'), 'application_timezone') ?> + <?= $this->form->select('application_timezone', $timezones, $values, $errors) ?> + + <?= $this->form->label(t('Date format'), 'application_date_format') ?> + <?= $this->form->select('application_date_format', $date_formats, $values, $errors) ?> + <p class="form-help"><?= t('ISO format is always accepted, example: "%s" and "%s"', date('Y-m-d'), date('Y_m_d')) ?></p> + + <?= $this->form->label(t('Time format'), 'application_time_format') ?> + <?= $this->form->select('application_time_format', $time_formats, $values, $errors) ?> + </fieldset> + + <fieldset> + <?= $this->form->checkbox('notifications_enabled', t('Enable notifications by default for all new users'), 1, isset($values['notifications_enabled']) && $values['notifications_enabled'] == 1) ?> + </fieldset> + + <fieldset> + <?= $this->form->label(t('Custom Stylesheet'), 'application_stylesheet') ?> + <?= $this->form->textarea('application_stylesheet', $values, $errors) ?> + </fieldset> + + <?= $this->hook->render('template:config:application', array('values' => $values, 'errors' => $errors)) ?> + + <div class="form-actions"> + <button type="submit" class="btn btn-blue"><?= t('Save') ?></button> + </div> +</form> diff --git a/app/Template/config/board.php b/app/Template/config/board.php new file mode 100644 index 0000000..4d6fe2f --- /dev/null +++ b/app/Template/config/board.php @@ -0,0 +1,26 @@ +<div class="page-header"> + <h2><?= t('Board settings') ?></h2> +</div> +<form method="post" action="<?= $this->url->href('ConfigController', 'save', array('redirect' => 'board')) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + + <fieldset> + <?= $this->form->label(t('Task highlight period'), 'board_highlight_period') ?> + <?= $this->form->number('board_highlight_period', $values, $errors) ?> + <p class="form-help"><?= t('Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)') ?></p> + + <?= $this->form->label(t('Refresh interval for public board'), 'board_public_refresh_interval') ?> + <?= $this->form->number('board_public_refresh_interval', $values, $errors) ?> + <p class="form-help"><?= t('Frequency in second (60 seconds by default)') ?></p> + + <?= $this->form->label(t('Refresh interval for personal board'), 'board_private_refresh_interval') ?> + <?= $this->form->number('board_private_refresh_interval', $values, $errors) ?> + <p class="form-help"><?= t('Frequency in second (0 to disable this feature, 10 seconds by default)') ?></p> + </fieldset> + + <?= $this->hook->render('template:config:board', array('values' => $values, 'errors' => $errors)) ?> + + <div class="form-actions"> + <button type="submit" class="btn btn-blue"><?= t('Save') ?></button> + </div> +</form> diff --git a/app/Template/config/email.php b/app/Template/config/email.php new file mode 100644 index 0000000..be729f5 --- /dev/null +++ b/app/Template/config/email.php @@ -0,0 +1,25 @@ +<div class="page-header"> + <h2><?= t('Email settings') ?></h2> +</div> +<form method="post" action="<?= $this->url->href('ConfigController', 'save', array('redirect' => 'email')) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + + <fieldset> + <legend><?= t('Outgoing Emails') ?></legend> + <?php if (MAIL_CONFIGURATION): ?> + <?= $this->form->label(t('Email sender address'), 'mail_sender_address') ?> + <?= $this->form->text('mail_sender_address', $values, $errors, array('placeholder="'.MAIL_FROM.'"')) ?> + + <?= $this->form->label(t('Email transport'), 'mail_transport') ?> + <?= $this->form->select('mail_transport', $mail_transports, $values, $errors) ?> + <?php else: ?> + <p class="alert"><?= t('The email configuration has been disabled by the administrator.') ?></p> + <?php endif ?> + </fieldset> + + <?= $this->hook->render('template:config:email', array('values' => $values, 'errors' => $errors)) ?> + + <div class="form-actions"> + <button type="submit" class="btn btn-blue"><?= t('Save') ?></button> + </div> +</form> diff --git a/app/Template/config/integrations.php b/app/Template/config/integrations.php new file mode 100644 index 0000000..0a3f995 --- /dev/null +++ b/app/Template/config/integrations.php @@ -0,0 +1,14 @@ +<div class="page-header"> + <h2><?= t('Integration with third-party services') ?></h2> +</div> + +<form method="post" action="<?= $this->url->href('ConfigController', 'save', array('redirect' => 'integrations')) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + <?php $contents = $this->hook->render('template:config:integrations', array('values' => $values)) ?> + + <?php if (empty($contents)): ?> + <p class="alert"><?= t('There is no external integration installed.') ?></p> + <?php else: ?> + <?= $contents ?> + <?php endif ?> +</form> diff --git a/app/Template/config/keyboard_shortcuts.php b/app/Template/config/keyboard_shortcuts.php new file mode 100644 index 0000000..4e78dbe --- /dev/null +++ b/app/Template/config/keyboard_shortcuts.php @@ -0,0 +1,33 @@ +<div class="page-header"> + <h2><?= t('Keyboard shortcuts') ?></h2> +</div> +<div class="panel"> + <h3><?= t('Board/Calendar/List view') ?></h3> + <ul> + <li><?= t('Switch to the project overview') ?> = <strong>v o</strong></li> + <li><?= t('Switch to the board view') ?> = <strong>v b</strong></li> + <li><?= t('Switch to the list view') ?> = <strong>v l</strong></li> + </ul> + <h3><?= t('Board view') ?></h3> + <ul> + <li><?= t('New task') ?> = <strong>n</strong></li> + <li><?= t('Expand/collapse tasks') ?> = <strong>s</strong></li> + <li><?= t('Compact/wide view') ?> = <strong>c</strong></li> + </ul> + <h3><?= t('Task view') ?></h3> + <ul> + <li><?= t('Edit task') ?> = <strong>e</strong></li> + <li><?= t('New subtask') ?> = <strong>s</strong></li> + <li><?= t('New comment') ?> = <strong>c</strong></li> + <li><?= t('New internal link') ?> = <strong>l</strong></li> + </ul> + <h3><?= t('Application') ?></h3> + <ul> + <li><?= t('Display list of keyboard shortcuts') ?> = <strong>?</strong></li> + <li><?= t('Open board switcher') ?> = <strong>b</strong></li> + <li><?= t('Go to the search/filter box') ?> = <strong>f</strong></li> + <li><?= t('Reset the search/filter box') ?> = <strong>r</strong></li> + <li><?= t('Close dialog box') ?> = <strong>ESC</strong></li> + <li><?= t('Submit a form') ?> = <strong>CTRL+ENTER</strong> <?= t('or') ?> <strong>⌘+ENTER</strong></li> + </ul> +</div> diff --git a/app/Template/config/layout.php b/app/Template/config/layout.php new file mode 100644 index 0000000..6eafa59 --- /dev/null +++ b/app/Template/config/layout.php @@ -0,0 +1,9 @@ +<section id="main"> + <section class="sidebar-container" id="config-section"> + <?= $this->render($sidebar_template) ?> + + <div class="sidebar-content"> + <?= $content_for_sublayout ?> + </div> + </section> +</section> diff --git a/app/Template/config/project.php b/app/Template/config/project.php new file mode 100644 index 0000000..89fe70f --- /dev/null +++ b/app/Template/config/project.php @@ -0,0 +1,30 @@ +<div class="page-header"> + <h2><?= t('Project settings') ?></h2> +</div> +<form method="post" action="<?= $this->url->href('ConfigController', 'save', array('redirect' => 'project')) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + + <fieldset> + <?= $this->form->label(t('Default task color'), 'default_color') ?> + <?= $this->form->select('default_color', $colors, $values, $errors) ?> + + <?= $this->form->label(t('Default columns for new projects (Comma-separated)'), 'board_columns') ?> + <?= $this->form->text('board_columns', $values, $errors) ?> + <p class="form-help"><?= t('Default values are "%s"', $default_columns) ?></p> + + <?= $this->form->label(t('Default categories for new projects (Comma-separated)'), 'project_categories') ?> + <?= $this->form->text('project_categories', $values, $errors) ?> + <p class="form-help"><?= t('Example: "Bug, Feature Request, Improvement"') ?></p> + </fieldset> + + <fieldset> + <?= $this->form->checkbox('disable_private_project', t('Disable personal projects'), 1, isset($values['disable_private_project']) && $values['disable_private_project'] == 1) ?> + <?= $this->form->checkbox('subtask_restriction', t('Allow only one subtask in progress at the same time for a user'), 1, $values['subtask_restriction'] == 1) ?> + <?= $this->form->checkbox('subtask_time_tracking', t('Trigger automatically subtask time tracking'), 1, $values['subtask_time_tracking'] == 1) ?> + <?= $this->form->checkbox('cfd_include_closed_tasks', t('Include closed tasks in the cumulative flow diagram'), 1, $values['cfd_include_closed_tasks'] == 1) ?> + </fieldset> + + <div class="form-actions"> + <button type="submit" class="btn btn-blue"><?= t('Save') ?></button> + </div> +</form> diff --git a/app/Template/config/sidebar.php b/app/Template/config/sidebar.php new file mode 100644 index 0000000..2f265ed --- /dev/null +++ b/app/Template/config/sidebar.php @@ -0,0 +1,38 @@ +<div class="sidebar"> + <ul> + <li <?= $this->app->checkMenuSelection('ConfigController', 'index') ?>> + <?= $this->url->link(t('About'), 'ConfigController', 'index') ?> + </li> + <li <?= $this->app->checkMenuSelection('ConfigController', 'application') ?>> + <?= $this->url->link(t('Application settings'), 'ConfigController', 'application') ?> + </li> + <li <?= $this->app->checkMenuSelection('ConfigController', 'email') ?>> + <?= $this->url->link(t('Email settings'), 'ConfigController', 'email') ?> + </li> + <li <?= $this->app->checkMenuSelection('ConfigController', 'project') ?>> + <?= $this->url->link(t('Project settings'), 'ConfigController', 'project') ?> + </li> + <li <?= $this->app->checkMenuSelection('ConfigController', 'board') ?>> + <?= $this->url->link(t('Board settings'), 'ConfigController', 'board') ?> + </li> + <li <?= $this->app->checkMenuSelection('TagController', 'index') ?>> + <?= $this->url->link(t('Tags management'), 'TagController', 'index') ?> + </li> + <li <?= $this->app->checkMenuSelection('LinkController') ?>> + <?= $this->url->link(t('Link labels'), 'LinkController', 'show') ?> + </li> + <li <?= $this->app->checkMenuSelection('CurrencyController') ?>> + <?= $this->url->link(t('Currency rates'), 'CurrencyController', 'show') ?> + </li> + <li <?= $this->app->checkMenuSelection('ConfigController', 'integrations') ?>> + <?= $this->url->link(t('Integrations'), 'ConfigController', 'integrations') ?> + </li> + <li <?= $this->app->checkMenuSelection('ConfigController', 'webhook') ?>> + <?= $this->url->link(t('Webhooks'), 'ConfigController', 'webhook') ?> + </li> + <li <?= $this->app->checkMenuSelection('ConfigController', 'api') ?>> + <?= $this->url->link(t('API'), 'ConfigController', 'api') ?> + </li> + <?= $this->hook->render('template:config:sidebar') ?> + </ul> +</div> diff --git a/app/Template/config/upload_db.php b/app/Template/config/upload_db.php new file mode 100644 index 0000000..efc8eb2 --- /dev/null +++ b/app/Template/config/upload_db.php @@ -0,0 +1,16 @@ +<div class="page-header"> + <h2><?= t('Upload the database') ?></h2> +</div> + +<div class="alert"> + <p> + <?= t('You could upload the previously downloaded Sqlite database (Gzip format).') ?> + </p> +</div> + +<form action="<?= $this->url->href('ConfigController', 'saveUploadedDb', [], true) ?>" method="post" enctype="multipart/form-data"> + <?= $this->form->label(t('Database file'), 'file') ?> + <?= $this->form->file('file') ?> + + <?= $this->modal->submitButtons(array('submitLabel' => t('Upload'))) ?> +</form> diff --git a/app/Template/config/webhook.php b/app/Template/config/webhook.php new file mode 100644 index 0000000..bc4bbfd --- /dev/null +++ b/app/Template/config/webhook.php @@ -0,0 +1,23 @@ +<div class="page-header"> + <h2><?= t('Webhook settings') ?></h2> +</div> +<form method="post" action="<?= $this->url->href('ConfigController', 'save', array('redirect' => 'webhook')) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + + <?= $this->form->label(t('Webhook URL'), 'webhook_url') ?> + <?= $this->form->text('webhook_url', $values, $errors) ?> + + <div class="form-actions"> + <button type="submit" class="btn btn-blue"><?= t('Save') ?></button> + </div> +</form> + +<div class="page-header margin-top"> + <h2><?= t('Webhook token') ?></h2> +</div> +<div class="panel"> + <?= t('Webhook token:') ?> + <strong><?= $this->text->e($values['webhook_token']) ?></strong> +</div> + +<?= $this->url->link(t('Reset token'), 'ConfigController', 'token', array('type' => 'webhook'), true, 'btn btn-red') ?> diff --git a/app/Template/currency/change.php b/app/Template/currency/change.php new file mode 100644 index 0000000..59a7ce3 --- /dev/null +++ b/app/Template/currency/change.php @@ -0,0 +1,9 @@ +<div class="page-header"> + <h2><?= t('Change reference currency') ?></h2> +</div> +<form method="post" action="<?= $this->url->href('CurrencyController', 'update') ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + <?= $this->form->label(t('Reference currency'), 'application_currency') ?> + <?= $this->form->select('application_currency', $currencies, $values, $errors) ?> + <?= $this->modal->submitButtons() ?> +</form> diff --git a/app/Template/currency/create.php b/app/Template/currency/create.php new file mode 100644 index 0000000..578ece8 --- /dev/null +++ b/app/Template/currency/create.php @@ -0,0 +1,11 @@ +<div class="page-header"> + <h2><?= t('Add or change currency rate') ?></h2> +</div> +<form method="post" action="<?= $this->url->href('CurrencyController', 'save') ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + <?= $this->form->label(t('Currency'), 'currency') ?> + <?= $this->form->select('currency', $currencies, $values, $errors) ?> + <?= $this->form->label(t('Rate'), 'rate') ?> + <?= $this->form->text('rate', $values, $errors, array('autofocus'), 'form-numeric') ?> + <?= $this->modal->submitButtons() ?> +</form> diff --git a/app/Template/currency/show.php b/app/Template/currency/show.php new file mode 100644 index 0000000..4b7f34b --- /dev/null +++ b/app/Template/currency/show.php @@ -0,0 +1,34 @@ +<div class="page-header"> + <h2><?= t('Currency rates') ?></h2> + <ul> + <li> + <?= $this->modal->medium('plus', t('Add or change currency rate'), 'CurrencyController', 'create') ?> + </li> + <li> + <?= $this->modal->medium('edit', t('Change reference currency'), 'CurrencyController', 'change') ?> + </li> + </ul> +</div> + +<div class="panel"> + <strong><?= t('Reference currency: %s', $application_currency) ?></strong> +</div> + +<?php if (! empty($rates)): ?> + <table class="table-striped"> + <tr> + <th class="column-35"><?= t('Currency') ?></th> + <th><?= t('Rate') ?></th> + </tr> + <?php foreach ($rates as $rate): ?> + <tr> + <td> + <strong><?= $this->text->e($rate['currency']) ?></strong> + </td> + <td> + <?= n($rate['rate']) ?> + </td> + </tr> + <?php endforeach ?> + </table> +<?php endif ?> diff --git a/app/Template/custom_filter/create.php b/app/Template/custom_filter/create.php new file mode 100644 index 0000000..df400ad --- /dev/null +++ b/app/Template/custom_filter/create.php @@ -0,0 +1,20 @@ +<div class="page-header"> + <h2><?= t('Add a new filter') ?></h2> +</div> +<form method="post" action="<?= $this->url->href('CustomFilterController', 'save', array('project_id' => $project['id'])) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + + <?= $this->form->label(t('Name'), 'name') ?> + <?= $this->form->text('name', $values, $errors, array('autofocus', 'required')) ?> + + <?= $this->form->label(t('Filter'), 'filter') ?> + <?= $this->form->text('filter', $values, $errors, array('required')) ?> + + <?php if ($this->user->hasProjectAccess('ProjectEditController', 'show', $project['id'])): ?> + <?= $this->form->checkbox('is_shared', t('Share with all project members'), 1) ?> + <?php endif ?> + + <?= $this->form->checkbox('append', t('Append filter (instead of replacement)'), 1) ?> + + <?= $this->modal->submitButtons() ?> +</form> diff --git a/app/Template/custom_filter/edit.php b/app/Template/custom_filter/edit.php new file mode 100644 index 0000000..4b0a25a --- /dev/null +++ b/app/Template/custom_filter/edit.php @@ -0,0 +1,25 @@ +<div class="page-header"> + <h2><?= t('Edit custom filter') ?></h2> +</div> + +<form method="post" action="<?= $this->url->href('CustomFilterController', 'update', array('project_id' => $filter['project_id'], 'filter_id' => $filter['id'])) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + + <?= $this->form->hidden('user_id', $values) ?> + + <?= $this->form->label(t('Name'), 'name') ?> + <?= $this->form->text('name', $values, $errors, array('autofocus', 'required')) ?> + + <?= $this->form->label(t('Filter'), 'filter') ?> + <?= $this->form->text('filter', $values, $errors, array('required')) ?> + + <?php if ($this->user->hasProjectAccess('ProjectEditController', 'show', $project['id'])): ?> + <?= $this->form->checkbox('is_shared', t('Share with all project members'), 1, $values['is_shared'] == 1) ?> + <?php else: ?> + <?= $this->form->hidden('is_shared', $values) ?> + <?php endif ?> + + <?= $this->form->checkbox('append', t('Append filter (instead of replacement)'), 1, $values['append'] == 1) ?> + + <?= $this->modal->submitButtons() ?> +</form> diff --git a/app/Template/custom_filter/index.php b/app/Template/custom_filter/index.php new file mode 100644 index 0000000..597a619 --- /dev/null +++ b/app/Template/custom_filter/index.php @@ -0,0 +1,57 @@ +<div class="page-header"> + <h2><?= t('Custom filters') ?></h2> + <ul> + <li> + <?= $this->modal->medium('filter', t('Add custom filters'), 'CustomFilterController', 'create', array('project_id' => $project['id'])) ?> + </li> + </ul> +</div> +<?php if (! empty($custom_filters)): ?> + <table class="table-striped table-scrolling"> + <tr> + <th><?= t('Name') ?></th> + <th class="column-30"><?= t('Filter') ?></th> + <th class="column-10"><?= t('Shared') ?></th> + <th class="column-15"><?= t('Append/Replace') ?></th> + <th class="column-20"><?= t('Owner') ?></th> + </tr> + <?php foreach ($custom_filters as $filter): ?> + <tr> + <td> + <?php if (($filter['user_id'] == $this->user->getId() || $this->user->isAdmin() || $this->projectRole->getProjectUserRole($project['id']) == \Kanboard\Core\Security\Role::PROJECT_MANAGER) && $this->user->hasProjectAccess('CustomFilterController', 'edit', $project['id'])): ?> + <div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-icon"><i class="fa fa-cog"></i><i class="fa fa-caret-down"></i></a> + <ul> + <li><?= $this->modal->medium('edit', t('Edit'), 'CustomFilterController', 'edit', array('project_id' => $filter['project_id'], 'filter_id' => $filter['id'])) ?></li> + <li><?= $this->modal->confirm('trash-o', t('Remove'), 'CustomFilterController', 'confirm', array('project_id' => $filter['project_id'], 'filter_id' => $filter['id'])) ?></li> + </ul> + </div> + <?php endif ?> + <?= $this->text->e($filter['name']) ?> + </td> + <td> + <?= $this->text->e($filter['filter']) ?> + </td> + <td> + <?php if ($filter['is_shared'] == 1): ?> + <?= t('Yes') ?> + <?php else: ?> + <?= t('No') ?> + <?php endif ?> + </td> + <td> + <?php if ($filter['append'] == 1): ?> + <?= t('Append') ?> + <?php else: ?> + <?= t('Replace') ?> + <?php endif ?> + </td> + <td> + <?= $this->text->e($filter['owner_name'] ?: $filter['owner_username']) ?> + </td> + </tr> + <?php endforeach ?> + </table> +<?php else: ?> + <p class="alert"><?= t('There is no custom filter.') ?></p> +<?php endif ?> \ No newline at end of file diff --git a/app/Template/custom_filter/remove.php b/app/Template/custom_filter/remove.php new file mode 100644 index 0000000..1c576fa --- /dev/null +++ b/app/Template/custom_filter/remove.php @@ -0,0 +1,15 @@ +<div class="page-header"> + <h2><?= t('Remove a custom filter') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"> + <?= t('Do you really want to remove this custom filter: "%s"?', $filter['name']) ?> + </p> + + <?= $this->modal->confirmButtons( + 'CustomFilterController', + 'remove', + array('project_id' => $project['id'], 'filter_id' => $filter['id']) + ) ?> +</div> diff --git a/app/Template/dashboard/layout.php b/app/Template/dashboard/layout.php new file mode 100644 index 0000000..1e7d735 --- /dev/null +++ b/app/Template/dashboard/layout.php @@ -0,0 +1,9 @@ +<section id="main"> + + <section class="sidebar-container" id="dashboard"> + <?= $this->render($sidebar_template, array('user' => $user)) ?> + <div class="sidebar-content"> + <?= $content_for_sublayout ?> + </div> + </section> +</section> diff --git a/app/Template/dashboard/overview.php b/app/Template/dashboard/overview.php new file mode 100644 index 0000000..a2287e5 --- /dev/null +++ b/app/Template/dashboard/overview.php @@ -0,0 +1,105 @@ +<?= $this->hook->render('template:dashboard:show:before-filter-box', array('user' => $user)) ?> + +<div class="filter-box margin-bottom"> + <form method="get" action="<?= $this->url->dir() ?>" class="search"> + <?= $this->form->hidden('controller', array('controller' => 'SearchController')) ?> + <?= $this->form->hidden('action', array('action' => 'index')) ?> + + <div class="input-addon"> + <?= $this->form->text('search', array(), array(), array('placeholder="'.t('Search').'"', 'aria-label="'.t('Search').'"'), 'input-addon-field') ?> + <div class="input-addon-item"> + <?= $this->render('app/filters_helper') ?> + </div> + </div> + </form> +</div> + +<?= $this->hook->render('template:dashboard:show:after-filter-box', array('user' => $user)) ?> + +<?php if (! $project_paginator->isEmpty()): ?> + <div class="table-list"> + <?= $this->render('project_list/header', array('paginator' => $project_paginator)) ?> + <?php foreach ($project_paginator->getCollection() as $project): ?> + <div class="table-list-row table-border-left"> + <div> + <?php if ($this->user->hasProjectAccess('ProjectViewController', 'show', $project['id'])): ?> + <?= $this->render('project/dropdown', array('project' => $project)) ?> + <?php else: ?> + <strong><?= '#'.$project['id'] ?></strong> + <?php endif ?> + + <?= $this->hook->render('template:dashboard:project:before-title', array('project' => $project)) ?> + + <span class="table-list-title <?= $project['is_active'] == 0 ? 'status-closed' : '' ?>"> + <?= $this->url->link($this->text->e($project['name']), 'BoardViewController', 'show', array('project_id' => $project['id'])) ?> + </span> + + <?php if ($project['is_private']): ?> + <i class="fa fa-lock fa-fw" title="<?= t('Personal project') ?>" role="img" aria-label="<?= t('Personal project') ?>"></i> + <?php endif ?> + + <?= $this->hook->render('template:dashboard:project:after-title', array('project' => $project)) ?> + + </div> + <div class="table-list-details"> + <?php foreach ($project['columns'] as $column): ?> + <strong title="<?= t('Task count') ?>"><span class="ui-helper-hidden-accessible"><?= t('Task count') ?> </span><?= $column['nb_open_tasks'] ?></strong> + <small><?= $this->text->e($column['title']) ?></small> + <?php endforeach ?> + </div> + </div> + <?php endforeach ?> + </div> + + <?= $project_paginator ?> +<?php endif ?> + +<?php if (empty($overview_paginator)): ?> + <p class="alert"><?= t('There is nothing assigned to you.') ?></p> +<?php else: ?> + <?php foreach ($overview_paginator as $result): ?> + <?php if (! $result['paginator']->isEmpty()): ?> + <div class="page-header"> + <h2 id="project-tasks-<?= $result['project_id'] ?>"><?= $this->url->link($this->text->e($result['project_name']), 'BoardViewController', 'show', array('project_id' => $result['project_id'])) ?></h2> + </div> + + <div class="table-list"> + <?= $this->render('task_list/header', array( + 'paginator' => $result['paginator'], + )) ?> + + <?php foreach ($result['paginator']->getCollection() as $task): ?> + <div class="table-list-row color-<?= $task['color_id'] ?>"> + <?= $this->render('task_list/task_title', array( + 'task' => $task, + 'redirect' => 'dashboard', + )) ?> + + <?= $this->render('task_list/task_details', array( + 'task' => $task, + )) ?> + + <?= $this->render('task_list/task_avatars', array( + 'task' => $task, + )) ?> + + <?= $this->render('task_list/task_icons', array( + 'task' => $task, + )) ?> + + <?= $this->render('task_list/task_subtasks', array( + 'task' => $task, + 'user_id' => $user['id'], + )) ?> + + <?= $this->hook->render('template:dashboard:task:footer', array('task' => $task)) ?> + </div> + <?php endforeach ?> + </div> + + <?= $result['paginator'] ?> + <?php endif ?> + <?php endforeach ?> +<?php endif ?> + +<?= $this->hook->render('template:dashboard:show', array('user' => $user)) ?> diff --git a/app/Template/dashboard/projects.php b/app/Template/dashboard/projects.php new file mode 100644 index 0000000..b7c1fdd --- /dev/null +++ b/app/Template/dashboard/projects.php @@ -0,0 +1,27 @@ +<div class="page-header"> + <h2><?= $this->url->link(t('My projects'), 'DashboardController', 'projects', array('user_id' => $user['id'])) ?> (<?= $paginator->getTotal() ?>)</h2> +</div> +<?php if ($paginator->isEmpty()): ?> + <p class="alert"><?= t('You are not a member of any project.') ?></p> +<?php else: ?> + <div class="table-list"> + <?= $this->render('project_list/header', array('paginator' => $paginator)) ?> + <?php foreach ($paginator->getCollection() as $project): ?> + <div class="table-list-row table-border-left"> + <?= $this->render('project_list/project_title', array( + 'project' => $project, + )) ?> + + <?= $this->render('project_list/project_details', array( + 'project' => $project, + )) ?> + + <?= $this->render('project_list/project_icons', array( + 'project' => $project, + )) ?> + </div> + <?php endforeach ?> + </div> + + <?= $paginator ?> +<?php endif ?> diff --git a/app/Template/dashboard/sidebar.php b/app/Template/dashboard/sidebar.php new file mode 100644 index 0000000..65e5911 --- /dev/null +++ b/app/Template/dashboard/sidebar.php @@ -0,0 +1,24 @@ +<div class="sidebar"> + <ul> + <li <?= $this->app->checkMenuSelection('ProjectListController', 'show') ?>> + <?= $this->url->icon('folder', '<span class="sidebar-text">'.t('Project management').'</span>', 'ProjectListController', 'show') ?> + </li> + <li <?= $this->app->checkMenuSelection('ActivityController', 'user') ?>> + <?= $this->url->icon('dashboard', '<span class="sidebar-text">'.t('My activity stream').'</span>', 'ActivityController', 'user') ?> + </li> + <li><hr></li> + <li <?= $this->app->checkMenuSelection('DashboardController', 'show') ?>> + <?= $this->url->link('<i class="fa fa-fw"></i> <span class="sidebar-text">'.t('Overview').'</span>', 'DashboardController', 'show', array('user_id' => $user['id'])) ?> + </li> + <li <?= $this->app->checkMenuSelection('DashboardController', 'projects') ?>> + <?= $this->url->link('<i class="fa fa-fw"></i> <span class="sidebar-text">'.t('My projects').'</span>', 'DashboardController', 'projects', array('user_id' => $user['id'])) ?> + </li> + <li <?= $this->app->checkMenuSelection('DashboardController', 'tasks') ?>> + <?= $this->url->link('<i class="fa fa-fw"></i> <span class="sidebar-text">'.t('My tasks').'</span>', 'DashboardController', 'tasks', array('user_id' => $user['id'])) ?> + </li> + <li <?= $this->app->checkMenuSelection('DashboardController', 'subtasks') ?>> + <?= $this->url->link('<i class="fa fa-fw"></i> <span class="sidebar-text">'.t('My subtasks').'</span>', 'DashboardController', 'subtasks', array('user_id' => $user['id'])) ?> + </li> + <?= $this->hook->render('template:dashboard:sidebar', array('user' => $user)) ?> + </ul> +</div> diff --git a/app/Template/dashboard/subtasks.php b/app/Template/dashboard/subtasks.php new file mode 100644 index 0000000..96bb13c --- /dev/null +++ b/app/Template/dashboard/subtasks.php @@ -0,0 +1,49 @@ +<div class="page-header"> + <h2><?= $this->url->link(t('My subtasks'), 'DashboardController', 'subtasks', array('user_id' => $user['id'])) ?> (<?= $nb_subtasks ?>)</h2> +</div> +<?php if ($nb_subtasks == 0): ?> + <p class="alert"><?= t('There is nothing assigned to you.') ?></p> +<?php else: ?> + <div class="table-list"> + <div class="table-list-header"> + <div class="table-list-header-count"> + <?php if ($nb_subtasks > 1): ?> + <?= t('%d subtasks', $nb_subtasks) ?> + <?php else: ?> + <?= t('%d subtask', $nb_subtasks) ?> + <?php endif ?> + </div> + <div class="table-list-header-menu"> + <div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-icon"><strong><?= t('Sort') ?> <i class="fa fa-caret-down"></i></strong></a> + <ul> + <li> + <?= $paginator->order(t('Task ID'), \Kanboard\Model\TaskModel::TABLE.'.id') ?> + </li> + <li> + <?= $paginator->order(t('Title'), \Kanboard\Model\TaskModel::TABLE.'.title') ?> + </li> + <li> + <?= $paginator->order(t('Priority'), \Kanboard\Model\TaskModel::TABLE.'.priority') ?> + </li> + </ul> + </div> + </div> + </div> + + <?php foreach ($paginator->getCollection() as $task): ?> + <div class="table-list-row color-<?= $task['color_id'] ?>"> + <?= $this->render('task_list/task_title', array( + 'task' => $task, + )) ?> + + <?= $this->render('task_list/task_subtasks', array( + 'task' => $task, + 'user_id' => $user['id'], + )) ?> + </div> + <?php endforeach ?> + </div> + + <?= $paginator ?> +<?php endif ?> diff --git a/app/Template/dashboard/tasks.php b/app/Template/dashboard/tasks.php new file mode 100644 index 0000000..4392e40 --- /dev/null +++ b/app/Template/dashboard/tasks.php @@ -0,0 +1,41 @@ +<div class="page-header"> + <h2><?= $this->url->link(t('My tasks'), 'DashboardController', 'tasks', array('user_id' => $user['id'])) ?> (<?= $paginator->getTotal() ?>)</h2> +</div> +<?php if ($paginator->isEmpty()): ?> + <p class="alert"><?= t('There is nothing assigned to you.') ?></p> +<?php else: ?> + <div class="table-list"> + <?= $this->render('task_list/header', array( + 'paginator' => $paginator, + )) ?> + + <?php foreach ($paginator->getCollection() as $task): ?> + <div class="table-list-row color-<?= $task['color_id'] ?>"> + <?= $this->render('task_list/task_title', array( + 'task' => $task, + 'redirect' => 'dashboard-tasks', + )) ?> + + <?= $this->render('task_list/task_details', array( + 'task' => $task, + )) ?> + + <?= $this->render('task_list/task_avatars', array( + 'task' => $task, + )) ?> + + <?= $this->render('task_list/task_icons', array( + 'task' => $task, + )) ?> + + <?= $this->render('task_list/task_subtasks', array( + 'task' => $task, + )) ?> + + <?= $this->hook->render('template:dashboard:task:footer', array('task' => $task)) ?> + </div> + <?php endforeach ?> + </div> + + <?= $paginator ?> +<?php endif ?> diff --git a/app/Template/event/comment_create.php b/app/Template/event/comment_create.php new file mode 100644 index 0000000..de3f226 --- /dev/null +++ b/app/Template/event/comment_create.php @@ -0,0 +1,11 @@ +<p class="activity-title"> + <?= e('%s commented the task %s', + $this->text->e($author), + $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'])) + ) ?> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> +</p> +<div class="activity-description"> + <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> + <div class="markdown"><?= $this->text->markdown($comment['comment']) ?></div> +</div> diff --git a/app/Template/event/comment_delete.php b/app/Template/event/comment_delete.php new file mode 100644 index 0000000..13ef37d --- /dev/null +++ b/app/Template/event/comment_delete.php @@ -0,0 +1,11 @@ +<p class="activity-title"> + <?= e('%s removed a comment on the task %s', + $this->text->e($author), + $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'])) + ) ?> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> +</p> +<div class="activity-description"> + <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> + <div class="markdown"><?= $this->text->markdown($comment['comment']) ?></div> +</div> diff --git a/app/Template/event/comment_update.php b/app/Template/event/comment_update.php new file mode 100644 index 0000000..0d911fd --- /dev/null +++ b/app/Template/event/comment_update.php @@ -0,0 +1,13 @@ +<p class="activity-title"> + <?= e('%s updated a comment on the task %s', + $this->text->e($author), + $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'])) + ) ?> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> +</p> +<div class="activity-description"> + <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> + <?php if (! empty($comment['comment'])): ?> + <div class="markdown"><?= $this->text->markdown($comment['comment']) ?></div> + <?php endif ?> +</div> diff --git a/app/Template/event/events.php b/app/Template/event/events.php new file mode 100644 index 0000000..c050f43 --- /dev/null +++ b/app/Template/event/events.php @@ -0,0 +1,19 @@ +<?php if (empty($events)): ?> + <p class="alert"><?= t('There is no activity yet.') ?></p> +<?php else: ?> + <?php foreach ($events as $event): ?> + <div class="activity-event"> + <?= $this->avatar->render( + $event['creator_id'], + $event['author_username'], + $event['author_name'], + $event['email'], + $event['avatar_path'] + ) ?> + + <div class="activity-content"> + <?= $event['event_content'] ?> + </div> + </div> + <?php endforeach ?> +<?php endif ?> \ No newline at end of file diff --git a/app/Template/event/subtask_create.php b/app/Template/event/subtask_create.php new file mode 100644 index 0000000..53f1b3e --- /dev/null +++ b/app/Template/event/subtask_create.php @@ -0,0 +1,23 @@ +<p class="activity-title"> + <?= e('%s created a subtask for the task %s', + $this->text->e($author), + $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'])) + ) ?> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> +</p> +<div class="activity-description"> + <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> + + <ul> + <li> + <?= $this->text->e($subtask['title']) ?> (<strong><?= t($subtask['status_name']) ?></strong>) + </li> + <li> + <?php if ($subtask['username']): ?> + <?= t('Assigned to %s with an estimate of %s/%sh', $subtask['name'] ?: $subtask['username'], $subtask['time_spent'], $subtask['time_estimated']) ?> + <?php else: ?> + <?= t('Not assigned, estimate of %sh', $subtask['time_estimated']) ?> + <?php endif ?> + </li> + </ul> +</div> diff --git a/app/Template/event/subtask_delete.php b/app/Template/event/subtask_delete.php new file mode 100644 index 0000000..b1e487f --- /dev/null +++ b/app/Template/event/subtask_delete.php @@ -0,0 +1,15 @@ +<p class="activity-title"> + <?= e('%s removed a subtask for the task %s', + $this->text->e($author), + $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'])) + ) ?> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> +</p> +<div class="activity-description"> + <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> + <ul> + <li> + <?= $this->text->e($subtask['title']) ?> (<strong><?= $this->text->e($subtask['status_name']) ?></strong>) + </li> + </ul> +</div> diff --git a/app/Template/event/subtask_update.php b/app/Template/event/subtask_update.php new file mode 100644 index 0000000..db3e017 --- /dev/null +++ b/app/Template/event/subtask_update.php @@ -0,0 +1,23 @@ +<p class="activity-title"> + <?= e('%s updated a subtask for the task %s', + $this->text->e($author), + $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'])) + ) ?> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> +</p> +<div class="activity-description"> + <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> + + <ul> + <li> + <?= $this->text->e($subtask['title']) ?> (<strong><?= t($subtask['status_name']) ?></strong>) + </li> + <li> + <?php if ($subtask['username']): ?> + <?= t('Assigned to %s with an estimate of %s/%sh', $subtask['name'] ?: $subtask['username'], $subtask['time_spent'], $subtask['time_estimated']) ?> + <?php else: ?> + <?= t('Not assigned, estimate of %sh', $subtask['time_estimated']) ?> + <?php endif ?> + </li> + </ul> +</div> diff --git a/app/Template/event/task_assignee_change.php b/app/Template/event/task_assignee_change.php new file mode 100644 index 0000000..f57ab63 --- /dev/null +++ b/app/Template/event/task_assignee_change.php @@ -0,0 +1,17 @@ +<p class="activity-title"> + <?php $assignee = $task['assignee_name'] ?: $task['assignee_username'] ?> + + <?php if (! empty($assignee)): ?> + <?= e('%s changed the assignee of the task %s to %s', + $this->text->e($author), + $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'])), + $this->text->e($assignee) + ) ?> + <?php else: ?> + <?= e('%s removed the assignee of the task %s', $this->text->e($author), $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id']))) ?> + <?php endif ?> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> +</p> +<div class="activity-description"> + <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> +</div> diff --git a/app/Template/event/task_close.php b/app/Template/event/task_close.php new file mode 100644 index 0000000..aefd3a7 --- /dev/null +++ b/app/Template/event/task_close.php @@ -0,0 +1,10 @@ +<p class="activity-title"> + <?= e('%s closed the task %s', + $this->text->e($author), + $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'])) + ) ?> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> +</p> +<div class="activity-description"> + <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> +</div> diff --git a/app/Template/event/task_create.php b/app/Template/event/task_create.php new file mode 100644 index 0000000..89865cc --- /dev/null +++ b/app/Template/event/task_create.php @@ -0,0 +1,10 @@ +<p class="activity-title"> + <?= e('%s created the task %s', + $this->text->e($author), + $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'])) + ) ?> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> +</p> +<div class="activity-description"> + <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> +</div> diff --git a/app/Template/event/task_file_create.php b/app/Template/event/task_file_create.php new file mode 100644 index 0000000..b197d3c --- /dev/null +++ b/app/Template/event/task_file_create.php @@ -0,0 +1,10 @@ +<p class="activity-title"> + <?= e('%s attached a new file to the task %s', + $this->text->e($author), + $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'])) + ) ?> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> +</p> +<div class="activity-description"> + <p class="activity-task-title"><?= $this->text->e($file['name']) ?></p> +</div> diff --git a/app/Template/event/task_file_destroy.php b/app/Template/event/task_file_destroy.php new file mode 100644 index 0000000..882c51b --- /dev/null +++ b/app/Template/event/task_file_destroy.php @@ -0,0 +1,10 @@ +<p class="activity-title"> + <?= e('%s removed a file from the task %s', + $this->text->e($author), + $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'])) + ) ?> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> +</p> +<div class="activity-description"> + <p class="activity-task-title"><?= $this->text->e($file['name']) ?></p> +</div> diff --git a/app/Template/event/task_internal_link_create_update.php b/app/Template/event/task_internal_link_create_update.php new file mode 100644 index 0000000..3031033 --- /dev/null +++ b/app/Template/event/task_internal_link_create_update.php @@ -0,0 +1,14 @@ +<p class="activity-title"> + <?= e('%s set a new internal link for the task %s', + $this->text->e($author), + $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'])) + ) ?> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> +</p> +<div class="activity-description"> + <p class="activity-task-title"> + <?= e('This task is now linked to the task %s with the relation "%s"', + $this->url->link(t('#%d', $task_link['opposite_task_id']), 'TaskViewController', 'show', array('task_id' => $task_link['opposite_task_id'])), + $this->text->e($task_link['label'])) ?> + </p> +</div> diff --git a/app/Template/event/task_internal_link_delete.php b/app/Template/event/task_internal_link_delete.php new file mode 100644 index 0000000..54194ca --- /dev/null +++ b/app/Template/event/task_internal_link_delete.php @@ -0,0 +1,14 @@ +<p class="activity-title"> + <?= e('%s removed an internal link for the task %s', + $this->text->e($author), + $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'])) + ) ?> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> +</p> +<div class="activity-description"> + <p class="activity-task-title"> + <?= e('The link with the relation "%s" to the task %s has been removed', + $this->text->e($task_link['label']), + $this->url->link(t('#%d', $task_link['opposite_task_id']), 'TaskViewController', 'show', array('task_id' => $task_link['opposite_task_id']))) ?> + </p> +</div> diff --git a/app/Template/event/task_move_column.php b/app/Template/event/task_move_column.php new file mode 100644 index 0000000..23f0af7 --- /dev/null +++ b/app/Template/event/task_move_column.php @@ -0,0 +1,11 @@ +<p class="activity-title"> + <?= e('%s moved the task %s to the column "%s"', + $this->text->e($author), + $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'])), + $this->text->e($task['column_title']) + ) ?> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> +</p> +<div class="activity-description"> + <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> +</div> diff --git a/app/Template/event/task_move_position.php b/app/Template/event/task_move_position.php new file mode 100644 index 0000000..5dedbf6 --- /dev/null +++ b/app/Template/event/task_move_position.php @@ -0,0 +1,12 @@ +<p class="activity-title"> + <?= e('%s moved the task %s to the position #%d in the column "%s"', + $this->text->e($author), + $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'])), + $task['position'], + $this->text->e($task['column_title']) + ) ?> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> +</p> +<div class="activity-description"> + <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> +</div> diff --git a/app/Template/event/task_move_project.php b/app/Template/event/task_move_project.php new file mode 100644 index 0000000..d0a913b --- /dev/null +++ b/app/Template/event/task_move_project.php @@ -0,0 +1,12 @@ +<p class="activity-title"> + <?= e('%s moved the task #%d "%s" to the project "%s"', + $this->text->e($author), + $task['id'], + $this->text->e($task['title']), + $this->text->e($task['project_name']) + ) ?> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> +</p> +<div class="activity-description"> + <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> +</div> diff --git a/app/Template/event/task_move_swimlane.php b/app/Template/event/task_move_swimlane.php new file mode 100644 index 0000000..2190cf3 --- /dev/null +++ b/app/Template/event/task_move_swimlane.php @@ -0,0 +1,18 @@ +<p class="activity-title"> + <?php if ($task['swimlane_id'] == 0): ?> + <?= e('%s moved the task %s to the first swimlane', + $this->text->e($author), + $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'])) + ) ?> + <?php else: ?> + <?= e('%s moved the task %s to the swimlane "%s"', + $this->text->e($author), + $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'])), + $this->text->e($task['swimlane_name']) + ) ?> + <?php endif ?> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> +</p> +<div class="activity-description"> + <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> +</div> diff --git a/app/Template/event/task_open.php b/app/Template/event/task_open.php new file mode 100644 index 0000000..eee795d --- /dev/null +++ b/app/Template/event/task_open.php @@ -0,0 +1,10 @@ +<p class="activity-title"> + <?= e('%s opened the task %s', + $this->text->e($author), + $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'])) + ) ?> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> +</p> +<div class="activity-description"> + <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> +</div> diff --git a/app/Template/event/task_update.php b/app/Template/event/task_update.php new file mode 100644 index 0000000..88c9097 --- /dev/null +++ b/app/Template/event/task_update.php @@ -0,0 +1,15 @@ +<p class="activity-title"> + <?= e('%s updated the task %s', + $this->text->e($author), + $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'])) + ) ?> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> +</p> +<div class="activity-description"> + <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> + <?php if (isset($changes)): ?> + <div class="activity-changes"> + <?= $this->render('task/changes', array('changes' => $changes, 'task' => $task)) ?> + </div> + <?php endif ?> +</div> diff --git a/app/Template/export/header.php b/app/Template/export/header.php new file mode 100644 index 0000000..d9c6284 --- /dev/null +++ b/app/Template/export/header.php @@ -0,0 +1,18 @@ +<div class="page-header"> + <h2><?= $this->text->e($project['name']) ?> > <?= $title ?></h2> + <ul> + <li <?= $this->app->checkMenuSelection('ExportController', 'tasks') ?>> + <?= $this->modal->replaceLink(t('Tasks'), 'ExportController', 'tasks', array('project_id' => $project['id'])) ?> + </li> + <li <?= $this->app->checkMenuSelection('ExportController', 'subtasks') ?>> + <?= $this->modal->replaceLink(t('Subtasks'), 'ExportController', 'subtasks', array('project_id' => $project['id'])) ?> + </li> + <li <?= $this->app->checkMenuSelection('ExportController', 'transitions') ?>> + <?= $this->modal->replaceLink(t('Task transitions'), 'ExportController', 'transitions', array('project_id' => $project['id'])) ?> + </li> + <li <?= $this->app->checkMenuSelection('ExportController', 'summary') ?>> + <?= $this->modal->replaceLink(t('Daily project summary'), 'ExportController', 'summary', array('project_id' => $project['id'])) ?> + </li> + <?= $this->hook->render('template:export:header', array('project_id' => $project['id'])) ?> + </ul> +</div> diff --git a/app/Template/export/subtasks.php b/app/Template/export/subtasks.php new file mode 100644 index 0000000..ea80af2 --- /dev/null +++ b/app/Template/export/subtasks.php @@ -0,0 +1,17 @@ +<?= $this->render('export/header', array('project' => $project, 'title' => $title)) ?> + +<p class="alert alert-info"><?= t('This report contains all subtasks information for the given date range.') ?></p> + +<form class="js-modal-ignore-form" method="post" action="<?= $this->url->href('ExportController', 'subtasks', array('project_id' => $project['id'])) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + <?= $this->form->hidden('project_id', $values) ?> + <?= $this->form->date(t('Start date'), 'from', $values) ?> + <?= $this->form->date(t('End date'), 'to', $values) ?> + <?= $this->form->checkbox('bom', t('Add a BOM at the beginning of the file (required for Microsoft Excel)'), 1, isset($values['bom']) && $values['bom'] == 1) ?> + + <div class="form-actions"> + <button type="submit" class="btn btn-blue js-form-export"><?= t('Export') ?></button> + <?= t('or') ?> + <?= $this->url->link(t('cancel'), 'ExportController', 'subtasks', array('project_id' => $project['id']), false, 'js-modal-close') ?> + </div> +</form> diff --git a/app/Template/export/summary.php b/app/Template/export/summary.php new file mode 100644 index 0000000..7273100 --- /dev/null +++ b/app/Template/export/summary.php @@ -0,0 +1,17 @@ +<?= $this->render('export/header', array('project' => $project, 'title' => $title)) ?> + +<p class="alert alert-info"><?= t('This export contains the number of tasks per column grouped per day.') ?></p> + +<form class="js-modal-ignore-form" method="post" action="<?= $this->url->href('ExportController', 'summary', array('project_id' => $project['id'])) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + <?= $this->form->hidden('project_id', $values) ?> + <?= $this->form->date(t('Start date'), 'from', $values) ?> + <?= $this->form->date(t('End date'), 'to', $values) ?> + <?= $this->form->checkbox('bom', t('Add a BOM at the beginning of the file (required for Microsoft Excel)'), 1, isset($values['bom']) && $values['bom'] == 1) ?> + + <div class="form-actions"> + <button type="submit" class="btn btn-blue js-form-export"><?= t('Export') ?></button> + <?= t('or') ?> + <?= $this->url->link(t('cancel'), 'ExportController', 'summary', array('project_id' => $project['id']), false, 'js-modal-close') ?> + </div> +</form> diff --git a/app/Template/export/tasks.php b/app/Template/export/tasks.php new file mode 100644 index 0000000..36926e2 --- /dev/null +++ b/app/Template/export/tasks.php @@ -0,0 +1,17 @@ +<?= $this->render('export/header', array('project' => $project, 'title' => $title)) ?> + +<p class="alert alert-info"><?= t('This report contains all tasks information for the given date range.') ?></p> + +<form class="js-modal-ignore-form" method="post" action="<?= $this->url->href('ExportController', 'tasks', array('project_id' => $project['id'])) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + <?= $this->form->hidden('project_id', $values) ?> + <?= $this->form->date(t('Start date'), 'from', $values) ?> + <?= $this->form->date(t('End date'), 'to', $values) ?> + <?= $this->form->checkbox('bom', t('Add a BOM at the beginning of the file (required for Microsoft Excel)'), 1, isset($values['bom']) && $values['bom'] == 1) ?> + + <div class="form-actions"> + <button type="submit" class="btn btn-blue js-form-export"><?= t('Export') ?></button> + <?= t('or') ?> + <?= $this->url->link(t('cancel'), 'ExportController', 'tasks', array('project_id' => $project['id']), false, 'js-modal-close') ?> + </div> +</form> diff --git a/app/Template/export/transitions.php b/app/Template/export/transitions.php new file mode 100644 index 0000000..a21f8e3 --- /dev/null +++ b/app/Template/export/transitions.php @@ -0,0 +1,17 @@ +<?= $this->render('export/header', array('project' => $project, 'title' => $title)) ?> + +<p class="alert alert-info"><?= t('This report contains all column moves for each task with the date, the user and the time spent for each transition.') ?></p> + +<form class="js-modal-ignore-form" method="post" action="<?= $this->url->href('ExportController', 'transitions', array('project_id' => $project['id'])) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + <?= $this->form->hidden('project_id', $values) ?> + <?= $this->form->date(t('Start date'), 'from', $values) ?> + <?= $this->form->date(t('End date'), 'to', $values) ?> + <?= $this->form->checkbox('bom', t('Add a BOM at the beginning of the file (required for Microsoft Excel)'), 1, isset($values['bom']) && $values['bom'] == 1) ?> + + <div class="form-actions"> + <button type="submit" class="btn btn-blue js-form-export"><?= t('Export') ?></button> + <?= t('or') ?> + <?= $this->url->link(t('cancel'), 'ExportController', 'transitions', array('project_id' => $project['id']), false, 'js-modal-close') ?> + </div> +</form> diff --git a/app/Template/external_task_creation/step1.php b/app/Template/external_task_creation/step1.php new file mode 100644 index 0000000..2a3b014 --- /dev/null +++ b/app/Template/external_task_creation/step1.php @@ -0,0 +1,16 @@ +<form method="post" action="<?= $this->url->href('ExternalTaskCreationController', 'step2', array('project_id' => $project['id'], 'provider_name' => $provider_name)) ?>"> + <?= $this->form->csrf() ?> + <?= $this->form->hidden('swimlane_id', $values) ?> + <?= $this->form->hidden('column_id', $values) ?> + + <?= $this->render($template, array( + 'project' => $project, + 'values' => $values, + )) ?> + + <?php if (! empty($error_message)): ?> + <div class="alert alert-error"><?= $this->text->e($error_message) ?></div> + <?php endif ?> + + <?= $this->modal->submitButtons(array('submitLabel' => t('Next'))) ?> +</form> diff --git a/app/Template/external_task_creation/step2.php b/app/Template/external_task_creation/step2.php new file mode 100644 index 0000000..baace3a --- /dev/null +++ b/app/Template/external_task_creation/step2.php @@ -0,0 +1,22 @@ +<form method="post" action="<?= $this->url->href('ExternalTaskCreationController', 'step3', array('project_id' => $project['id'], 'provider_name' => $provider_name)) ?>"> + <?= $this->form->csrf() ?> + <?= $this->form->hidden('external_provider', $values) ?> + <?= $this->form->hidden('external_uri', $values) ?> + + <?= $this->render($template, array( + 'project' => $project, + 'external_task' => $external_task, + 'values' => $values, + 'errors' => $errors, + 'users_list' => $users_list, + 'categories_list' => $categories_list, + 'swimlanes_list' => $swimlanes_list, + 'columns_list' => $columns_list, + )) ?> + + <?php if (! empty($error_message)): ?> + <div class="alert alert-error"><?= $this->text->e($error_message) ?></div> + <?php endif ?> + + <?= $this->modal->submitButtons() ?> +</form> diff --git a/app/Template/external_task_modification/show.php b/app/Template/external_task_modification/show.php new file mode 100644 index 0000000..933ad0d --- /dev/null +++ b/app/Template/external_task_modification/show.php @@ -0,0 +1,22 @@ +<form method="post" action="<?= $this->url->href('TaskModificationController', 'update', array('task_id' => $task['id'])) ?>"> + <?= $this->form->csrf() ?> + <?= $this->form->hidden('id', $values) ?> + <?= $this->form->hidden('project_id', $values) ?> + + <?php if (! empty($error_message)): ?> + <p class="alert alert-error"><?= $this->text->e($error_message) ?></p> + <?php else: ?> + <?= $this->render($template, array( + 'project' => $project, + 'task' => $task, + 'external_task' => $external_task, + 'tags' => $tags, + 'users_list' => $users_list, + 'categories_list' => $categories_list, + 'values' => $values, + 'errors' => $errors, + )) ?> + <?php endif ?> + + <?= $this->modal->submitButtons() ?> +</form> diff --git a/app/Template/feed/project.php b/app/Template/feed/project.php new file mode 100644 index 0000000..f9e9c13 --- /dev/null +++ b/app/Template/feed/project.php @@ -0,0 +1,23 @@ +<?= '<?xml version="1.0" encoding="UTF-8"?>' ?> +<feed xmlns="http://www.w3.org/2005/Atom"> + <title><?= t("%s's activity", $project['name']) ?> + + + + url->href('FeedController', 'project', ['token' => $project['token']], false, '', true) ?> + + + + url->href('TaskViewController', 'show', ['task_id' => $event['task_id']], false, 'event-'.$event['id'], true) ?> + + + + + + + <?= htmlentities($event['event_title'], ENT_XML1) ?> + ]]> + + + + \ No newline at end of file diff --git a/app/Template/feed/user.php b/app/Template/feed/user.php new file mode 100644 index 0000000..89f3093 --- /dev/null +++ b/app/Template/feed/user.php @@ -0,0 +1,23 @@ +' ?> + + <?= t('Project activities for %s', $this->user->getFullname($user)) ?> + + + + url->href('FeedController', 'user', ['token' => $user['token']], false, '', true) ?> + + + + url->href('TaskViewController', 'show', ['task_id' => $event['task_id']], false, 'event-'.$event['id'], true) ?> + + + + + + + <?= htmlentities($event['event_title'], ENT_XML1) ?> + ]]> + + + + \ No newline at end of file diff --git a/app/Template/file_viewer/show.php b/app/Template/file_viewer/show.php new file mode 100644 index 0000000..0c1c5e2 --- /dev/null +++ b/app/Template/file_viewer/show.php @@ -0,0 +1,14 @@ + +
+ + <?= $this->text->e($file['name']) ?> + +
+ text->markdown($content) ?> +
+ +
text->e($content) ?>
+ +
diff --git a/app/Template/group/associate.php b/app/Template/group/associate.php new file mode 100644 index 0000000..cd7f2b7 --- /dev/null +++ b/app/Template/group/associate.php @@ -0,0 +1,23 @@ + + +

+
+ url->link(t('Close this window'), 'GroupListController', 'index', array(), false, 'btn js-modal-close') ?> +
+ +
+ form->csrf() ?> + form->hidden('group_id', $values) ?> + + form->label(t('User'), 'user_id') ?> + app->component('select-dropdown-autocomplete', array( + 'name' => 'user_id', + 'items' => $users, + 'defaultValue' => isset($values['user_id']) ? $values['user_id'] : key($users), + )) ?> + + modal->submitButtons() ?> +
+ diff --git a/app/Template/group/dissociate.php b/app/Template/group/dissociate.php new file mode 100644 index 0000000..2483639 --- /dev/null +++ b/app/Template/group/dissociate.php @@ -0,0 +1,12 @@ + +
+

+ + modal->confirmButtons( + 'GroupListController', + 'removeUser', + array('group_id' => $group['id'], 'user_id' => $user['id']) + ) ?> +
diff --git a/app/Template/group/dropdown.php b/app/Template/group/dropdown.php new file mode 100644 index 0000000..9d807dc --- /dev/null +++ b/app/Template/group/dropdown.php @@ -0,0 +1,9 @@ + diff --git a/app/Template/group/index.php b/app/Template/group/index.php new file mode 100644 index 0000000..32d1460 --- /dev/null +++ b/app/Template/group/index.php @@ -0,0 +1,71 @@ + + +
+ +
+ +isEmpty()): ?> +

+ +
+
+
+ getTotal() > 1): ?> + getTotal()) ?> + + getTotal()) ?> + +
+
+ +
+
+ + getCollection() as $group): ?> +
+ + render('group/dropdown', array('group' => $group)) ?> + url->link($this->text->e($group['name']), 'GroupListController', 'users', array('group_id' => $group['id'])) ?> + + +
+
    + 1): ?> +
  • + +
  • + + + +
  • text->e($group['external_id']) ?>
  • + +
+
+
+ +
+ + + diff --git a/app/Template/group/remove.php b/app/Template/group/remove.php new file mode 100644 index 0000000..77d602f --- /dev/null +++ b/app/Template/group/remove.php @@ -0,0 +1,12 @@ + +
+

+ + modal->confirmButtons( + 'GroupListController', + 'remove', + array('group_id' => $group['id']) + ) ?> +
diff --git a/app/Template/group/user_dropdown.php b/app/Template/group/user_dropdown.php new file mode 100644 index 0000000..48acb95 --- /dev/null +++ b/app/Template/group/user_dropdown.php @@ -0,0 +1,11 @@ + diff --git a/app/Template/group/users.php b/app/Template/group/users.php new file mode 100644 index 0000000..2469296 --- /dev/null +++ b/app/Template/group/users.php @@ -0,0 +1,44 @@ +
+ + isEmpty()): ?> +

+ +
+ render('user_list/header', array('paginator' => $paginator)) ?> + getCollection() as $user): ?> +
+
+ render('group/user_dropdown', array('user' => $user)) ?> + + avatar->small( + $user['id'], + $user['username'], + $user['name'], + $user['email'], + $user['avatar_path'], + 'avatar-inline' + ) ?> + url->link($this->text->e($user['name'] ?: $user['username']), 'UserViewController', 'show', array('user_id' => $user['id'])) ?> + +
+ + render('user_list/user_details', array( + 'user' => $user, + )) ?> + + render('user_list/user_icons', array( + 'user' => $user, + )) ?> +
+ +
+ + + +
diff --git a/app/Template/group_creation/show.php b/app/Template/group_creation/show.php new file mode 100644 index 0000000..7cec075 --- /dev/null +++ b/app/Template/group_creation/show.php @@ -0,0 +1,11 @@ + +
+ form->csrf() ?> + + form->label(t('Name'), 'name') ?> + form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="191"')) ?> + + modal->submitButtons() ?> +
diff --git a/app/Template/group_modification/show.php b/app/Template/group_modification/show.php new file mode 100644 index 0000000..aa64777 --- /dev/null +++ b/app/Template/group_modification/show.php @@ -0,0 +1,14 @@ + +
+ form->csrf() ?> + + form->hidden('id', $values) ?> + form->hidden('external_id', $values) ?> + + form->label(t('Name'), 'name') ?> + form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="191"')) ?> + + modal->submitButtons() ?> +
diff --git a/app/Template/header.php b/app/Template/header.php new file mode 100644 index 0000000..5241870 --- /dev/null +++ b/app/Template/header.php @@ -0,0 +1,43 @@ +render('header/title', array( + 'project' => isset($project) ? $project : null, + 'task' => isset($task) ? $task : null, + 'description' => isset($description) ? $description : null, + 'title' => $title, + 'board_selector' => isset($board_selector) ? $board_selector : null, +)) ?> + +render('header/user_notifications'), + $this->render('header/user_dropdown') + )) ?> + +
+
+ +
+
+ +
+ +
diff --git a/app/Template/header/board_selector.php b/app/Template/header/board_selector.php new file mode 100644 index 0000000..43a3f6d --- /dev/null +++ b/app/Template/header/board_selector.php @@ -0,0 +1,13 @@ +app->component('select-dropdown-autocomplete', array( + 'name' => 'boardId', + 'placeholder' => t('Display another project'), + 'ariaLabel' => t('Display another project'), + 'items' => $board_selector, + 'redirect' => array( + 'regex' => 'PROJECT_ID', + 'url' => $this->url->to('BoardViewController', 'show', array('project_id' => 'PROJECT_ID')), + ), + 'onFocus' => array( + 'board.selector.open', + ) +)) ?> \ No newline at end of file diff --git a/app/Template/header/creation_dropdown.php b/app/Template/header/creation_dropdown.php new file mode 100644 index 0000000..8d2a51a --- /dev/null +++ b/app/Template/header/creation_dropdown.php @@ -0,0 +1,21 @@ +user->hasAccess('ProjectCreationController', 'create'); ?> +app->config('disable_private_project', 0) == 0; ?> + + + + diff --git a/app/Template/header/custom_board_selector.php b/app/Template/header/custom_board_selector.php new file mode 100644 index 0000000..49bb185 --- /dev/null +++ b/app/Template/header/custom_board_selector.php @@ -0,0 +1,26 @@ + diff --git a/app/Template/header/title.php b/app/Template/header/title.php new file mode 100644 index 0000000..8db6949 --- /dev/null +++ b/app/Template/header/title.php @@ -0,0 +1,29 @@ +
+
+ + + url->link($this->text->e($project['name']), 'BoardViewController', 'show', array('project_id' => $project['id'])) ?> + + + text->e($title) ?> + + ( / text->e($project['task_limit']) ?>) + + + + + +
+ +
+ + + helper->projectHeader->hookBefore)) echo $this->helper->projectHeader->hookBefore; ?> + helper->projectHeader->hookAfter)) echo $this->helper->projectHeader->hookAfter; ?> +
+ + + render('header/custom_board_selector', array('board_selector' => $board_selector)) ?> + +
+
diff --git a/app/Template/header/user_dropdown.php b/app/Template/header/user_dropdown.php new file mode 100644 index 0000000..996ea89 --- /dev/null +++ b/app/Template/header/user_dropdown.php @@ -0,0 +1,41 @@ + diff --git a/app/Template/header/user_notifications.php b/app/Template/header/user_notifications.php new file mode 100644 index 0000000..036fee2 --- /dev/null +++ b/app/Template/header/user_notifications.php @@ -0,0 +1,7 @@ + +user->hasNotifications()): ?> + modal->mediumIcon('bell web-notification-icon', t('Unread notifications'), 'WebNotificationController', 'show', array('user_id' => $this->user->getId())) ?> + + modal->mediumIcon('bell', t('My notifications'), 'WebNotificationController', 'show', array('user_id' => $this->user->getId())) ?> + + diff --git a/app/Template/layout.php b/app/Template/layout.php new file mode 100644 index 0000000..45a7e75 --- /dev/null +++ b/app/Template/layout.php @@ -0,0 +1,81 @@ + +app->isRtlLanguage()): ?> dir="rtl"> + + + + + + + + + + + + + + asset->colorCss() ?> + asset->css('assets/css/vendor.min.css') ?> + + asset->css('assets/css/'.$this->user->getTheme().'.min.css') ?> + + asset->css('assets/css/light.min.css') ?> + + asset->css('assets/css/print.min.css', true, 'print') ?> + asset->css('assets/css/custom_login.css') ?> + asset->css('assets/css/custom_dashboard.css') ?> + asset->customCss() ?> + + + asset->js('assets/js/vendor.min.js') ?> + asset->js('assets/js/app.min.js') ?> + + + hook->asset('css', 'template:layout:css') ?> + hook->asset('js', 'template:layout:js') ?> + + + + + + + + + + <?php if (isset($page_title)): ?> + <?= $this->text->e($page_title) ?> + <?php elseif (isset($title)): ?> + <?= $this->text->e($title) ?> + <?php else: ?> + Kanboard + <?php endif ?> + + + hook->render('template:layout:head') ?> + + + + + app->flashMessage() ?> + + + hook->render('template:layout:top') ?> + render('header', array( + 'title' => $title, + 'description' => isset($description) ? $description : '', + 'board_selector' => isset($board_selector) ? $board_selector : array(), + 'project' => isset($project) ? $project : array(), + )) ?> +
+ app->flashMessage() ?> + +
+ hook->render('template:layout:bottom') ?> + + + diff --git a/app/Template/link/create.php b/app/Template/link/create.php new file mode 100644 index 0000000..37610a3 --- /dev/null +++ b/app/Template/link/create.php @@ -0,0 +1,12 @@ + + +
+ form->csrf() ?> + form->label(t('Label'), 'label') ?> + form->text('label', $values, $errors, array('required', 'autofocus')) ?> + form->label(t('Opposite label'), 'opposite_label') ?> + form->text('opposite_label', $values, $errors) ?> + modal->submitButtons() ?> +
diff --git a/app/Template/link/edit.php b/app/Template/link/edit.php new file mode 100644 index 0000000..4be5657 --- /dev/null +++ b/app/Template/link/edit.php @@ -0,0 +1,17 @@ + + +
+ + form->csrf() ?> + form->hidden('id', $values) ?> + + form->label(t('Label'), 'label') ?> + form->text('label', $values, $errors, array('required')) ?> + + form->label(t('Opposite label'), 'opposite_id') ?> + form->select('opposite_id', $labels, $values, $errors) ?> + + modal->submitButtons() ?> +
diff --git a/app/Template/link/remove.php b/app/Template/link/remove.php new file mode 100644 index 0000000..e5ea246 --- /dev/null +++ b/app/Template/link/remove.php @@ -0,0 +1,15 @@ + + +
+

+ +

+ + modal->confirmButtons( + 'LinkController', + 'remove', + array('link_id' => $link['id']) + ) ?> +
diff --git a/app/Template/link/show.php b/app/Template/link/show.php new file mode 100644 index 0000000..6aadd66 --- /dev/null +++ b/app/Template/link/show.php @@ -0,0 +1,36 @@ + + + + + + + + + + + + + +
+ + + + | + + +
    + modal->medium('edit', t('Edit'), 'LinkController', 'edit', array('link_id' => $link['id'])) ?> + + modal->confirm('trash-o', t('Remove'), 'LinkController', 'confirm', array('link_id' => $link['id'])) ?> +
+
+ + + diff --git a/app/Template/notification/comment_create.php b/app/Template/notification/comment_create.php new file mode 100644 index 0000000..1f890a5 --- /dev/null +++ b/app/Template/notification/comment_create.php @@ -0,0 +1,15 @@ + + +

text->e($task['title']) ?> (#)

+ + +

+ +

+ + +text->markdown($comment['comment'], true) ?> + +render('notification/footer', array('task' => $task)) ?> + + \ No newline at end of file diff --git a/app/Template/notification/comment_delete.php b/app/Template/notification/comment_delete.php new file mode 100644 index 0000000..224fcdb --- /dev/null +++ b/app/Template/notification/comment_delete.php @@ -0,0 +1,11 @@ + + +

text->e($task['title']) ?> (#)

+ +

+ +text->markdown($comment['comment'], true) ?> + +render('notification/footer', array('task' => $task)) ?> + + \ No newline at end of file diff --git a/app/Template/notification/comment_update.php b/app/Template/notification/comment_update.php new file mode 100644 index 0000000..87c785c --- /dev/null +++ b/app/Template/notification/comment_update.php @@ -0,0 +1,11 @@ + + +

text->e($task['title']) ?> (#)

+ +

+ +text->markdown($comment['comment'], true) ?> + +render('notification/footer', array('task' => $task)) ?> + + \ No newline at end of file diff --git a/app/Template/notification/comment_user_mention.php b/app/Template/notification/comment_user_mention.php new file mode 100644 index 0000000..afa3aa7 --- /dev/null +++ b/app/Template/notification/comment_user_mention.php @@ -0,0 +1,11 @@ + + +

+ +

text->e($task['title']) ?>

+ +text->markdown($comment['comment'], true) ?> + +render('notification/footer', array('task' => $task)) ?> + + \ No newline at end of file diff --git a/app/Template/notification/footer.php b/app/Template/notification/footer.php new file mode 100644 index 0000000..6888d50 --- /dev/null +++ b/app/Template/notification/footer.php @@ -0,0 +1,9 @@ +
+Kanboard + +app->config('application_url') != ''): ?> + + - url->absoluteLink(t('view the task on Kanboard'), 'TaskViewController', 'show', array('task_id' => $task['id'])) ?> + + - url->absoluteLink(t('view the board on Kanboard'), 'BoardViewController', 'show', array('project_id' => $task['project_id'])) ?> + diff --git a/app/Template/notification/subtask_create.php b/app/Template/notification/subtask_create.php new file mode 100644 index 0000000..f5b91d3 --- /dev/null +++ b/app/Template/notification/subtask_create.php @@ -0,0 +1,21 @@ + + +

text->e($task['title']) ?> (#)

+ +

+ +
    +
  • text->e($subtask['title']) ?>
  • +
  • +
  • text->e($subtask['name'] ?: $subtask['username'] ?: '?') ?>
  • + +
  • + + +
  • + +
+ +render('notification/footer', array('task' => $task)) ?> + + \ No newline at end of file diff --git a/app/Template/notification/subtask_delete.php b/app/Template/notification/subtask_delete.php new file mode 100644 index 0000000..eaf51e4 --- /dev/null +++ b/app/Template/notification/subtask_delete.php @@ -0,0 +1,15 @@ + + +

text->e($task['title']) ?> (#)

+ +

+ +
    +
  • text->e($subtask['title']) ?>
  • +
  • +
  • text->e($subtask['name'] ?: $subtask['username'] ?: '?') ?>
  • +
+ +render('notification/footer', array('task' => $task)) ?> + + \ No newline at end of file diff --git a/app/Template/notification/subtask_update.php b/app/Template/notification/subtask_update.php new file mode 100644 index 0000000..95663e8 --- /dev/null +++ b/app/Template/notification/subtask_update.php @@ -0,0 +1,27 @@ + + +

text->e($task['title']) ?> (#)

+ +

+ +
    +
  • text->e($subtask['title']) ?>
  • +
  • +
  • text->e($subtask['name'] ?: $subtask['username'] ?: '?') ?>
  • + +
  • + + + + + / + + + +
  • + +
+ +render('notification/footer', array('task' => $task)) ?> + + \ No newline at end of file diff --git a/app/Template/notification/task_assignee_change.php b/app/Template/notification/task_assignee_change.php new file mode 100644 index 0000000..de47e56 --- /dev/null +++ b/app/Template/notification/task_assignee_change.php @@ -0,0 +1,24 @@ + + +

text->e($task['title']) ?> (#)

+ +
    +
  • + + + + + + + +
  • +
+ + +

+ text->markdown($task['description'], true) ?: t('There is no description.') ?> + + +render('notification/footer', array('task' => $task)) ?> + + \ No newline at end of file diff --git a/app/Template/notification/task_close.php b/app/Template/notification/task_close.php new file mode 100644 index 0000000..67d04f6 --- /dev/null +++ b/app/Template/notification/task_close.php @@ -0,0 +1,9 @@ + + +

text->e($task['title']) ?> (#)

+ +

+ +render('notification/footer', array('task' => $task)) ?> + + \ No newline at end of file diff --git a/app/Template/notification/task_create.php b/app/Template/notification/task_create.php new file mode 100644 index 0000000..3a34dbe --- /dev/null +++ b/app/Template/notification/task_create.php @@ -0,0 +1,47 @@ + + +

text->e($task['title']) ?> (#)

+ +
    +
  • + dt->datetime($task['date_creation']) ?> +
  • + +
  • + dt->datetime($task['date_due']) ?> +
  • + + +
  • + +
  • + +
  • + + + + + + + +
  • +
  • + + text->e($task['column_title']) ?> +
  • +
  • text->e($task['position']) ?>
  • + +
  • + text->e($task['category_name']) ?> +
  • + +
+ + +

+ text->markdown($task['description'], true) ?> + + +render('notification/footer', array('task' => $task)) ?> + + \ No newline at end of file diff --git a/app/Template/notification/task_file_create.php b/app/Template/notification/task_file_create.php new file mode 100644 index 0000000..c8f6b30 --- /dev/null +++ b/app/Template/notification/task_file_create.php @@ -0,0 +1,9 @@ + + +

text->e($task['title']) ?> (#)

+ +

+ +render('notification/footer', array('task' => $task)) ?> + + \ No newline at end of file diff --git a/app/Template/notification/task_file_destroy.php b/app/Template/notification/task_file_destroy.php new file mode 100644 index 0000000..17d0463 --- /dev/null +++ b/app/Template/notification/task_file_destroy.php @@ -0,0 +1,9 @@ + + +

text->e($task['title']) ?> (#)

+ +

+ +render('notification/footer', array('task' => $task)) ?> + + \ No newline at end of file diff --git a/app/Template/notification/task_internal_link_create_update.php b/app/Template/notification/task_internal_link_create_update.php new file mode 100644 index 0000000..5273dc3 --- /dev/null +++ b/app/Template/notification/task_internal_link_create_update.php @@ -0,0 +1,13 @@ + + +

text->e($task['title']) ?> (#)

+ +

+ url->absoluteLink(t('#%d', $task_link['opposite_task_id']), 'TaskViewController', 'show', array('task_id' => $task_link['opposite_task_id'])), + e($task_link['label'])) ?> +

+ +render('notification/footer', array('task' => $task)) ?> + + \ No newline at end of file diff --git a/app/Template/notification/task_internal_link_delete.php b/app/Template/notification/task_internal_link_delete.php new file mode 100644 index 0000000..b3413c7 --- /dev/null +++ b/app/Template/notification/task_internal_link_delete.php @@ -0,0 +1,13 @@ + + +

text->e($task['title']) ?> (#)

+ +

+ url->absoluteLink(t('#%d', $task_link['opposite_task_id']), 'TaskViewController', 'show', array('task_id' => $task_link['opposite_task_id']))) ?> +

+ +render('notification/footer', array('task' => $task)) ?> + + \ No newline at end of file diff --git a/app/Template/notification/task_move_column.php b/app/Template/notification/task_move_column.php new file mode 100644 index 0000000..e4281d5 --- /dev/null +++ b/app/Template/notification/task_move_column.php @@ -0,0 +1,15 @@ + + +

text->e($task['title']) ?> (#)

+ +
    +
  • + + text->e($task['column_title']) ?> +
  • +
  • text->e($task['position']) ?>
  • +
+ +render('notification/footer', array('task' => $task)) ?> + + \ No newline at end of file diff --git a/app/Template/notification/task_move_position.php b/app/Template/notification/task_move_position.php new file mode 100644 index 0000000..e4281d5 --- /dev/null +++ b/app/Template/notification/task_move_position.php @@ -0,0 +1,15 @@ + + +

text->e($task['title']) ?> (#)

+ +
    +
  • + + text->e($task['column_title']) ?> +
  • +
  • text->e($task['position']) ?>
  • +
+ +render('notification/footer', array('task' => $task)) ?> + + \ No newline at end of file diff --git a/app/Template/notification/task_move_project.php b/app/Template/notification/task_move_project.php new file mode 100644 index 0000000..96f4e54 --- /dev/null +++ b/app/Template/notification/task_move_project.php @@ -0,0 +1,9 @@ + + +

text->e($task['title']) ?> (#)

+ +

+ +render('notification/footer', array('task' => $task)) ?> + + \ No newline at end of file diff --git a/app/Template/notification/task_move_swimlane.php b/app/Template/notification/task_move_swimlane.php new file mode 100644 index 0000000..95de271 --- /dev/null +++ b/app/Template/notification/task_move_swimlane.php @@ -0,0 +1,23 @@ + + +

text->e($task['title']) ?> (#)

+ +
    +
  • + + + + + text->e($task['swimlane_name']) ?> + +
  • +
  • + + text->e($task['column_title']) ?> +
  • +
  • text->e($task['position']) ?>
  • +
+ +render('notification/footer', array('task' => $task)) ?> + + \ No newline at end of file diff --git a/app/Template/notification/task_open.php b/app/Template/notification/task_open.php new file mode 100644 index 0000000..2123d5f --- /dev/null +++ b/app/Template/notification/task_open.php @@ -0,0 +1,9 @@ + + +

text->e($task['title']) ?> (#)

+ +

+ +render('notification/footer', array('task' => $task)) ?> + + \ No newline at end of file diff --git a/app/Template/notification/task_overdue.php b/app/Template/notification/task_overdue.php new file mode 100644 index 0000000..8da0d62 --- /dev/null +++ b/app/Template/notification/task_overdue.php @@ -0,0 +1,41 @@ + + +

+ + + + + + + + + + + + + + + + + + + +
# + app->config('application_url') !== ''): ?> + url->absoluteLink($this->text->e($task['title']), 'TaskViewController', 'show', array('task_id' => $task['id'])) ?> + + text->e($task['title']) ?> + + dt->datetime($task['date_due']) ?> + app->config('application_url') !== ''): ?> + url->absoluteLink($this->text->e($task['project_name']), 'BoardViewController', 'show', array('project_id' => $task['project_id'])) ?> + + text->e($task['project_name']) ?> + + + + text->e($task['assignee_name'] ?: $task['assignee_username']) ?> + +
+ + \ No newline at end of file diff --git a/app/Template/notification/task_update.php b/app/Template/notification/task_update.php new file mode 100644 index 0000000..86b7466 --- /dev/null +++ b/app/Template/notification/task_update.php @@ -0,0 +1,8 @@ + + +

text->e($task['title']) ?> (#)

+ +render('task/changes', array('changes' => $changes, 'task' => $task, 'public' => true)) ?> +render('notification/footer', array('task' => $task)) ?> + + \ No newline at end of file diff --git a/app/Template/notification/task_user_mention.php b/app/Template/notification/task_user_mention.php new file mode 100644 index 0000000..dc72169 --- /dev/null +++ b/app/Template/notification/task_user_mention.php @@ -0,0 +1,11 @@ + + +

+

text->e($task['title']) ?>

+ +

+text->markdown($task['description'], true) ?> + +render('notification/footer', array('task' => $task)) ?> + + \ No newline at end of file diff --git a/app/Template/password_reset/change.php b/app/Template/password_reset/change.php new file mode 100644 index 0000000..ac93309 --- /dev/null +++ b/app/Template/password_reset/change.php @@ -0,0 +1,16 @@ + diff --git a/app/Template/password_reset/create.php b/app/Template/password_reset/create.php new file mode 100644 index 0000000..52bfc9f --- /dev/null +++ b/app/Template/password_reset/create.php @@ -0,0 +1,18 @@ + diff --git a/app/Template/password_reset/email.php b/app/Template/password_reset/email.php new file mode 100644 index 0000000..63b08e4 --- /dev/null +++ b/app/Template/password_reset/email.php @@ -0,0 +1,6 @@ +

+ +

url->to('PasswordResetController', 'change', array('token' => $token), '', true) ?>

+ +
+Kanboard diff --git a/app/Template/plugin/directory.php b/app/Template/plugin/directory.php new file mode 100644 index 0000000..eaaa8db --- /dev/null +++ b/app/Template/plugin/directory.php @@ -0,0 +1,53 @@ + + + +

+ +

+ + + +

+ + $plugin): ?> + + + + + + + + + + + + +
+ text->e($plugin['title']) ?> +
+ text->e($plugin['author']) ?> + + text->e($plugin['version']) ?> + + + + url->icon('cloud-download', t('Install'), 'PluginController', 'install', array('archive_url' => urlencode($plugin['download'])), true) ?> + + url->icon('refresh', t('Update'), 'PluginController', 'update', array('archive_url' => urlencode($plugin['download'])), true) ?> + + + + + + + + +
+
+ text->markdown($plugin['description']) ?> +
+
+ + diff --git a/app/Template/plugin/layout.php b/app/Template/plugin/layout.php new file mode 100644 index 0000000..6eafa59 --- /dev/null +++ b/app/Template/plugin/layout.php @@ -0,0 +1,9 @@ +
+ +
diff --git a/app/Template/plugin/remove.php b/app/Template/plugin/remove.php new file mode 100644 index 0000000..1280f8a --- /dev/null +++ b/app/Template/plugin/remove.php @@ -0,0 +1,13 @@ + + +
+

getPluginName()) ?>

+ + modal->confirmButtons( + 'PluginController', + 'uninstall', + array('pluginId' => $plugin_id) + ) ?> +
diff --git a/app/Template/plugin/show.php b/app/Template/plugin/show.php new file mode 100644 index 0000000..90b62bd --- /dev/null +++ b/app/Template/plugin/show.php @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + $plugin): ?> + + + + + + + + + + + + + +
+ getPluginHomepage()): ?> + text->e($plugin->getPluginName()) ?> + + text->e($plugin->getPluginName()) ?> + + text->e($plugin->getPluginAuthor()) ?>text->e($plugin->getPluginVersion()) ?>text->e($plugin->getCompatibleVersion()) ?> + modal->confirm('trash-o', t('Uninstall'), 'PluginController', 'confirm', array('pluginId' => $pluginFolder)) ?> +
text->e($plugin->getPluginDescription()) ?>
+ + + + + +

+ + + + + + + + + + + + $plugin): ?> + + + + + + + + + + + + +
+ getPluginHomepage()): ?> + text->e($plugin->getPluginName()) ?> + + text->e($plugin->getPluginName()) ?> + + text->e($plugin->getPluginAuthor()) ?>text->e($plugin->getPluginVersion()) ?> + modal->confirm('trash-o', t('Uninstall'), 'PluginController', 'confirm', array('pluginId' => $pluginFolder)) ?> +
text->e($plugin->getPluginDescription()) ?>
+ diff --git a/app/Template/plugin/sidebar.php b/app/Template/plugin/sidebar.php new file mode 100644 index 0000000..dd1a2a6 --- /dev/null +++ b/app/Template/plugin/sidebar.php @@ -0,0 +1,10 @@ + diff --git a/app/Template/predefined_task_description/create.php b/app/Template/predefined_task_description/create.php new file mode 100644 index 0000000..5a7a8d9 --- /dev/null +++ b/app/Template/predefined_task_description/create.php @@ -0,0 +1,14 @@ + +
+ form->csrf() ?> + + form->label(t('Title'), 'title') ?> + form->text('title', $values, $errors, array('autofocus', 'required', 'tabindex="1"')) ?> + + form->label(t('Description'), 'description') ?> + form->textEditor('description', $values, $errors, array('tabindex' => 2)) ?> + + modal->submitButtons() ?> +
diff --git a/app/Template/predefined_task_description/edit.php b/app/Template/predefined_task_description/edit.php new file mode 100644 index 0000000..039d650 --- /dev/null +++ b/app/Template/predefined_task_description/edit.php @@ -0,0 +1,14 @@ + +
+ form->csrf() ?> + + form->label(t('Title'), 'title') ?> + form->text('title', $values, $errors, array('autofocus', 'required', 'tabindex="1"')) ?> + + form->label(t('Description'), 'description') ?> + form->textEditor('description', $values, $errors, array('tabindex' => 2)) ?> + + modal->submitButtons() ?> +
diff --git a/app/Template/predefined_task_description/remove.php b/app/Template/predefined_task_description/remove.php new file mode 100644 index 0000000..f60a8e7 --- /dev/null +++ b/app/Template/predefined_task_description/remove.php @@ -0,0 +1,15 @@ + + +
+

+ +

+ + modal->confirmButtons( + 'PredefinedTaskDescriptionController', + 'remove', + array('project_id' => $project['id'], 'id' => $template['id']) + ) ?> +
diff --git a/app/Template/project/dropdown.php b/app/Template/project/dropdown.php new file mode 100644 index 0000000..4b77637 --- /dev/null +++ b/app/Template/project/dropdown.php @@ -0,0 +1,28 @@ + diff --git a/app/Template/project/layout.php b/app/Template/project/layout.php new file mode 100644 index 0000000..ec03920 --- /dev/null +++ b/app/Template/project/layout.php @@ -0,0 +1,11 @@ +
+ projectHeader->render($project, 'TaskListController', 'show') ?> + +
diff --git a/app/Template/project/sidebar.php b/app/Template/project/sidebar.php new file mode 100644 index 0000000..d59ad71 --- /dev/null +++ b/app/Template/project/sidebar.php @@ -0,0 +1,74 @@ + diff --git a/app/Template/project_action_duplication/show.php b/app/Template/project_action_duplication/show.php new file mode 100644 index 0000000..c2f52e3 --- /dev/null +++ b/app/Template/project_action_duplication/show.php @@ -0,0 +1,15 @@ + + +

+ +
+ form->csrf() ?> + + form->label(t('Create from another project'), 'src_project_id') ?> + form->select('src_project_id', $projects_list) ?> + + modal->submitButtons() ?> +
+ diff --git a/app/Template/project_creation/create.php b/app/Template/project_creation/create.php new file mode 100644 index 0000000..877e86a --- /dev/null +++ b/app/Template/project_creation/create.php @@ -0,0 +1,52 @@ +
+ +
+ + form->csrf() ?> + form->hidden('is_private', $values) ?> + + form->label(t('Name'), 'name') ?> + form->text('name', $values, $errors, array('autofocus', 'required')) ?> + + form->label(t('Identifier'), 'identifier') ?> + form->text('identifier', $values, $errors, array('autofocus')) ?> +

+ + form->checkbox('per_swimlane_task_limits', t('Column task limits apply to each swimlane individually'), 1, false) ?> + + form->label(t('Task limit'), 'task_limit') ?> + form->number('task_limit', $values, $errors, array('min="0"')) ?> + + 1): ?> + form->label(t('Create from another project'), 'src_project_id') ?> + form->select('src_project_id', $projects_list, $values, array(), array(), 'js-project-creation-select-options') ?> + + +
0 ? '' : 'style="display: none"' ?>> +

+ + + form->checkbox('projectPermissionModel', t('Permissions'), 1, true) ?> + form->checkbox('projectRoleModel', t('Custom roles'), 1, true) ?> + + + form->checkbox('categoryModel', t('Categories'), 1, true) ?> + form->checkbox('tagDuplicationModel', t('Tags'), 1, true) ?> + form->checkbox('actionModel', t('Actions'), 1, true) ?> + form->checkbox('customFilterModel', t('Custom filters'), 1, true) ?> + form->checkbox('projectMetadataModel', t('Metadata'), 1, false) ?> + form->checkbox('projectTaskDuplicationModel', t('Tasks'), 1, false) ?> +
+ + hook->render('template:project:creation:form', array('values' => $values, 'errors' => $errors)) ?> + + modal->submitButtons() ?> +
+ +
+

+
+ +
diff --git a/app/Template/project_edit/show.php b/app/Template/project_edit/show.php new file mode 100644 index 0000000..c1fc6f3 --- /dev/null +++ b/app/Template/project_edit/show.php @@ -0,0 +1,76 @@ +app->isAjax()): ?> + + + + +
+ form->csrf() ?> + +
+ + + form->label(t('Name'), 'name') ?> + form->text('name', $values, $errors, array('required', 'autofocus', 'tabindex="1"')) ?> + + form->label(t('Email'), 'email') ?> + form->email('email', $values, $errors, array('tabindex="2"', 'autocomplete="email"')) ?> +

+ + form->label(t('Identifier'), 'identifier') ?> + form->text('identifier', $values, $errors, array('maxlength="50"', 'tabindex="3"')) ?> +

+ + form->label(t('Description'), 'description') ?> + form->textEditor('description', $values, $errors, array('tabindex' => 4)) ?> + + hook->render('template:project:edit:form', array('values' => $values, 'errors' => $errors)) ?> + + + form->checkbox('per_swimlane_task_limits', t('Task limits apply to each swimlane individually'), 1, $project['per_swimlane_task_limits'] == 1, '', array('tabindex' => 5)) ?> + + form->label(t('Task limit'), 'task_limit') ?> + form->number('task_limit', $values, $errors, array('tabindex' => 6, 'min="0"')) ?> +
+ +
+ + + app->config('disable_private_project') != 1): ?> + user->hasProjectAccess('ProjectCreationController', 'create', $project['id'])): ?> + form->checkbox('is_private', t('Personal project'), 1, $project['is_private'] == 1) ?> +

+ + + +
+ form->label(t('Project owner'), 'owner_id') ?> + form->select('owner_id', $owners, $values, $errors, array('tabindex="7"')) ?> +
+
+ +
+ + + form->date(t('Start date'), 'start_date', $values, $errors, array('tabindex="8"')) ?> + form->date(t('End date'), 'end_date', $values, $errors, array('tabindex="9"')) ?> +
+ +
+ + + form->label(t('Default priority'), 'priority_default') ?> + form->number('priority_default', $values, $errors, array('tabindex="10"')) ?> + + form->label(t('Lowest priority'), 'priority_start') ?> + form->number('priority_start', $values, $errors, array('tabindex="11"')) ?> + + form->label(t('Highest priority'), 'priority_end') ?> + form->number('priority_end', $values, $errors, array('tabindex="12"')) ?> +
+ + modal->submitButtons(array('tabindex' => 13)) ?> +
diff --git a/app/Template/project_file/create.php b/app/Template/project_file/create.php new file mode 100644 index 0000000..28b80ce --- /dev/null +++ b/app/Template/project_file/create.php @@ -0,0 +1,21 @@ + + +app->component('file-upload', array( + 'csrf' => $this->app->getToken()->getReusableCSRFToken(), + 'maxSize' => $max_size, + 'url' => $this->url->to('ProjectFileController', 'save', array('project_id' => $project['id'])), + 'labelDropzone' => t('Drag and drop your files here'), + 'labelOr' => t('or'), + 'labelChooseFiles' => t('choose files'), + 'labelOversize' => $max_size > 0 ? t('The maximum allowed file size is %sB.', $this->text->bytes($max_size)) : null, + 'labelSuccess' => t('All files have been uploaded successfully.'), + 'labelCloseSuccess' => t('Close this window'), + 'labelUploadError' => t('Unable to upload this file.'), +)) ?> + +modal->submitButtons(array( + 'submitLabel' => t('Upload files'), + 'disabled' => true, +)) ?> diff --git a/app/Template/project_file/remove.php b/app/Template/project_file/remove.php new file mode 100644 index 0000000..043b8fc --- /dev/null +++ b/app/Template/project_file/remove.php @@ -0,0 +1,15 @@ + + +
+

+ text->e($file['name'])) ?> +

+ + modal->confirmButtons( + 'ProjectFileController', + 'remove', + array('project_id' => $project['id'], 'file_id' => $file['id']) + ) ?> +
diff --git a/app/Template/project_header/dropdown.php b/app/Template/project_header/dropdown.php new file mode 100644 index 0000000..745ad66 --- /dev/null +++ b/app/Template/project_header/dropdown.php @@ -0,0 +1,83 @@ + diff --git a/app/Template/project_header/header.php b/app/Template/project_header/header.php new file mode 100644 index 0000000..a92d630 --- /dev/null +++ b/app/Template/project_header/header.php @@ -0,0 +1,13 @@ +helper->projectHeader->hookBefore = $this->hook->render('template:project:header:before', array('project' => $project)); +$this->helper->projectHeader->dropdownHtml = $this->render('project_header/dropdown', array('project' => $project, 'board_view' => $board_view)); +$this->helper->projectHeader->viewsHtml = $this->render('project_header/views', array('project' => $project, 'filters' => $filters)); +$this->helper->projectHeader->searchHtml = $this->render('project_header/search', array( + 'project' => $project, + 'filters' => $filters, + 'custom_filters_list' => isset($custom_filters_list) ? $custom_filters_list : array(), + 'users_list' => isset($users_list) ? $users_list : array(), + 'categories_list' => isset($categories_list) ? $categories_list : array(), +)); +$this->helper->projectHeader->hookAfter = $this->hook->render('template:project:header:after', array('project' => $project)); +?> diff --git a/app/Template/project_header/search.php b/app/Template/project_header/search.php new file mode 100644 index 0000000..36bb73b --- /dev/null +++ b/app/Template/project_header/search.php @@ -0,0 +1,58 @@ +
+ +
diff --git a/app/Template/project_header/views.php b/app/Template/project_header/views.php new file mode 100644 index 0000000..e6e7a99 --- /dev/null +++ b/app/Template/project_header/views.php @@ -0,0 +1,22 @@ +
    + + hook->render('template:project-header:view-switcher-before-project-overview', array('project' => $project, 'filters' => $filters)) ?> + +
  • app->checkMenuSelection('ProjectOverviewController') ?>> + url->icon('eye', t('Overview'), 'ProjectOverviewController', 'show', array('project_id' => $project['id'], 'search' => $filters['search']), false, 'view-overview', t('Keyboard shortcut: "%s"', 'v o')) ?> +
  • + + hook->render('template:project-header:view-switcher-before-board-view', array('project' => $project, 'filters' => $filters)) ?> + +
  • app->checkMenuSelection('BoardViewController') ?>> + url->icon('th', t('Board'), 'BoardViewController', 'show', array('project_id' => $project['id'], 'search' => $filters['search']), false, 'view-board', t('Keyboard shortcut: "%s"', 'v b')) ?> +
  • + + hook->render('template:project-header:view-switcher-before-task-list', array('project' => $project, 'filters' => $filters)) ?> + +
  • app->checkMenuSelection('TaskListController') ?>> + url->icon('list', t('List'), 'TaskListController', 'show', array('project_id' => $project['id'], 'search' => $filters['search']), false, 'view-listing', t('Keyboard shortcut: "%s"', 'v l')) ?> +
  • + + hook->render('template:project-header:view-switcher', array('project' => $project, 'filters' => $filters)) ?> +
diff --git a/app/Template/project_list/header.php b/app/Template/project_list/header.php new file mode 100644 index 0000000..24ac904 --- /dev/null +++ b/app/Template/project_list/header.php @@ -0,0 +1,12 @@ +
+
+ getTotal() > 1): ?> + getTotal()) ?> + + getTotal()) ?> + +
+
+ render('project_list/sort_menu', array('paginator' => $paginator)) ?> +
+
diff --git a/app/Template/project_list/listing.php b/app/Template/project_list/listing.php new file mode 100644 index 0000000..d1ecd4b --- /dev/null +++ b/app/Template/project_list/listing.php @@ -0,0 +1,56 @@ + + +
+ +
+ +isEmpty()): ?> +

+ +
+ render('project_list/header', array('paginator' => $paginator)) ?> + getCollection() as $project): ?> +
+ render('project_list/project_title', array( + 'project' => $project, + )) ?> + + render('project_list/project_details', array( + 'project' => $project, + )) ?> + + render('project_list/project_icons', array( + 'project' => $project, + )) ?> +
+ +
+ + + diff --git a/app/Template/project_list/project_details.php b/app/Template/project_list/project_details.php new file mode 100644 index 0000000..e83c6c7 --- /dev/null +++ b/app/Template/project_list/project_details.php @@ -0,0 +1,15 @@ +
+
    + 0): ?> +
  • text->e($project['owner_name'] ?: $project['owner_username']) ?>
  • + + + +
  • dt->date($project['start_date']) ?>
  • + + + +
  • dt->date($project['end_date']) ?>
  • + +
+
\ No newline at end of file diff --git a/app/Template/project_list/project_icons.php b/app/Template/project_list/project_icons.php new file mode 100644 index 0000000..45d696c --- /dev/null +++ b/app/Template/project_list/project_icons.php @@ -0,0 +1,23 @@ +
+   + + + + + + + + + + user->hasAccess('ProjectUserOverviewController', 'managers')): ?> + app->tooltipLink('', $this->url->href('ProjectUserOverviewController', 'users', array('project_id' => $project['id']))) ?> + + + + app->tooltipMarkdown($project['description']) ?> + + + + + +
diff --git a/app/Template/project_list/project_title.php b/app/Template/project_list/project_title.php new file mode 100644 index 0000000..04dd42b --- /dev/null +++ b/app/Template/project_list/project_title.php @@ -0,0 +1,16 @@ +
+ user->hasProjectAccess('ProjectViewController', 'show', $project['id'])): ?> + render('project/dropdown', array('project' => $project)) ?> + + + + + hook->render('template:dashboard:project:before-title', array('project' => $project)) ?> + + + url->link($this->text->e($project['name']), 'BoardViewController', 'show', array('project_id' => $project['id'])) ?> + + + hook->render('template:dashboard:project:after-title', array('project' => $project)) ?> + +
diff --git a/app/Template/project_list/sort_menu.php b/app/Template/project_list/sort_menu.php new file mode 100644 index 0000000..39bac60 --- /dev/null +++ b/app/Template/project_list/sort_menu.php @@ -0,0 +1,26 @@ + diff --git a/app/Template/project_overview/activity.php b/app/Template/project_overview/activity.php new file mode 100644 index 0000000..e7d7c1b --- /dev/null +++ b/app/Template/project_overview/activity.php @@ -0,0 +1,5 @@ +
+
+ render('event/events', array('events' => $events)) ?> +
+
\ No newline at end of file diff --git a/app/Template/project_overview/attachments.php b/app/Template/project_overview/attachments.php new file mode 100644 index 0000000..d50211a --- /dev/null +++ b/app/Template/project_overview/attachments.php @@ -0,0 +1,12 @@ +
+
+ user->hasProjectAccess('ProjectFileController', 'create', $project['id'])): ?> +
+ modal->mediumButton('plus', t('Upload a file'), 'ProjectFileController', 'create', array('project_id' => $project['id'])) ?> +
+ + + render('project_overview/images', array('project' => $project, 'images' => $images)) ?> + render('project_overview/files', array('project' => $project, 'files' => $files)) ?> +
+
diff --git a/app/Template/project_overview/columns.php b/app/Template/project_overview/columns.php new file mode 100644 index 0000000..22b0ffd --- /dev/null +++ b/app/Template/project_overview/columns.php @@ -0,0 +1,8 @@ +
+ +
+ + text->e($column['title']) ?> +
+ +
diff --git a/app/Template/project_overview/description.php b/app/Template/project_overview/description.php new file mode 100644 index 0000000..6ad9b67 --- /dev/null +++ b/app/Template/project_overview/description.php @@ -0,0 +1,12 @@ +
+
+ user->hasProjectAccess('ProjectEditController', 'show', $project['id'])): ?> +
+ modal->mediumButton('edit', t('Edit description'), 'ProjectEditController', 'show', array('project_id' => $project['id'])) ?> +
+ +
+ text->markdown($project['description']) ?> +
+
+
diff --git a/app/Template/project_overview/files.php b/app/Template/project_overview/files.php new file mode 100644 index 0000000..09b7915 --- /dev/null +++ b/app/Template/project_overview/files.php @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + +
+ + + + text->e($file['user_name'] ?: $file['username']) ?> + + dt->date($file['date']) ?> + + text->bytes($file['size']) ?> +
+ diff --git a/app/Template/project_overview/images.php b/app/Template/project_overview/images.php new file mode 100644 index 0000000..f875ce7 --- /dev/null +++ b/app/Template/project_overview/images.php @@ -0,0 +1,49 @@ + +
+ +
+ app->component('image-slideshow', array( + 'images' => $images, + 'image' => $file, + 'regex_file_id' => 'FILE_ID', + 'regex_etag' => 'ETAG', + 'url' => array( + 'image' => $this->url->to('FileViewerController', 'image', array('file_id' => 'FILE_ID', 'project_id' => $project['id'], 'etag' => 'ETAG')), + 'thumbnail' => $this->url->to('FileViewerController', 'thumbnail', array('file_id' => 'FILE_ID', 'project_id' => $project['id'], 'etag' => 'ETAG')), + 'download' => $this->url->to('FileViewerController', 'download', array('file_id' => 'FILE_ID', 'project_id' => $project['id'], 'etag' => 'ETAG')), + ) + )) ?> + +
+
+ +
+
+ app->tooltipMarkdown(t('Uploaded: %s', $this->dt->datetime($file['date']))."\n\n".t('Size: %s', $this->text->bytes($file['size']))) ?> + + + + dt->datetime($file['date'])) ?> + +
+
+
+ +
+ \ No newline at end of file diff --git a/app/Template/project_overview/information.php b/app/Template/project_overview/information.php new file mode 100644 index 0000000..bec0543 --- /dev/null +++ b/app/Template/project_overview/information.php @@ -0,0 +1,36 @@ +
+
+
+
    + 0): ?> +
  • text->e($project['owner_name'] ?: $project['owner_username']) ?>
  • + + + + $role_name): ?> + +
  • + text->e($role_name) ?>: + text->implode(', ', $users[$role]) ?> +
  • + + + + + +
  • dt->date($project['start_date']) ?>
  • + + + +
  • dt->date($project['end_date']) ?>
  • + + + +
  • url->icon('share-alt', t('Public link'), 'BoardViewController', 'readonly', array('token' => $project['token']), false, '', '', true) ?>
  • +
  • url->icon('rss-square', t('RSS feed'), 'FeedController', 'project', array('token' => $project['token']), false, '', '', true) ?>
  • +
  • url->icon('calendar', t('iCal feed'), 'ICalendarController', 'project', array('token' => $project['token'])) ?>
  • + +
+
+
+
diff --git a/app/Template/project_overview/show.php b/app/Template/project_overview/show.php new file mode 100644 index 0000000..9f3ab5b --- /dev/null +++ b/app/Template/project_overview/show.php @@ -0,0 +1,40 @@ +
+ projectHeader->render($project, 'ProjectOverviewController', 'show') ?> + +
+ render('project_overview/columns', array('project' => $project, 'columns' => $columns)) ?> +
+ + +
+ + + + + + + +
+ + + + +
+ + +
+
+ render('project_overview/description', array('project' => $project)) ?> +
+
+ render('project_overview/attachments', array('project' => $project, 'images' => $images, 'files' => $files)) ?> +
+
+ render('project_overview/information', array('project' => $project, 'users' => $users, 'roles' => $roles)) ?> +
+
+ render('project_overview/activity', array('project' => $project, 'events' => $events)) ?> +
+
+
+
diff --git a/app/Template/project_permission/groups.php b/app/Template/project_permission/groups.php new file mode 100644 index 0000000..a5fcf66 --- /dev/null +++ b/app/Template/project_permission/groups.php @@ -0,0 +1,59 @@ + + + +
+ + + + + + + + + + + + + + + + +
text->e($group['name']) ?> + app->component('project-select-role', array( + 'roles' => $roles, + 'role' => $group['role'], + 'id' => $group['id'], + 'ariaLabel' => t('Role'), + 'url' => $this->url->to('ProjectPermissionController', 'changeGroupRole', array('project_id' => $project['id'], 'csrf_token' => $this->app->getToken()->getReusableCSRFToken())), + )) ?> + + url->icon('trash-o', t('Remove'), 'ProjectPermissionController', 'removeGroup', array('project_id' => $project['id'], 'group_id' => $group['id']), true) ?> +
+ + + +
+
+ form->csrf() ?> + form->hidden('group_id', $values) ?> + form->hidden('external_id', $values) ?> + + form->label(t('Group Name'), 'name') ?> + form->text('name', $values, $errors, array( + 'required', + 'placeholder="'.t('Enter group name...').'"', + 'title="'.t('Enter group name...').'"', + 'data-dst-field="group_id"', + 'data-dst-extra-fields="external_id"', + 'data-search-url="'.$this->url->href('GroupAjaxController', 'autocomplete').'"', + ), + 'autocomplete') ?> + + form->select('role', $roles, $values, $errors, array('aria-label="'.t('Role').'"')) ?> + + +
+
+ \ No newline at end of file diff --git a/app/Template/project_permission/index.php b/app/Template/project_permission/index.php new file mode 100644 index 0000000..52a69fb --- /dev/null +++ b/app/Template/project_permission/index.php @@ -0,0 +1,19 @@ + + +render('project_permission/users', array( + 'project' => $project, + 'roles' => $roles, + 'users' => $users, + 'errors' => $errors, + 'values' => $values, +)) ?> + +render('project_permission/groups', array( + 'project' => $project, + 'roles' => $roles, + 'groups' => $groups, + 'errors' => $errors, + 'values' => $values, +)) ?> diff --git a/app/Template/project_permission/users.php b/app/Template/project_permission/users.php new file mode 100644 index 0000000..b43dda2 --- /dev/null +++ b/app/Template/project_permission/users.php @@ -0,0 +1,60 @@ + +
+ + + + + + + + + + + + + + + + +
text->e($user['name'] ?: $user['username']) ?> + app->component('project-select-role', array( + 'roles' => $roles, + 'role' => $user['role'], + 'id' => $user['id'], + 'ariaLabel' => t('Role'), + 'url' => $this->url->to('ProjectPermissionController', 'changeUserRole', array('project_id' => $project['id'], 'csrf_token' => $this->app->getToken()->getReusableCSRFToken())), + )) ?> + + user->isCurrentUser($user['id'])): ?> + url->icon('trash-o', t('Remove'), 'ProjectPermissionController', 'removeUser', array('project_id' => $project['id'], 'user_id' => $user['id']), true) ?> + +
+ + + +
+
+ form->csrf() ?> + form->hidden('user_id', $values) ?> + form->hidden('username', $values) ?> + form->hidden('external_id', $values) ?> + form->hidden('external_id_column', $values) ?> + + form->label(t('Name'), 'name') ?> + form->text('name', $values, $errors, array( + 'required', + 'placeholder="'.t('Enter user name...').'"', + 'title="'.t('Enter user name...').'"', + 'data-dst-field="user_id"', + 'data-dst-extra-fields="external_id,external_id_column,username"', + 'data-search-url="'.$this->url->href('UserAjaxController', 'autocomplete').'"', + ), + 'autocomplete') ?> + + form->select('role', $roles, $values, $errors, array('aria-label="'.t('Role').'"')) ?> + + +
+
+ hook->render('template:project-permission:after-adduser', ['project' => $project, 'values' => $values, 'errors' => $errors]) ?> + diff --git a/app/Template/project_predefined_content/show.php b/app/Template/project_predefined_content/show.php new file mode 100644 index 0000000..5bfd072 --- /dev/null +++ b/app/Template/project_predefined_content/show.php @@ -0,0 +1,45 @@ + + + +

+ + + + + + +
+ + text->e($template['title']) ?> + app->tooltipMarkdown($template['description']) ?> +
+ + +
+ form->csrf() ?> + +
+ + form->textarea('predefined_email_subjects', $values, $errors, array('tabindex="1"')) ?> +

+
+ + modal->submitButtons(array('tabindex' => 2)) ?> +
diff --git a/app/Template/project_role/create.php b/app/Template/project_role/create.php new file mode 100644 index 0000000..f554eb1 --- /dev/null +++ b/app/Template/project_role/create.php @@ -0,0 +1,12 @@ + +
+ form->csrf() ?> + form->hidden('project_id', $values) ?> + + form->label(t('Role'), 'role') ?> + form->text('role', $values, $errors, array('autofocus', 'required', 'maxlength="50"')) ?> + + modal->submitButtons() ?> +
diff --git a/app/Template/project_role/edit.php b/app/Template/project_role/edit.php new file mode 100644 index 0000000..740ac0f --- /dev/null +++ b/app/Template/project_role/edit.php @@ -0,0 +1,13 @@ + +
+ form->csrf() ?> + form->hidden('project_id', $values) ?> + form->hidden('role_id', $values) ?> + + form->label(t('Role'), 'role') ?> + form->text('role', $values, $errors, array('autofocus', 'required', 'maxlength="50"')) ?> + + modal->submitButtons() ?> +
diff --git a/app/Template/project_role/remove.php b/app/Template/project_role/remove.php new file mode 100644 index 0000000..44d24ed --- /dev/null +++ b/app/Template/project_role/remove.php @@ -0,0 +1,15 @@ + + +
+

+ +

+ + modal->confirmButtons( + 'ProjectRoleController', + 'remove', + array('project_id' => $project['id'], 'role_id' => $role['role_id']) + ) ?> +
diff --git a/app/Template/project_role/show.php b/app/Template/project_role/show.php new file mode 100644 index 0000000..65c9ef1 --- /dev/null +++ b/app/Template/project_role/show.php @@ -0,0 +1,97 @@ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + text->e($restriction['title']) ?> + + modal->confirm('trash-o', t('Remove'), 'ProjectRoleRestrictionController', 'confirm', array('project_id' => $project['id'], 'restriction_id' => $restriction['restriction_id'])) ?> +
+ + + + + + text->e($restriction['column_title']) ?> + + text->e($restriction['title']) ?> + + modal->confirm('trash-o', t('Remove'), 'ColumnRestrictionController', 'confirm', array('project_id' => $project['id'], 'restriction_id' => $restriction['restriction_id'])) ?> +
+ + text->e($restriction['src_column_title']) ?> / text->e($restriction['dst_column_title']) ?> + + + + + + + + modal->confirm('trash-o', t('Remove'), 'ColumnMoveRestrictionController', 'confirm', array('project_id' => $project['id'], 'restriction_id' => $restriction['restriction_id'])) ?> +
+ + diff --git a/app/Template/project_role_restriction/create.php b/app/Template/project_role_restriction/create.php new file mode 100644 index 0000000..2b6a61d --- /dev/null +++ b/app/Template/project_role_restriction/create.php @@ -0,0 +1,15 @@ +
+ +
+ form->csrf() ?> + form->hidden('project_id', $values) ?> + form->hidden('role_id', $values) ?> + + form->label(t('Restriction'), 'rule') ?> + form->select('rule', $restrictions, $values, $errors) ?> + + modal->submitButtons() ?> +
+
diff --git a/app/Template/project_role_restriction/remove.php b/app/Template/project_role_restriction/remove.php new file mode 100644 index 0000000..1a99419 --- /dev/null +++ b/app/Template/project_role_restriction/remove.php @@ -0,0 +1,15 @@ + + +
+

+ text->in($restriction['rule'], $restrictions)) ?> +

+ + modal->confirmButtons( + 'ProjectRoleRestrictionController', + 'remove', + array('project_id' => $project['id'], 'restriction_id' => $restriction['restriction_id']) + ) ?> +
diff --git a/app/Template/project_status/disable.php b/app/Template/project_status/disable.php new file mode 100644 index 0000000..f2bd762 --- /dev/null +++ b/app/Template/project_status/disable.php @@ -0,0 +1,15 @@ + + +
+

+ +

+ + modal->confirmButtons( + 'ProjectStatusController', + 'disable', + array('project_id' => $project['id']) + ) ?> +
diff --git a/app/Template/project_status/enable.php b/app/Template/project_status/enable.php new file mode 100644 index 0000000..c851899 --- /dev/null +++ b/app/Template/project_status/enable.php @@ -0,0 +1,15 @@ + + +
+

+ +

+ + modal->confirmButtons( + 'ProjectStatusController', + 'enable', + array('project_id' => $project['id']) + ) ?> +
diff --git a/app/Template/project_status/remove.php b/app/Template/project_status/remove.php new file mode 100644 index 0000000..27ae2ae --- /dev/null +++ b/app/Template/project_status/remove.php @@ -0,0 +1,15 @@ + + +
+

+ +

+ + modal->confirmButtons( + 'ProjectStatusController', + 'remove', + array('project_id' => $project['id']) + ) ?> +
diff --git a/app/Template/project_tag/create.php b/app/Template/project_tag/create.php new file mode 100644 index 0000000..61b6e67 --- /dev/null +++ b/app/Template/project_tag/create.php @@ -0,0 +1,14 @@ + +
+ form->csrf() ?> + + form->label(t('Name'), 'name') ?> + form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="191"')) ?> + + form->label(t('Color'), 'color_id') ?> + form->select('color_id', array('' => t('No color')) + $colors, $values, $errors, array(), 'color-picker') ?> + + modal->submitButtons() ?> +
diff --git a/app/Template/project_tag/edit.php b/app/Template/project_tag/edit.php new file mode 100644 index 0000000..0fab036 --- /dev/null +++ b/app/Template/project_tag/edit.php @@ -0,0 +1,14 @@ + +
+ form->csrf() ?> + + form->label(t('Name'), 'name') ?> + form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="191"')) ?> + + form->label(t('Color'), 'color_id') ?> + form->select('color_id', array('' => t('No color')) + $colors, $values, $errors, array(), 'color-picker') ?> + + modal->submitButtons() ?> +
diff --git a/app/Template/project_tag/index.php b/app/Template/project_tag/index.php new file mode 100644 index 0000000..f9373c3 --- /dev/null +++ b/app/Template/project_tag/index.php @@ -0,0 +1,62 @@ + + + +

+ + + + + + + + + + + + +
+ + text->e($tag['name']) ?> + + +
+ text->e($colors[$tag['color_id']]) ?> + +
+ + + + +
+
+ form->csrf() ?> + + form->checkbox('enable_global_tags', t('Enable global tags for this project'), 1, $project['enable_global_tags'] == 1) ?> + + modal->submitButtons() ?> +
+
diff --git a/app/Template/project_tag/make_global.php b/app/Template/project_tag/make_global.php new file mode 100644 index 0000000..13a40c5 --- /dev/null +++ b/app/Template/project_tag/make_global.php @@ -0,0 +1,15 @@ + + +
+

+ +

+ + modal->confirmButtons( + 'ProjectTagController', + 'makeGlobalTag', + array('tag_id' => $tag['id'], 'project_id' => $project['id']) + ) ?> +
diff --git a/app/Template/project_tag/remove.php b/app/Template/project_tag/remove.php new file mode 100644 index 0000000..9f957d1 --- /dev/null +++ b/app/Template/project_tag/remove.php @@ -0,0 +1,15 @@ + + +
+

+ +

+ + modal->confirmButtons( + 'ProjectTagController', + 'remove', + array('tag_id' => $tag['id'], 'project_id' => $project['id']) + ) ?> +
diff --git a/app/Template/project_user_overview/layout.php b/app/Template/project_user_overview/layout.php new file mode 100644 index 0000000..5a1b312 --- /dev/null +++ b/app/Template/project_user_overview/layout.php @@ -0,0 +1,29 @@ +
+ + +
diff --git a/app/Template/project_user_overview/roles.php b/app/Template/project_user_overview/roles.php new file mode 100644 index 0000000..1d33840 --- /dev/null +++ b/app/Template/project_user_overview/roles.php @@ -0,0 +1,32 @@ +isEmpty()): ?> +

+ + + + + + + + getCollection() as $project): ?> + + + + + + +
order(t('User'), 'users.username') ?>order(t('Project'), 'projects.name') ?>
+ text->e($this->user->getFullname($project)) ?> + + url->link('', 'BoardViewController', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link', t('Board')) ?> + url->link('', 'ProjectViewController', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link', t('Project settings')) ?> + + text->e($project['project_name']) ?> + + + + text->e($column['title']) ?> + +
+ + + diff --git a/app/Template/project_user_overview/sidebar.php b/app/Template/project_user_overview/sidebar.php new file mode 100644 index 0000000..1fee5b7 --- /dev/null +++ b/app/Template/project_user_overview/sidebar.php @@ -0,0 +1,32 @@ + diff --git a/app/Template/project_user_overview/tasks.php b/app/Template/project_user_overview/tasks.php new file mode 100644 index 0000000..6f75134 --- /dev/null +++ b/app/Template/project_user_overview/tasks.php @@ -0,0 +1,46 @@ +isEmpty()): ?> +

+isEmpty()): ?> + + + + + + + + + + + getCollection() as $task): ?> + + + + + + + + + + +
order(t('Id'), 'tasks.id') ?>order(t('Project'), 'projects.name') ?>order(t('Column'), 'tasks.column_id') ?>order(t('Title'), 'tasks.title') ?>order(t('Assignee'), 'users.username') ?>order(t('Start date'), 'tasks.date_started') ?>order(t('Due date'), 'tasks.date_due') ?>
+ url->link('#'.$this->text->e($task['id']), 'TaskViewController', 'show', array('task_id' => $task['id']), false, '', t('View this task')) ?> + + url->link($this->text->e($task['project_name']), 'BoardViewController', 'show', array('project_id' => $task['project_id'])) ?> + + text->e($task['column_name']) ?> + + url->link($this->text->e($task['title']), 'TaskViewController', 'show', array('task_id' => $task['id']), false, '', t('View this task')) ?> + + + text->e($task['assignee_name'] ?: $task['assignee_username']) ?> + + + + + dt->date($task['date_started']) ?> + + dt->datetime($task['date_due']) ?> +
+ + + diff --git a/app/Template/project_user_overview/tooltip_users.php b/app/Template/project_user_overview/tooltip_users.php new file mode 100644 index 0000000..8bc821e --- /dev/null +++ b/app/Template/project_user_overview/tooltip_users.php @@ -0,0 +1,16 @@ + +

+ + + $role_name): ?> + + + $user): ?> + + + + +
text->e($role_name) ?>
+ url->link($this->text->e($user), 'ProjectUserOverviewController', 'opens', array('user_id' => $user_id)) ?> +
+ diff --git a/app/Template/project_view/duplicate.php b/app/Template/project_view/duplicate.php new file mode 100644 index 0000000..f0268f6 --- /dev/null +++ b/app/Template/project_view/duplicate.php @@ -0,0 +1,30 @@ + + +
+

+ +

+
+ + form->csrf() ?> + + + form->checkbox('projectPermissionModel', t('Permissions'), 1, true) ?> + form->checkbox('projectRoleModel', t('Custom roles'), 1, true) ?> + + + form->checkbox('categoryModel', t('Categories'), 1, true) ?> + form->checkbox('tagDuplicationModel', t('Tags'), 1, true) ?> + form->checkbox('actionModel', t('Actions'), 1, true) ?> + form->checkbox('customFilterModel', t('Custom filters'), 1, true) ?> + form->checkbox('projectMetadataModel', t('Metadata'), 1, false) ?> + form->checkbox('projectTaskDuplicationModel', t('Tasks'), 1, false) ?> + +
+ + url->link(t('cancel'), 'ProjectViewController', 'show', array('project_id' => $project['id'])) ?> +
+
+
diff --git a/app/Template/project_view/importTasks.php b/app/Template/project_view/importTasks.php new file mode 100644 index 0000000..d66011b --- /dev/null +++ b/app/Template/project_view/importTasks.php @@ -0,0 +1,15 @@ + + 0): ?> +
+ form->csrf() ?> + + form->label(t('Select the project to copy tasks from'), 'src_project_id') ?> + form->select('src_project_id', $projects, $values, $errors) ?> + + modal->submitButtons(['submitLabel' => t('Save')]) ?> +
+ +

+ diff --git a/app/Template/project_view/integrations.php b/app/Template/project_view/integrations.php new file mode 100644 index 0000000..f8bff7e --- /dev/null +++ b/app/Template/project_view/integrations.php @@ -0,0 +1,15 @@ + + +
+ form->csrf() ?> + + hook->render('template:project:integrations', array('project' => $project, 'values' => $values, 'webhook_token' => $webhook_token)) ?> + + +

+ + + +
diff --git a/app/Template/project_view/notifications.php b/app/Template/project_view/notifications.php new file mode 100644 index 0000000..29cc088 --- /dev/null +++ b/app/Template/project_view/notifications.php @@ -0,0 +1,20 @@ + + +

+ +
+ + form->csrf() ?> + +

+ form->checkboxes('notification_types', $types, $notifications) ?> + +
+ + + url->link(t('cancel'), 'ProjectViewController', 'show', array('project_id' => $project['id'])) ?> +
+
+ diff --git a/app/Template/project_view/share.php b/app/Template/project_view/share.php new file mode 100644 index 0000000..ab2515c --- /dev/null +++ b/app/Template/project_view/share.php @@ -0,0 +1,20 @@ + + + + + +
+
    +
  • url->icon('share-alt', t('Public link'), 'BoardViewController', 'readonly', array('token' => $project['token']), false, '', '', true) ?>
  • +
  • url->icon('rss-square', t('RSS feed'), 'FeedController', 'project', array('token' => $project['token']), false, '', '', true) ?>
  • +
  • url->icon('calendar', t('iCal feed'), 'ICalendarController', 'project', array('token' => $project['token']), false, '', '', true) ?>
  • +
+
+ + url->link(t('Disable public access'), 'ProjectViewController', 'updateSharing', array('project_id' => $project['id'], 'switch' => 'disable'), true, 'btn btn-red') ?> + +

+ url->link(t('Enable public access'), 'ProjectViewController', 'updateSharing', array('project_id' => $project['id'], 'switch' => 'enable'), true, 'btn btn-blue') ?> + diff --git a/app/Template/project_view/show.php b/app/Template/project_view/show.php new file mode 100644 index 0000000..c671879 --- /dev/null +++ b/app/Template/project_view/show.php @@ -0,0 +1,98 @@ + +
    +
  • + + 0): ?> +
  • text->e($project['owner_name'] ?: $project['owner_username']) ?>
  • + + + +
  • + + + +
  • url->icon('share-alt', t('Public link'), 'BoardViewController', 'readonly', array('token' => $project['token']), false, '', '', true) ?>
  • +
  • url->icon('rss-square', t('RSS feed'), 'FeedController', 'project', array('token' => $project['token']), false, '', '', true) ?>
  • +
  • url->icon('calendar', t('iCal feed'), 'ICalendarController', 'project', array('token' => $project['token'])) ?>
  • + +
  • + + + +
  • dt->datetime($project['last_modified']) ?>
  • + + + +
  • dt->date($project['start_date']) ?>
  • + + + +
  • dt->date($project['end_date']) ?>
  • + + + +
  • + +
  • + + +
  • +
+ + + + + hook->render('template:project:view:form', array('values' => $values, 'errors' => $errors)) ?> + + +
+ text->markdown($project['description']) ?> +
+ + + + +

+ + + + + + + + + + + + + + + + + + + + + + +
+ text->e($column['title']) ?> + + app->tooltipMarkdown($column['description']) ?> + + + + + + + + + +
+ diff --git a/app/Template/search/activity.php b/app/Template/search/activity.php new file mode 100644 index 0000000..5d8f142 --- /dev/null +++ b/app/Template/search/activity.php @@ -0,0 +1,40 @@ + + +
+ +
+ + +
+

+

project:"My project" creator:me

+
    +
  • project:"My project"
  • +
  • creator:admin
  • +
  • created:today
  • +
  • status:open
  • +
  • title:"My task"
  • +
+

url->doc(t('View advanced search syntax'), 'search') ?>

+
+ +

+ + render('event/events', array('events' => $events)) ?> + diff --git a/app/Template/search/index.php b/app/Template/search/index.php new file mode 100644 index 0000000..3e8d39b --- /dev/null +++ b/app/Template/search/index.php @@ -0,0 +1,44 @@ + + +
+ +
+ + +
+

+

project:"My project" assignee:me due:tomorrow

+
    +
  • project:"My project"
  • +
  • column:"Work in progress"
  • +
  • assignee:nobody
  • +
  • color:Blue
  • +
  • category:"Feature Request"
  • +
  • description:"Something to find"
  • +
  • due:2015-07-01
  • +
+

url->doc(t('View advanced search syntax'), 'search') ?>

+
+isEmpty()): ?> +

+isEmpty()): ?> + render('search/results', array( + 'paginator' => $paginator, + )) ?> + diff --git a/app/Template/search/results.php b/app/Template/search/results.php new file mode 100644 index 0000000..85b09ea --- /dev/null +++ b/app/Template/search/results.php @@ -0,0 +1,33 @@ +
+ render('task_list/header', array( + 'paginator' => $paginator, + )) ?> + + getCollection() as $task): ?> +
+ render('task_list/task_title', array( + 'task' => $task, + )) ?> + + render('task_list/task_details', array( + 'task' => $task, + )) ?> + + render('task_list/task_avatars', array( + 'task' => $task, + )) ?> + + render('task_list/task_icons', array( + 'task' => $task, + )) ?> + + render('task_list/task_subtasks', array( + 'task' => $task, + )) ?> + + hook->render('template:search:task:footer', array('task' => $task)) ?> +
+ +
+ + \ No newline at end of file diff --git a/app/Template/subtask/create.php b/app/Template/subtask/create.php new file mode 100644 index 0000000..6919347 --- /dev/null +++ b/app/Template/subtask/create.php @@ -0,0 +1,27 @@ + + + 0): ?> +

+ + + + + +

+ + +
+ form->csrf() ?> + + subtask->renderBulkTitleField($values, $errors, array('autofocus')) ?> + subtask->renderAssigneeField($users_list, $values, $errors) ?> + subtask->renderTimeEstimatedField($values, $errors) ?> + + hook->render('template:subtask:form:create', array('values' => $values, 'errors' => $errors)) ?> + + form->checkbox('another_subtask', t('Create another sub-task'), 1, isset($values['another_subtask']) && $values['another_subtask'] == 1) ?> + + modal->submitButtons() ?> +
diff --git a/app/Template/subtask/edit.php b/app/Template/subtask/edit.php new file mode 100644 index 0000000..90aaca8 --- /dev/null +++ b/app/Template/subtask/edit.php @@ -0,0 +1,16 @@ + + +
+ form->csrf() ?> + + subtask->renderTitleField($values, $errors, array('autofocus')) ?> + subtask->renderAssigneeField($users_list, $values, $errors) ?> + subtask->renderTimeEstimatedField($values, $errors) ?> + subtask->renderTimeSpentField($values, $errors) ?> + + hook->render('template:subtask:form:edit', array('values' => $values, 'errors' => $errors)) ?> + + modal->submitButtons() ?> +
diff --git a/app/Template/subtask/menu.php b/app/Template/subtask/menu.php new file mode 100644 index 0000000..30d28c7 --- /dev/null +++ b/app/Template/subtask/menu.php @@ -0,0 +1,16 @@ + diff --git a/app/Template/subtask/remove.php b/app/Template/subtask/remove.php new file mode 100644 index 0000000..3db814e --- /dev/null +++ b/app/Template/subtask/remove.php @@ -0,0 +1,20 @@ + + +
+
+ +
    +
  • + text->e($subtask['title']) ?> +
  • +
+
+ + modal->confirmButtons( + 'SubtaskController', + 'remove', + array('task_id' => $task['id'], 'subtask_id' => $subtask['id']) + ) ?> +
diff --git a/app/Template/subtask/show.php b/app/Template/subtask/show.php new file mode 100644 index 0000000..e5d3860 --- /dev/null +++ b/app/Template/subtask/show.php @@ -0,0 +1,10 @@ +
> + +
+ render('subtask/table', array( + 'subtasks' => $subtasks, + 'task' => $task, + 'editable' => $editable + )) ?> +
+
diff --git a/app/Template/subtask/table.php b/app/Template/subtask/table.php new file mode 100644 index 0000000..8b9e040 --- /dev/null +++ b/app/Template/subtask/table.php @@ -0,0 +1,47 @@ + + + + + + + hook->render('template:subtask:table:header:before-timetracking') ?> + + + + + + + + + hook->render('template:subtask:table:rows', array('subtask' => $subtask)) ?> + + + + +
+
+ +   + render('subtask/menu', array( + 'task' => $task, + 'subtask' => $subtask, + )) ?> + subtask->renderToggleStatus($task, $subtask, 'table') ?> + + subtask->renderTitle($subtask) ?> + +
+
+ + text->e($subtask['name'] ?: $subtask['username']) ?> + + + render('subtask/timer', array( + 'task' => $task, + 'subtask' => $subtask, + )) ?> +
+ diff --git a/app/Template/subtask/timer.php b/app/Template/subtask/timer.php new file mode 100644 index 0000000..0f17fc7 --- /dev/null +++ b/app/Template/subtask/timer.php @@ -0,0 +1,15 @@ + + + + + + / + + + + + + user->hasProjectAccess('SubtaskController', 'edit', $task['project_id']) && $subtask['user_id'] == $this->user->getId()): ?> + subtask->renderTimer($task, $subtask) ?> + + diff --git a/app/Template/subtask_converter/show.php b/app/Template/subtask_converter/show.php new file mode 100644 index 0000000..12e1b1b --- /dev/null +++ b/app/Template/subtask_converter/show.php @@ -0,0 +1,20 @@ + + +
+
+ +
    +
  • + text->e($subtask['title']) ?> +
  • +
+
+ + modal->confirmButtons( + 'SubtaskConverterController', + 'save', + array('task_id' => $task['id'], 'subtask_id' => $subtask['id']) + ) ?> +
diff --git a/app/Template/subtask_restriction/show.php b/app/Template/subtask_restriction/show.php new file mode 100644 index 0000000..e431b86 --- /dev/null +++ b/app/Template/subtask_restriction/show.php @@ -0,0 +1,16 @@ + +
+ form->csrf() ?> + + +

+ +

+ form->radios('status', $status_list) ?> + form->hidden('id', $subtask_inprogress) ?> + + + modal->submitButtons() ?> +
diff --git a/app/Template/swimlane/create.php b/app/Template/swimlane/create.php new file mode 100644 index 0000000..8ecfa52 --- /dev/null +++ b/app/Template/swimlane/create.php @@ -0,0 +1,17 @@ + +
+ form->csrf() ?> + + form->label(t('Name'), 'name') ?> + form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="191"', 'tabindex="1"')) ?> + + form->label(t('Description'), 'description') ?> + form->textEditor('description', $values, $errors, array('tabindex' => 2)) ?> + + form->label(t('Task limit'), 'task_limit') ?> + form->number('task_limit', $values, $errors, array('tabindex' => 3, 'min="0"')) ?> + + modal->submitButtons() ?> +
diff --git a/app/Template/swimlane/edit.php b/app/Template/swimlane/edit.php new file mode 100644 index 0000000..046407a --- /dev/null +++ b/app/Template/swimlane/edit.php @@ -0,0 +1,18 @@ + + +
+ form->csrf() ?> + + form->label(t('Name'), 'name') ?> + form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="191"', 'tabindex="1"')) ?> + + form->label(t('Description'), 'description') ?> + form->textEditor('description', $values, $errors, array('tabindex' => 2)) ?> + + form->label(t('Task limit'), 'task_limit') ?> + form->number('task_limit', $values, $errors, array('tabindex' => 3, 'min="0"')) ?> + + modal->submitButtons() ?> +
diff --git a/app/Template/swimlane/index.php b/app/Template/swimlane/index.php new file mode 100644 index 0000000..59133db --- /dev/null +++ b/app/Template/swimlane/index.php @@ -0,0 +1,28 @@ + + +

+ + +

+ + render('swimlane/table', array( + 'swimlanes' => $active_swimlanes, + 'project' => $project, + )) ?> + + + +

+ render('swimlane/table', array( + 'swimlanes' => $inactive_swimlanes, + 'project' => $project, + 'disable_handle' => true, + )) ?> + diff --git a/app/Template/swimlane/remove.php b/app/Template/swimlane/remove.php new file mode 100644 index 0000000..02d1e32 --- /dev/null +++ b/app/Template/swimlane/remove.php @@ -0,0 +1,15 @@ + + +
+

+ +

+ + modal->confirmButtons( + 'SwimlaneController', + 'remove', + array('project_id' => $project['id'], 'swimlane_id' => $swimlane['id']) + ) ?> +
diff --git a/app/Template/swimlane/table.php b/app/Template/swimlane/table.php new file mode 100644 index 0000000..edbf25c --- /dev/null +++ b/app/Template/swimlane/table.php @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + +
+ +   + + + + + text->e($swimlane['name']) ?> + + + app->tooltipMarkdown($swimlane['description']) ?> + + + 0 ? $swimlane['task_limit'] : '∞' ?> + + + + +
diff --git a/app/Template/tag/create.php b/app/Template/tag/create.php new file mode 100644 index 0000000..f080f8d --- /dev/null +++ b/app/Template/tag/create.php @@ -0,0 +1,15 @@ + +
+ form->csrf() ?> + form->hidden('project_id', $values) ?> + + form->label(t('Name'), 'name') ?> + form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="191"')) ?> + + form->label(t('Color'), 'color_id') ?> + form->select('color_id', array('' => t('No color')) + $colors, $values, $errors, array(), 'color-picker') ?> + + modal->submitButtons() ?> +
diff --git a/app/Template/tag/edit.php b/app/Template/tag/edit.php new file mode 100644 index 0000000..93ce0c9 --- /dev/null +++ b/app/Template/tag/edit.php @@ -0,0 +1,16 @@ + +
+ form->csrf() ?> + form->hidden('id', $values) ?> + form->hidden('project_id', $values) ?> + + form->label(t('Name'), 'name') ?> + form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="191"')) ?> + + form->label(t('Color'), 'color_id') ?> + form->select('color_id', array('' => t('No color')) + $colors, $values, $errors, array(), 'color-picker') ?> + + modal->submitButtons() ?> +
diff --git a/app/Template/tag/index.php b/app/Template/tag/index.php new file mode 100644 index 0000000..2022438 --- /dev/null +++ b/app/Template/tag/index.php @@ -0,0 +1,35 @@ + + + +

+ + + + + + + + + + + + + + +
text->e($tag['name']) ?> + +
+ text->e($colors[$tag['color_id']]) ?> + +
+ modal->medium('edit', t('Edit'), 'TagController', 'edit', array('tag_id' => $tag['id'])) ?> + modal->confirm('trash-o', t('Remove'), 'TagController', 'confirm', array('tag_id' => $tag['id'])) ?> +
+ diff --git a/app/Template/tag/remove.php b/app/Template/tag/remove.php new file mode 100644 index 0000000..47ba8d3 --- /dev/null +++ b/app/Template/tag/remove.php @@ -0,0 +1,15 @@ + + +
+

+ +

+ + modal->confirmButtons( + 'TagController', + 'remove', + array('tag_id' => $tag['id']) + ) ?> +
diff --git a/app/Template/task/analytics.php b/app/Template/task/analytics.php new file mode 100644 index 0000000..ec82143 --- /dev/null +++ b/app/Template/task/analytics.php @@ -0,0 +1,45 @@ +render('task/details', array( + 'task' => $task, + 'tags' => $tags, + 'project' => $project, + 'editable' => false, +)) ?> + + + +
+
    +
  • '.$this->dt->duration($lead_time) ?>
  • +
  • '.$this->dt->duration($cycle_time) ?>
  • +
+
+ +

+ +app->component('chart-task-time-column', array( + 'metrics' => $time_spent_columns, + 'label' => t('Time spent'), +)) ?> + + + + + + + + + + + + +
text->e($column['title']) ?>dt->duration($column['time_spent']) ?>
+ +
+
    +
  • +
  • +
  • +
+
diff --git a/app/Template/task/changes.php b/app/Template/task/changes.php new file mode 100644 index 0000000..a228765 --- /dev/null +++ b/app/Template/task/changes.php @@ -0,0 +1,78 @@ + +
    + $value) { + switch ($field) { + case 'title': + echo '
  • '.t('New title: %s', $task['title']).'
  • '; + break; + case 'owner_id': + if (empty($task['owner_id'])) { + echo '
  • '.t('The task is not assigned anymore').'
  • '; + } else { + echo '
  • '.t('New assignee: %s', $task['assignee_name'] ?: $task['assignee_username']).'
  • '; + } + break; + case 'category_id': + if (empty($task['category_id'])) { + echo '
  • '.t('There is no category now').'
  • '; + } else { + echo '
  • '.t('New category: %s', $task['category_name']).'
  • '; + } + break; + case 'color_id': + echo '
  • '.t('New color: %s', $this->text->in($task['color_id'], $this->task->getColors())).'
  • '; + break; + case 'score': + echo '
  • '.t('New complexity: %d', $task['score']).'
  • '; + break; + case 'date_due': + if (empty($task['date_due'])) { + echo '
  • '.t('The due date has been removed').'
  • '; + } else { + echo '
  • '.t('New due date: ').$this->dt->datetime($task['date_due']).'
  • '; + } + break; + case 'description': + if (empty($task['description'])) { + echo '
  • '.t('There is no description anymore').'
  • '; + } + break; + case 'recurrence_status': + case 'recurrence_trigger': + case 'recurrence_factor': + case 'recurrence_timeframe': + case 'recurrence_basedate': + case 'recurrence_parent': + case 'recurrence_child': + echo '
  • '.t('Recurrence settings has been modified').'
  • '; + break; + case 'time_spent': + echo '
  • '.t('Time spent changed: %sh', $task['time_spent']).'
  • '; + break; + case 'time_estimated': + echo '
  • '.t('Time estimated changed: %sh', $task['time_estimated']).'
  • '; + break; + case 'date_started': + if ($value != 0) { + echo '
  • '.t('Start date changed: ').$this->dt->datetime($task['date_started']).'
  • '; + } + break; + default: + echo '
  • '.t('The field "%s" has been updated', $field).'
  • '; + } + } + + ?> +
+ + +

+ +
text->markdown($task['description'], true) ?>
+ +
text->markdown($task['description']) ?>
+ + + \ No newline at end of file diff --git a/app/Template/task/description.php b/app/Template/task/description.php new file mode 100644 index 0000000..241b837 --- /dev/null +++ b/app/Template/task/description.php @@ -0,0 +1,8 @@ +
> + +
+
+ text->markdown($task['description'], isset($is_public) && $is_public) ?> +
+
+
diff --git a/app/Template/task/details.php b/app/Template/task/details.php new file mode 100644 index 0000000..d659ef4 --- /dev/null +++ b/app/Template/task/details.php @@ -0,0 +1,173 @@ +
+

text->e($task['title']) ?>

+ + hook->render('template:task:details:top', array('task' => $task)) ?> + +
+
+
+
    +
  • + + + + + + + + +
  • +
  • + +
  • + +
  • + task->renderReference($task) ?> +
  • + + +
  • + text->e($task['score']) ?> +
  • + + +
  • + + url->icon('external-link', t('Public link'), 'TaskViewController', 'readonly', array('task_id' => $task['id'], 'token' => $project['token']), false, '', '', true) ?> + +
  • + + +
  • + + url->icon('th', t('Back to the board'), 'BoardViewController', 'readonly', array('token' => $project['token'])) ?> + +
  • + + + hook->render('template:task:details:first-column', array('task' => $task)) ?> +
+
+
+
    + +
  • + + text->e($task['category_name']) ?> +
  • + + +
  • + + text->e($task['swimlane_name']) ?> +
  • + +
  • + + text->e($task['column_title']) ?> +
  • +
  • + + +
  • + + hook->render('template:task:details:second-column', array('task' => $task)) ?> +
+
+
+
    +
  • + + + + text->e($task['assignee_name'] ?: $task['assignee_username']) ?> + + + + + user->getId()): ?> + - url->link(t('Assign to me'), 'TaskModificationController', 'assignToMe', ['task_id' => $task['id'], 'csrf_token' => $this->app->getToken()->getReusableCSRFToken()]) ?> + +
  • + +
  • + + text->e($task['creator_name'] ?: $task['creator_username']) ?> +
  • + + +
  • + + +
  • + + +
  • + + +
  • + + + hook->render('template:task:details:third-column', array('task' => $task)) ?> +
+
+
+
    + +
  • + + dt->datetime($task['date_due']) ?> +
  • + +
  • + + + dt->datetime($task['date_started']) ?> + + url->link(t('Start now'), 'TaskModificationController', 'start', ['task_id' => $task['id'], 'csrf_token' => $this->app->getToken()->getReusableCSRFToken()]) ?> + +
  • +
  • + + dt->datetime($task['date_creation']) ?> +
  • +
  • + + dt->datetime($task['date_modification']) ?> +
  • + +
  • + + dt->datetime($task['date_completed']) ?> +
  • + + +
  • + + dt->datetime($task['date_moved']) ?> +
  • + + + hook->render('template:task:details:fourth-column', array('task' => $task)) ?> +
+
+
+ +
+
    + +
  • ">text->e($tag['name']) ?>
  • + +
+
+ +
+ + + app->component('external-task-view', array( + 'url' => $this->url->href('ExternalTaskViewController', 'show', array('task_id' => $task['id'])), + )) ?> + + + hook->render('template:task:details:bottom', array('task' => $task)) ?> +
diff --git a/app/Template/task/dropdown.php b/app/Template/task/dropdown.php new file mode 100644 index 0000000..e73637b --- /dev/null +++ b/app/Template/task/dropdown.php @@ -0,0 +1,80 @@ + diff --git a/app/Template/task/layout.php b/app/Template/task/layout.php new file mode 100644 index 0000000..19f7791 --- /dev/null +++ b/app/Template/task/layout.php @@ -0,0 +1,17 @@ +
+ projectHeader->render($project, 'TaskListController', 'show') ?> + hook->render('template:task:layout:top', array('task' => $task)) ?> + +
diff --git a/app/Template/task/public.php b/app/Template/task/public.php new file mode 100644 index 0000000..eb3b9f1 --- /dev/null +++ b/app/Template/task/public.php @@ -0,0 +1,36 @@ +
+ render('task/details', array( + 'task' => $task, + 'tags' => $tags, + 'project' => $project, + 'editable' => false, + )) ?> + + render('task/description', array( + 'task' => $task, + 'project' => $project, + 'is_public' => true, + )) ?> + + render('subtask/show', array( + 'task' => $task, + 'subtasks' => $subtasks, + 'editable' => false + )) ?> + + render('task_internal_link/show', array( + 'task' => $task, + 'links' => $links, + 'project' => $project, + 'editable' => false, + 'is_public' => true, + )) ?> + + render('task_comments/show', array( + 'task' => $task, + 'comments' => $comments, + 'project' => $project, + 'editable' => false, + 'is_public' => true, + )) ?> +
diff --git a/app/Template/task/show.php b/app/Template/task/show.php new file mode 100644 index 0000000..659d4ca --- /dev/null +++ b/app/Template/task/show.php @@ -0,0 +1,53 @@ +hook->render('template:task:show:top', array('task' => $task, 'project' => $project)) ?> + +render('task/details', array( + 'task' => $task, + 'tags' => $tags, + 'project' => $project, + 'editable' => $this->user->hasProjectAccess('TaskModificationController', 'edit', $project['id']), +)) ?> + +hook->render('template:task:show:before-description', array('task' => $task, 'project' => $project)) ?> +render('task/description', array('task' => $task)) ?> + +hook->render('template:task:show:before-subtasks', array('task' => $task, 'project' => $project)) ?> +render('subtask/show', array( + 'task' => $task, + 'subtasks' => $subtasks, + 'project' => $project, + 'editable' => $this->user->hasProjectAccess('SubtaskController', 'edit', $project['id']), +)) ?> + +hook->render('template:task:show:before-internal-links', array('task' => $task, 'project' => $project)) ?> +render('task_internal_link/show', array( + 'task' => $task, + 'links' => $internal_links, + 'project' => $project, + 'link_label_list' => $link_label_list, + 'editable' => $this->user->hasProjectAccess('TaskInternalLinkController', 'edit', $project['id']), + 'is_public' => false, +)) ?> + +hook->render('template:task:show:before-external-links', array('task' => $task, 'project' => $project)) ?> +render('task_external_link/show', array( + 'task' => $task, + 'links' => $external_links, + 'project' => $project, +)) ?> + +hook->render('template:task:show:before-attachments', array('task' => $task, 'project' => $project)) ?> +render('task_file/show', array( + 'task' => $task, + 'files' => $files, + 'images' => $images +)) ?> + +hook->render('template:task:show:before-comments', array('task' => $task, 'project' => $project)) ?> +render('task_comments/show', array( + 'task' => $task, + 'comments' => $comments, + 'project' => $project, + 'editable' => $this->user->hasProjectAccess('CommentController', 'edit', $project['id']), +)) ?> + +hook->render('template:task:show:bottom', array('task' => $task, 'project' => $project)) ?> diff --git a/app/Template/task/sidebar.php b/app/Template/task/sidebar.php new file mode 100644 index 0000000..6bf1db2 --- /dev/null +++ b/app/Template/task/sidebar.php @@ -0,0 +1,109 @@ + diff --git a/app/Template/task/time_tracking_details.php b/app/Template/task/time_tracking_details.php new file mode 100644 index 0000000..7cb419e --- /dev/null +++ b/app/Template/task/time_tracking_details.php @@ -0,0 +1,34 @@ +render('task/details', array( + 'task' => $task, + 'tags' => $tags, + 'project' => $project, + 'editable' => false, +)) ?> + +render('task/time_tracking_summary', array('task' => $task)) ?> + +

+isEmpty()): ?> +

+ + + + + + + + + + getCollection() as $record): ?> + + + + + + + + +
order(t('User'), 'username') ?>order(t('Subtask'), 'subtask_title') ?>order(t('Start'), 'start') ?>order(t('End'), 'end') ?>order(t('Time spent'), \Kanboard\Model\SubtaskTimeTrackingModel::TABLE.'.time_spent') ?>
url->link($this->text->e($record['user_fullname'] ?: $record['username']), 'UserViewController', 'show', array('user_id' => $record['user_id'])) ?>dt->datetime($record['start']) ?>dt->datetime($record['end']) ?>
+ + + diff --git a/app/Template/task/time_tracking_summary.php b/app/Template/task/time_tracking_summary.php new file mode 100644 index 0000000..63de733 --- /dev/null +++ b/app/Template/task/time_tracking_summary.php @@ -0,0 +1,13 @@ + 0 || $task['time_spent'] > 0): ?> + + +
+
    +
  • text->e($task['time_estimated']) ?>
  • +
  • text->e($task['time_spent']) ?>
  • +
  • text->e($task['time_estimated'] - $task['time_spent']) ?>
  • +
+
+ \ No newline at end of file diff --git a/app/Template/task/transitions.php b/app/Template/task/transitions.php new file mode 100644 index 0000000..4a9f22c --- /dev/null +++ b/app/Template/task/transitions.php @@ -0,0 +1,33 @@ +render('task/details', array( + 'task' => $task, + 'tags' => $tags, + 'project' => $project, + 'editable' => false, +)) ?> + + + + +

+ + + + + + + + + + + + + + + + + + +
dt->datetime($transition['date']) ?>text->e($transition['src_column']) ?>text->e($transition['dst_column']) ?>url->link($this->text->e($transition['name'] ?: $transition['username']), 'UserViewController', 'show', array('user_id' => $transition['user_id'])) ?>dt->duration($transition['time_spent']) ?>
+ diff --git a/app/Template/task_bulk/show.php b/app/Template/task_bulk/show.php new file mode 100644 index 0000000..a52208f --- /dev/null +++ b/app/Template/task_bulk/show.php @@ -0,0 +1,30 @@ + + +
+ form->csrf() ?> + form->hidden('column_id', $values) ?> + form->hidden('swimlane_id', $values) ?> + + + form->label(t('Template for the task description'), 'task_description_template_id') ?> + form->select('task_description_template_id', $task_description_templates, $values, $errors) ?> + + + form->label(t('Tasks'), 'tasks') ?> + form->textarea('tasks', $values, $errors, array('placeholder="'.t('My task title').'"')) ?> +

+ + task->renderColorField($values) ?> + task->renderAssigneeField($users_list, $values, $errors) ?> + task->renderPriorityField($project, $values) ?> + task->renderDueDateField($values, $errors) ?> + task->renderTimeEstimatedField($values, $errors) ?> + task->renderScoreField($values, $errors) ?> + task->renderCategoryField($categories_list, $values, $errors) ?> + task->renderTagField($project) ?> + + modal->submitButtons() ?> +
+ diff --git a/app/Template/task_bulk_change_property/show.php b/app/Template/task_bulk_change_property/show.php new file mode 100644 index 0000000..f8c0a8f --- /dev/null +++ b/app/Template/task_bulk_change_property/show.php @@ -0,0 +1,126 @@ + + +
+ form->csrf() ?> + form->hidden('task_ids', $values) ?> + +

+ +
+
+ +
+
+ task->renderColorField($values) ?> +
+
+ +
+
+ +
+
+ task->renderAssigneeField($users_list, $values, $errors) ?> +
+
+ +
+
+ +
+
+ task->renderPriorityField($project, $values) ?> +
+
+ +
+
+ +
+
+ task->renderCategoryField($categories_list, $values, $errors, [], true) ?> +
+
+ +
+
+ +
+
+ task->renderTagField($project) ?> + + + + +
+
+ +
+
+ +
+
+ task->renderDueDateField($values, $errors) ?> +
+
+ +
+
+ +
+
+ task->renderStartDateField($values, $errors) ?> +
+
+ +
+
+ +
+
+ task->renderTimeEstimatedField($values, $errors) ?> +
+
+ +
+
+ +
+
+ task->renderTimeSpentField($values, $errors) ?> +
+
+ +
+
+ +
+
+ task->renderScoreField($values, $errors) ?> +
+
+ +
+
+ +
+
+ task->renderInternalLinkField($internallink_list, $values, $errors) ?> +
+
+ +
+
+ +
+
+ + + +
+
+ + modal->submitButtons() ?> +
diff --git a/app/Template/task_bulk_move_column/show.php b/app/Template/task_bulk_move_column/show.php new file mode 100644 index 0000000..beba207 --- /dev/null +++ b/app/Template/task_bulk_move_column/show.php @@ -0,0 +1,16 @@ + + +
+ form->csrf() ?> + form->hidden('task_ids', $values) ?> + + form->label(t('Swimlane'), 'swimlane_id') ?> + form->select('swimlane_id', $swimlanes, $values) ?> + + form->label(t('Column'), 'column_id') ?> + form->select('column_id', $columns, $values) ?> + + modal->submitButtons() ?> +
diff --git a/app/Template/task_comments/create.php b/app/Template/task_comments/create.php new file mode 100644 index 0000000..ebf6075 --- /dev/null +++ b/app/Template/task_comments/create.php @@ -0,0 +1,30 @@ + +
+ form->csrf() ?> + form->hidden('task_id', $values) ?> + form->hidden('user_id', $values) ?> + + form->textEditor('comment', $values, $errors, array('required' => true, 'aria-label' => t('New comment'))) ?> + + + + user->getRole() !== Role::APP_USER) { + echo $this->form->label(t('Visibility:'), $formName); + $attribute = []; + $visibilityOptions['app-user'] = t('Standard users'); + $visibilityOptions['app-manager'] = t('Application managers or more'); + } + ?> + + user->getRole() === Role::APP_ADMIN) { + $visibilityOptions['app-admin'] = t('Administrators'); + } + ?> + + form->select($formName, $visibilityOptions, array(), array(), $attribute) ?> + modal->submitButtons() ?> +
diff --git a/app/Template/task_comments/show.php b/app/Template/task_comments/show.php new file mode 100644 index 0000000..d3704f9 --- /dev/null +++ b/app/Template/task_comments/show.php @@ -0,0 +1,39 @@ +
> + +
+ +
+ + + modal->medium('comment', t('Add a comment'), 'CommentController', 'create', array('task_id' => $task['id'])) ?> + + url->icon('sort', t('Change sorting'), 'CommentController', 'toggleSorting', array('task_id' => $task['id'], 'csrf_token' => $this->app->getToken()->getReusableCSRFToken())) ?> + + modal->medium('paper-plane', t('Send by email'), 'CommentMailController', 'create', array('task_id' => $task['id'])) ?> + + +
+ + + render('comment/show', array( + 'comment' => $comment, + 'task' => $task, + 'project' => $project, + 'editable' => $editable, + 'is_public' => isset($is_public) && $is_public, + )) ?> + + + + render('task_comments/create', array( + 'values' => array( + 'user_id' => $this->user->getId(), + 'task_id' => $task['id'], + 'project_id' => $task['project_id'], + ), + 'errors' => array(), + 'task' => $task, + )) ?> + +
+
diff --git a/app/Template/task_creation/duplicate_projects.php b/app/Template/task_creation/duplicate_projects.php new file mode 100644 index 0000000..3c9c279 --- /dev/null +++ b/app/Template/task_creation/duplicate_projects.php @@ -0,0 +1,25 @@ + + + +

+
+ url->link(t('cancel'), 'BoardViewController', 'show', array('project_id' => $task['project_id']), false, 'js-modal-close btn') ?> +
+ +
+ form->csrf() ?> + form->hidden('task_id', $values) ?> + + form->select( + 'project_ids[]', + $projects_list, + $values, + array(), + array('multiple', 'aria-label="'.t('Duplicate to multiple projects').'"') + ) ?> + + modal->submitButtons() ?> +
+ diff --git a/app/Template/task_creation/show.php b/app/Template/task_creation/show.php new file mode 100644 index 0000000..f9d3f2f --- /dev/null +++ b/app/Template/task_creation/show.php @@ -0,0 +1,132 @@ + + +
+ form->csrf() ?> + +
+
+ task->renderTitleField($values, $errors) ?> + task->renderDescriptionField($values, $errors) ?> + task->renderDescriptionTemplateDropdown($project['id']) ?> + task->renderTagField($project) ?> + + hook->render('template:task:form:first-column', array('values' => $values, 'errors' => $errors)) ?> +
+ +
+ task->renderColorField($values) ?> + task->renderAssigneeField($users_list, $values, $errors) ?> + task->renderCategoryField($categories_list, $values, $errors) ?> + task->renderSwimlaneField($swimlanes_list, $values, $errors) ?> + task->renderColumnField($columns_list, $values, $errors) ?> + task->renderPriorityField($project, $values) ?> + + hook->render('template:task:form:second-column', array('values' => $values, 'errors' => $errors)) ?> + + task->renderDueDateField($values, $errors) ?> + task->renderStartDateField($values, $errors) ?> + task->renderTimeEstimatedField($values, $errors) ?> + task->renderTimeSpentField($values, $errors) ?> + task->renderScoreField($values, $errors) ?> + task->renderReferenceField($values, $errors) ?> + + hook->render('template:task:form:third-column', array('values' => $values, 'errors' => $errors)) ?> +
+ +
+ +
+ +
+ task->renderFileUpload($screenshot, $files) ?> +
+
+ + hook->render('template:task:form:bottom-before-buttons', array('values' => $values, 'errors' => $errors)) ?> + + + form->checkbox('another_task', t('Create another task'), 1, isset($values['another_task']) && $values['another_task'] == 1, '', array("tabindex" => "16")) ?> + form->checkbox('duplicate_multiple_projects', t('Duplicate to multiple projects'), 1, false, '', array("tabindex" => "17")) ?> + + + modal->submitButtons() ?> +
+
+
diff --git a/app/Template/task_duplication/copy.php b/app/Template/task_duplication/copy.php new file mode 100644 index 0000000..03f46ea --- /dev/null +++ b/app/Template/task_duplication/copy.php @@ -0,0 +1,46 @@ + + + +

+
+ url->link(t('cancel'), 'TaskViewController', 'show', array('task_id' => $task['id']), false, 'js-modal-close btn') ?> +
+ + +
+ form->csrf() ?> + form->hidden('id', $values) ?> + + form->label(t('Project'), 'project_id') ?> + app->component('select-dropdown-autocomplete', array( + 'name' => 'project_id', + 'items' => $projects_list, + 'defaultValue' => isset($values['project_id']) ? $values['project_id'] : null, + 'placeholder' => t('Choose a project'), + 'replace' => array( + 'regex' => 'PROJECT_ID', + 'url' => $this->url->href('TaskDuplicationController', 'copy', array('task_id' => $task['id'], 'dst_project_id' => 'PROJECT_ID')), + ) + )) ?> + + form->label(t('Swimlane'), 'swimlane_id') ?> + form->select('swimlane_id', $swimlanes_list, $values) ?> +

+ + form->label(t('Column'), 'column_id') ?> + form->select('column_id', $columns_list, $values) ?> +

+ + form->label(t('Category'), 'category_id') ?> + form->select('category_id', $categories_list, $values) ?> +

+ + form->label(t('Assignee'), 'owner_id') ?> + form->select('owner_id', $users_list, $values) ?> +

+ + modal->submitButtons() ?> +
+ diff --git a/app/Template/task_duplication/duplicate.php b/app/Template/task_duplication/duplicate.php new file mode 100644 index 0000000..8df6b93 --- /dev/null +++ b/app/Template/task_duplication/duplicate.php @@ -0,0 +1,15 @@ + + +
+

+ +

+ + modal->confirmButtons( + 'TaskDuplicationController', + 'duplicate', + array('task_id' => $task['id'], 'confirmation' => 'yes') + ) ?> +
diff --git a/app/Template/task_duplication/move.php b/app/Template/task_duplication/move.php new file mode 100644 index 0000000..a1a2eb6 --- /dev/null +++ b/app/Template/task_duplication/move.php @@ -0,0 +1,48 @@ + + + +

+
+ url->link(t('cancel'), 'TaskViewController', 'show', array('task_id' => $task['id']), false, 'js-modal-close btn') ?> +
+ + +
+ + form->csrf() ?> + form->hidden('id', $values) ?> + + form->label(t('Project'), 'project_id') ?> + app->component('select-dropdown-autocomplete', array( + 'name' => 'project_id', + 'items' => $projects_list, + 'defaultValue' => isset($values['project_id']) ? $values['project_id'] : null, + 'placeholder' => t('Choose a project'), + 'replace' => array( + 'regex' => 'PROJECT_ID', + 'url' => $this->url->href('TaskDuplicationController', 'move', array('task_id' => $task['id'], 'dst_project_id' => 'PROJECT_ID')), + ) + )) ?> + + form->label(t('Swimlane'), 'swimlane_id') ?> + form->select('swimlane_id', $swimlanes_list, $values) ?> +

+ + form->label(t('Column'), 'column_id') ?> + form->select('column_id', $columns_list, $values) ?> +

+ + form->label(t('Category'), 'category_id') ?> + form->select('category_id', $categories_list, $values) ?> +

+ + form->label(t('Assignee'), 'owner_id') ?> + form->select('owner_id', $users_list, $values) ?> +

+ + modal->submitButtons() ?> +
+ + diff --git a/app/Template/task_external_link/create.php b/app/Template/task_external_link/create.php new file mode 100644 index 0000000..2ceb1aa --- /dev/null +++ b/app/Template/task_external_link/create.php @@ -0,0 +1,8 @@ + + +
+ render('task_external_link/form', array('task' => $task, 'dependencies' => $dependencies, 'values' => $values, 'errors' => $errors)) ?> + modal->submitButtons() ?> +
diff --git a/app/Template/task_external_link/edit.php b/app/Template/task_external_link/edit.php new file mode 100644 index 0000000..dd6f8f4 --- /dev/null +++ b/app/Template/task_external_link/edit.php @@ -0,0 +1,8 @@ + + +
+ render('task_external_link/form', array('task' => $task, 'dependencies' => $dependencies, 'values' => $values, 'errors' => $errors)) ?> + modal->submitButtons() ?> +
diff --git a/app/Template/task_external_link/find.php b/app/Template/task_external_link/find.php new file mode 100644 index 0000000..393b97d --- /dev/null +++ b/app/Template/task_external_link/find.php @@ -0,0 +1,23 @@ + + +
+ form->csrf() ?> + + form->label(t('External link'), 'text') ?> + form->text( + 'text', + $values, + $errors, + array( + 'required', + 'autofocus', + 'placeholder="'.t('Copy and paste your link here...').'"', + )) ?> + + form->label(t('Link type'), 'type') ?> + form->select('type', $types, $values) ?> + + modal->submitButtons() ?> +
diff --git a/app/Template/task_external_link/form.php b/app/Template/task_external_link/form.php new file mode 100644 index 0000000..4ad2b2e --- /dev/null +++ b/app/Template/task_external_link/form.php @@ -0,0 +1,11 @@ +form->csrf() ?> +form->hidden('link_type', $values) ?> + +form->label(t('URL'), 'url') ?> +form->text('url', $values, $errors, array('required')) ?> + +form->label(t('Title'), 'title') ?> +form->text('title', $values, $errors, array('required')) ?> + +form->label(t('Dependency'), 'dependency') ?> +form->select('dependency', $dependencies, $values, $errors) ?> diff --git a/app/Template/task_external_link/remove.php b/app/Template/task_external_link/remove.php new file mode 100644 index 0000000..c3eead6 --- /dev/null +++ b/app/Template/task_external_link/remove.php @@ -0,0 +1,15 @@ + + +
+

+ +

+ + modal->confirmButtons( + 'TaskExternalLinkController', + 'remove', + array('link_id' => $link['id'], 'task_id' => $task['id']) + ) ?> +
diff --git a/app/Template/task_external_link/show.php b/app/Template/task_external_link/show.php new file mode 100644 index 0000000..1bb8997 --- /dev/null +++ b/app/Template/task_external_link/show.php @@ -0,0 +1,10 @@ +
> + +
+ render('task_external_link/table', array( + 'links' => $links, + 'task' => $task, + 'project' => $project, + )) ?> +
+
diff --git a/app/Template/task_external_link/table.php b/app/Template/task_external_link/table.php new file mode 100644 index 0000000..816f533 --- /dev/null +++ b/app/Template/task_external_link/table.php @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + +
+ user->hasProjectAccess('TaskExternalLinkController', 'edit', $task['project_id'])): ?> + + + text->e($link['type']) ?> + + text->e($link['title']) ?> (text->e($link['url']) ?>) + + text->e($link['dependency_label']) ?> + + text->e($link['creator_name'] ?: $link['creator_username']) ?> + + dt->date($link['date_creation']) ?> +
+ diff --git a/app/Template/task_file/create.php b/app/Template/task_file/create.php new file mode 100644 index 0000000..22ee1b0 --- /dev/null +++ b/app/Template/task_file/create.php @@ -0,0 +1,21 @@ + + +app->component('file-upload', array( + 'csrf' => $this->app->getToken()->getReusableCSRFToken(), + 'maxSize' => $max_size, + 'url' => $this->url->to('TaskFileController', 'save', array('task_id' => $task['id'])), + 'labelDropzone' => t('Drag and drop your files here'), + 'labelOr' => t('or'), + 'labelChooseFiles' => t('choose files'), + 'labelOversize' => $max_size > 0 ? t('The maximum allowed file size is %sB.', $this->text->bytes($max_size)) : null, + 'labelSuccess' => t('All files have been uploaded successfully.'), + 'labelCloseSuccess' => t('Close this window'), + 'labelUploadError' => t('Unable to upload this file.'), +)) ?> + +modal->submitButtons(array( + 'submitLabel' => t('Upload files'), + 'disabled' => true, +)) ?> diff --git a/app/Template/task_file/files.php b/app/Template/task_file/files.php new file mode 100644 index 0000000..7cdef8a --- /dev/null +++ b/app/Template/task_file/files.php @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + +
+ + + + text->bytes($file['size']) ?> + + text->e($file['user_name'] ?: $file['username']) ?> + + dt->date($file['date']) ?> +
+ diff --git a/app/Template/task_file/images.php b/app/Template/task_file/images.php new file mode 100644 index 0000000..ae1bcab --- /dev/null +++ b/app/Template/task_file/images.php @@ -0,0 +1,50 @@ + +
+ +
+ app->component('image-slideshow', array( + 'images' => $images, + 'image' => $file, + 'regex_file_id' => 'FILE_ID', + 'regex_etag' => 'ETAG', + 'url' => array( + 'image' => $this->url->to('FileViewerController', 'image', array('file_id' => 'FILE_ID', 'task_id' => $task['id'], 'etag' => 'ETAG')), + 'thumbnail' => $this->url->to('FileViewerController', 'thumbnail', array('file_id' => 'FILE_ID', 'task_id' => $task['id'], 'etag' => 'ETAG')), + 'download' => $this->url->to('FileViewerController', 'download', array('file_id' => 'FILE_ID', 'task_id' => $task['id'], 'etag' => 'ETAG')), + ) + )) ?> + +
+
+ +
+
+ hook->render('template:task-file:images:before-thumbnail-description', array('task' => $task, 'file' => $file)) ?> + app->tooltipMarkdown(t('Uploaded: %s', $this->dt->datetime($file['date']))."\n\n".t('Size: %s', $this->text->bytes($file['size']))) ?> + + + + dt->datetime($file['date'])) ?> + +
+
+
+ +
+ diff --git a/app/Template/task_file/remove.php b/app/Template/task_file/remove.php new file mode 100644 index 0000000..351a09e --- /dev/null +++ b/app/Template/task_file/remove.php @@ -0,0 +1,15 @@ + + +
+

+ text->e($file['name'])) ?> +

+ + modal->confirmButtons( + 'TaskFileController', + 'remove', + array('task_id' => $task['id'], 'file_id' => $file['id']) + ) ?> +
diff --git a/app/Template/task_file/screenshot.php b/app/Template/task_file/screenshot.php new file mode 100644 index 0000000..4abe8bf --- /dev/null +++ b/app/Template/task_file/screenshot.php @@ -0,0 +1,15 @@ + + +
+

+
+ +
+ form->csrf() ?> + app->component('screenshot') ?> + modal->submitButtons() ?> +
+ +

diff --git a/app/Template/task_file/show.php b/app/Template/task_file/show.php new file mode 100644 index 0000000..e9723f0 --- /dev/null +++ b/app/Template/task_file/show.php @@ -0,0 +1,7 @@ +
> + +
+ render('task_file/images', array('task' => $task, 'images' => $images)) ?> + render('task_file/files', array('task' => $task, 'files' => $files)) ?> +
+
diff --git a/app/Template/task_import/show.php b/app/Template/task_import/show.php new file mode 100644 index 0000000..c407938 --- /dev/null +++ b/app/Template/task_import/show.php @@ -0,0 +1,37 @@ + +
+ form->csrf() ?> + + form->label(t('Delimiter'), 'delimiter') ?> + form->select('delimiter', $delimiters, $values) ?> + + form->label(t('Enclosure'), 'enclosure') ?> + form->select('enclosure', $enclosures, $values) ?> + + form->label(t('CSV File'), 'file') ?> + form->file('file', $errors) ?> + + 0): ?> +

text->bytes($max_size) : $max_size ?>

+ + + modal->submitButtons(array('submitLabel' => t('Import'))) ?> +
+ +
+

+
    +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
+

+ url->icon('download', t('Download CSV template'), 'TaskImportController', 'template', array('project_id' => $project['id'])) ?> +

+
diff --git a/app/Template/task_internal_link/create.php b/app/Template/task_internal_link/create.php new file mode 100644 index 0000000..1f0e4c3 --- /dev/null +++ b/app/Template/task_internal_link/create.php @@ -0,0 +1,30 @@ + + +
+ + form->csrf() ?> + form->hidden('opposite_task_id', $values) ?> + + form->label(t('Label'), 'link_id') ?> + form->select('link_id', $labels, $values, $errors) ?> + + form->label(t('Task'), 'title') ?> + form->text( + 'title', + $values, + $errors, + array( + 'required', + 'placeholder="'.t('Start to type task title...').'"', + 'title="'.t('Start to type task title...').'"', + 'data-dst-field="opposite_task_id"', + 'data-search-url="'.$this->url->href('TaskAjaxController', 'autocomplete', array('exclude_task_id' => $task['id'])).'"', + ), + 'autocomplete') ?> + + form->checkbox('another_tasklink', t('Create another link'), 1, isset($values['another_tasklink']) && $values['another_tasklink'] == 1) ?> + + modal->submitButtons() ?> +
diff --git a/app/Template/task_internal_link/edit.php b/app/Template/task_internal_link/edit.php new file mode 100644 index 0000000..81b0191 --- /dev/null +++ b/app/Template/task_internal_link/edit.php @@ -0,0 +1,28 @@ + + +
+ form->csrf() ?> + + form->hidden('opposite_task_id', $values) ?> + + form->label(t('Label'), 'link_id') ?> + form->select('link_id', $labels, $values, $errors) ?> + + form->label(t('Task'), 'title') ?> + form->text( + 'title', + $values, + $errors, + array( + 'required', + 'placeholder="'.t('Start to type task title...').'"', + 'title="'.t('Start to type task title...').'"', + 'data-dst-field="opposite_task_id"', + 'data-search-url="'.$this->url->href('TaskAjaxController', 'autocomplete', array('exclude_task_id' => $task['id'])).'"', + ), + 'autocomplete') ?> + + modal->submitButtons() ?> +
diff --git a/app/Template/task_internal_link/remove.php b/app/Template/task_internal_link/remove.php new file mode 100644 index 0000000..d331679 --- /dev/null +++ b/app/Template/task_internal_link/remove.php @@ -0,0 +1,15 @@ + + +
+

+ +

+ + modal->confirmButtons( + 'TaskInternalLinkController', + 'remove', + array('link_id' => $link['id'], 'task_id' => $task['id']) + ) ?> +
diff --git a/app/Template/task_internal_link/show.php b/app/Template/task_internal_link/show.php new file mode 100644 index 0000000..e5c0389 --- /dev/null +++ b/app/Template/task_internal_link/show.php @@ -0,0 +1,12 @@ +
> + +
+ render('task_internal_link/table', array( + 'links' => $links, + 'task' => $task, + 'project' => $project, + 'editable' => $editable, + 'is_public' => $is_public, + )) ?> +
+
diff --git a/app/Template/task_internal_link/table.php b/app/Template/task_internal_link/table.php new file mode 100644 index 0000000..7c24cd8 --- /dev/null +++ b/app/Template/task_internal_link/table.php @@ -0,0 +1,80 @@ + + + $grouped_links): ?> + + + + + + + + + + + + + + + + + + + diff --git a/app/Template/task_list/header.php b/app/Template/task_list/header.php new file mode 100644 index 0000000..2e86a2d --- /dev/null +++ b/app/Template/task_list/header.php @@ -0,0 +1,41 @@ +
+
+ getTotal() > 1): ?> + getTotal()) ?> + + getTotal()) ?> + +
+ + user->hasProjectAccess('TaskModificationController', 'save', $project['id'])): ?> + +
+ -  + +
+ + +
+ + user->hasSubtaskListActivated()): ?> + url->icon('tasks', t('Hide subtasks'), 'TaskListController', 'show', array('project_id' => $project['id'], 'hide_subtasks' => 1, 'csrf_token' => $this->app->getToken()->getReusableCSRFToken())) ?> + + url->icon('tasks', t('Show subtasks'), 'TaskListController', 'show', array('project_id' => $project['id'], 'show_subtasks' => 1, 'csrf_token' => $this->app->getToken()->getReusableCSRFToken())) ?> + + + + render('task_list/sort_menu', array('paginator' => $paginator)) ?> +
+
\ No newline at end of file diff --git a/app/Template/task_list/listing.php b/app/Template/task_list/listing.php new file mode 100644 index 0000000..595a190 --- /dev/null +++ b/app/Template/task_list/listing.php @@ -0,0 +1,41 @@ +projectHeader->render($project, 'TaskListController', 'show') ?> + +isEmpty()): ?> +

+isEmpty()): ?> +
+ render('task_list/header', array( + 'paginator' => $paginator, + 'project' => $project, + 'show_items_selection' => true, + )) ?> + + getCollection() as $task): ?> +
+ render('task_list/task_title', array( + 'task' => $task, + 'show_items_selection' => true, + 'redirect' => 'list', + )) ?> + + render('task_list/task_details', array( + 'task' => $task, + )) ?> + + render('task_list/task_avatars', array( + 'task' => $task, + )) ?> + + render('task_list/task_icons', array( + 'task' => $task, + )) ?> + + render('task_list/task_subtasks', array( + 'task' => $task, + )) ?> +
+ +
+ + + diff --git a/app/Template/task_list/sort_menu.php b/app/Template/task_list/sort_menu.php new file mode 100644 index 0000000..e0322d7 --- /dev/null +++ b/app/Template/task_list/sort_menu.php @@ -0,0 +1,41 @@ + diff --git a/app/Template/task_list/task_avatars.php b/app/Template/task_list/task_avatars.php new file mode 100644 index 0000000..d82460f --- /dev/null +++ b/app/Template/task_list/task_avatars.php @@ -0,0 +1,20 @@ + +
+ user->hasProjectAccess('TaskModificationController', 'edit', $task['project_id'])): ?> + class="task-board-change-assignee" + data-url="url->href('TaskModificationController', 'edit', array('task_id' => $task['id'])) ?>"> + + class="task-board-assignee"> + + avatar->small( + $task['owner_id'], + $task['assignee_username'], + $task['assignee_name'], + $task['assignee_email'], + $task['assignee_avatar_path'], + 'avatar-inline' + ) ?>text->e($task['assignee_name'] ?: $task['assignee_username']) ?> + +
+ diff --git a/app/Template/task_list/task_details.php b/app/Template/task_list/task_details.php new file mode 100644 index 0000000..ad2eb23 --- /dev/null +++ b/app/Template/task_list/task_details.php @@ -0,0 +1,32 @@ +
+ text->e($task['project_name']) ?> > + text->e($task['swimlane_name']) ?> > + text->e($task['column_name']) ?> + + + "> + user->hasProjectAccess('TaskModificationController', 'edit', $task['project_id'])): ?> + url->link( + $this->text->e($task['category_name']), + 'TaskModificationController', + 'edit', + array('task_id' => $task['id']), + false, + 'js-modal-medium' . (! empty($task['category_description']) ? ' tooltip' : ''), + t('Change category') + ) ?> + + app->tooltipMarkdown($task['category_description']) ?> + + + text->e($task['category_name']) ?> + + + + + + "> + text->e($tag['name']) ?> + + +
diff --git a/app/Template/task_list/task_icons.php b/app/Template/task_list/task_icons.php new file mode 100644 index 0000000..21b90df --- /dev/null +++ b/app/Template/task_list/task_icons.php @@ -0,0 +1,103 @@ +
+ + + task->renderReference($task) ?> + + + + + + + + + + + + text->e($task['score']) ?> + + + + + + text->e($task['time_spent']) ?>/text->e($task['time_estimated']) ?>h + + + + + + + dt->date($task['date_started']) ?> + + + + + + + dt->datetime($task['date_due']) ?> + + + + + app->tooltipLink('', $this->url->href('BoardTooltipController', 'recurrence', array('task_id' => $task['id']))) ?> + + + + app->tooltipLink('', $this->url->href('BoardTooltipController', 'recurrence', array('task_id' => $task['id']))) ?> + + + + app->tooltipLink(''.$task['nb_links'], $this->url->href('BoardTooltipController', 'tasklinks', array('task_id' => $task['id']))) ?> + + + + app->tooltipLink(''.$task['nb_external_links'], $this->url->href('BoardTooltipController', 'externallinks', array('task_id' => $task['id']))) ?> + + + + 0 ? round($nb_completed / $nb_subtasks * 100, 0) : 0; ?> + app->tooltipLink(''.$percentage.'% ('.$nb_completed.'/'.$nb_subtasks.')', $this->url->href('BoardTooltipController', 'subtasks', array('task_id' => $task['id']))) ?> + + + + app->tooltipLink(''.$task['nb_files'], $this->url->href('BoardTooltipController', 'attachments', array('task_id' => $task['id']))) ?> + + + 0): ?> + user->hasProjectAccess('TaskModificationController', 'edit', $task['project_id'])): ?> + modal->medium( + 'comments-o', + $task['nb_comments'], + 'CommentListController', + 'show', + array('task_id' => $task['id']), + $task['nb_comments'] == 1 ? t('%d comment', $task['nb_comments']) : t('%d comments', $task['nb_comments']) + ) ?> + + +   + + + + + app->tooltipLink('', $this->url->href('BoardTooltipController', 'description', array('task_id' => $task['id']))) ?> + + + ( ) + + +
+ dt->age($task['date_creation']) ?> + dt->age($task['date_moved']) ?> +
+ + + + + task->renderPriority($task['priority']) ?> +
diff --git a/app/Template/task_list/task_subtasks.php b/app/Template/task_list/task_subtasks.php new file mode 100644 index 0000000..9110b17 --- /dev/null +++ b/app/Template/task_list/task_subtasks.php @@ -0,0 +1,22 @@ + +
+ +
+ + subtask->renderToggleStatus($task, $subtask, 'rows', isset($user_id) ? $user_id : 0) ?> + + + + text->e($subtask['name'] ?: $subtask['username']) ?> + + + + render('subtask/timer', array( + 'task' => $task, + 'subtask' => $subtask, + )) ?> + +
+ +
+ diff --git a/app/Template/task_list/task_title.php b/app/Template/task_list/task_title.php new file mode 100644 index 0000000..43fdb06 --- /dev/null +++ b/app/Template/task_list/task_title.php @@ -0,0 +1,14 @@ +
+ user->hasProjectAccess('TaskModificationController', 'edit', $task['project_id'])): ?> + + + + render('task/dropdown', array('task' => $task, 'redirect' => isset($redirect) ? $redirect : '')) ?> + + + + + + url->link($this->text->e($task['title']), 'TaskViewController', 'show', array('task_id' => $task['id'])) ?> + +
diff --git a/app/Template/task_mail/create.php b/app/Template/task_mail/create.php new file mode 100644 index 0000000..813cfde --- /dev/null +++ b/app/Template/task_mail/create.php @@ -0,0 +1,47 @@ + +
+ form->csrf() ?> + + form->label(t('Email'), 'emails') ?> + form->text('emails', $values, $errors, array('autofocus', 'required', 'tabindex="1"')) ?> + + + + + + form->label(t('Subject'), 'subject') ?> + form->text('subject', $values, $errors, array('required', 'tabindex="2"')) ?> + + + + + + modal->submitButtons(array( + 'submitLabel' => t('Send by email'), + 'tabindex' => 3, + )) ?> +
diff --git a/app/Template/task_mail/email.php b/app/Template/task_mail/email.php new file mode 100644 index 0000000..ee85b1d --- /dev/null +++ b/app/Template/task_mail/email.php @@ -0,0 +1,46 @@ +

text->e($task['title']) ?> (#)

+ +
    +
  • + +
  • +
  • + dt->datetime($task['date_creation']) ?> +
  • + +
  • + dt->datetime($task['date_due']) ?> +
  • + + +
  • + +
  • + +
  • + + + + + + + +
  • +
  • + + text->e($task['column_title']) ?> +
  • +
  • text->e($task['position']) ?>
  • + +
  • + text->e($task['category_name']) ?> +
  • + +
+ + +

+ text->markdown($task['description'], true) ?> + + +render('notification/footer', array('task' => $task)) ?> \ No newline at end of file diff --git a/app/Template/task_modification/show.php b/app/Template/task_modification/show.php new file mode 100644 index 0000000..1b6ffaa --- /dev/null +++ b/app/Template/task_modification/show.php @@ -0,0 +1,44 @@ + +
+ form->csrf() ?> + +
+
+ task->renderTitleField($values, $errors) ?> + task->renderDescriptionField($values, $errors) ?> + task->renderDescriptionTemplateDropdown($project['id']) ?> + task->renderTagField($project, $tags) ?> + + hook->render('template:task:form:first-column', array('values' => $values, 'errors' => $errors)) ?> +
+ +
+ task->renderColorField($values) ?> + task->renderAssigneeField($users_list, $values, $errors) ?> + task->renderCategoryField($categories_list, $values, $errors) ?> + task->renderPriorityField($project, $values) ?> + + hook->render('template:task:form:second-column', array('values' => $values, 'errors' => $errors)) ?> +
+ +
+ task->renderDueDateField($values, $errors) ?> + task->renderStartDateField($values, $errors) ?> + task->renderTimeEstimatedField($values, $errors) ?> + task->renderTimeSpentField($values, $errors) ?> + task->renderScoreField($values, $errors) ?> + task->renderReferenceField($values, $errors) ?> + + hook->render('template:task:form:third-column', array('values' => $values, 'errors' => $errors)) ?> +
+ +
+ + hook->render('template:task:form:bottom-before-buttons', array('values' => $values, 'errors' => $errors)) ?> + + modal->submitButtons() ?> +
+
+
diff --git a/app/Template/task_move_position/show.php b/app/Template/task_move_position/show.php new file mode 100644 index 0000000..35ff293 --- /dev/null +++ b/app/Template/task_move_position/show.php @@ -0,0 +1,20 @@ + + +
+ +app->component('task-move-position', array( + 'saveUrl' => $this->url->href('TaskMovePositionController', 'save', array('task_id' => $task['id'], 'csrf_token' => $this->app->getToken()->getReusableCSRFToken())), + 'board' => $board, + 'task' => $task, + 'swimlaneLabel' => t('Swimlane'), + 'columnLabel' => t('Column'), + 'positionLabel' => t('Position'), + 'beforeLabel' => t('Insert before this task'), + 'afterLabel' => t('Insert after this task'), +)) ?> + +modal->submitButtons() ?> + +
diff --git a/app/Template/task_recurrence/edit.php b/app/Template/task_recurrence/edit.php new file mode 100644 index 0000000..0b1bf26 --- /dev/null +++ b/app/Template/task_recurrence/edit.php @@ -0,0 +1,43 @@ + + + +
+ render('task_recurrence/info', array( + 'task' => $task, + 'recurrence_trigger_list' => $recurrence_trigger_list, + 'recurrence_timeframe_list' => $recurrence_timeframe_list, + 'recurrence_basedate_list' => $recurrence_basedate_list, + )) ?> +
+ + + + +
+ + form->csrf() ?> + + form->hidden('id', $values) ?> + form->hidden('project_id', $values) ?> + + form->label(t('Generate recurrent task'), 'recurrence_status') ?> + form->select('recurrence_status', $recurrence_status_list, $values, $errors) ?> + + form->label(t('Trigger to generate recurrent task'), 'recurrence_trigger') ?> + form->select('recurrence_trigger', $recurrence_trigger_list, $values, $errors) ?> + + form->label(t('Factor to calculate new due date'), 'recurrence_factor') ?> + form->number('recurrence_factor', $values, $errors) ?> + + form->label(t('Timeframe to calculate new due date'), 'recurrence_timeframe') ?> + form->select('recurrence_timeframe', $recurrence_timeframe_list, $values, $errors) ?> + + form->label(t('Base date to calculate new due date'), 'recurrence_basedate') ?> + form->select('recurrence_basedate', $recurrence_basedate_list, $values, $errors) ?> + + modal->submitButtons() ?> +
+ + diff --git a/app/Template/task_recurrence/info.php b/app/Template/task_recurrence/info.php new file mode 100644 index 0000000..7c06e3f --- /dev/null +++ b/app/Template/task_recurrence/info.php @@ -0,0 +1,39 @@ +
+
    + +
  • + +
  • +
      +
    • + text->e($recurrence_trigger_list[$task['recurrence_trigger']]) ?> +
    • +
    • + text->e($task['recurrence_factor']) ?> +
    • +
    • + text->e($recurrence_timeframe_list[$task['recurrence_timeframe']]) ?> +
    • +
    • + text->e($recurrence_basedate_list[$task['recurrence_basedate']]) ?> +
    • +
    +
  • + + + + +
  • + + url->link('#'.$task['recurrence_parent'], 'TaskViewController', 'show', array('task_id' => $task['recurrence_parent'])) ?> +
  • + + +
  • + + url->link('#'.$task['recurrence_child'], 'TaskViewController', 'show', array('task_id' => $task['recurrence_child'])) ?> +
  • + + +
+
\ No newline at end of file diff --git a/app/Template/task_status/close.php b/app/Template/task_status/close.php new file mode 100644 index 0000000..dfeba00 --- /dev/null +++ b/app/Template/task_status/close.php @@ -0,0 +1,15 @@ + + +
+

+ +

+ + modal->confirmButtons( + 'TaskStatusController', + 'close', + array('task_id' => $task['id'], 'confirmation' => 'yes') + ) ?> +
diff --git a/app/Template/task_status/open.php b/app/Template/task_status/open.php new file mode 100644 index 0000000..dce28d7 --- /dev/null +++ b/app/Template/task_status/open.php @@ -0,0 +1,15 @@ + + +
+

+ +

+ + modal->confirmButtons( + 'TaskStatusController', + 'open', + array('task_id' => $task['id'], 'confirmation' => 'yes') + ) ?> +
diff --git a/app/Template/task_suppression/remove.php b/app/Template/task_suppression/remove.php new file mode 100644 index 0000000..06d0211 --- /dev/null +++ b/app/Template/task_suppression/remove.php @@ -0,0 +1,15 @@ + + +
+

+ text->e($task['title'])) ?> +

+ + modal->confirmButtons( + 'TaskSuppressionController', + 'remove', + array('task_id' => $task['id'], 'redirect' => $redirect) + ) ?> +
diff --git a/app/Template/twofactor/check.php b/app/Template/twofactor/check.php new file mode 100644 index 0000000..dc95eb5 --- /dev/null +++ b/app/Template/twofactor/check.php @@ -0,0 +1,17 @@ + diff --git a/app/Template/twofactor/disable.php b/app/Template/twofactor/disable.php new file mode 100644 index 0000000..c44c450 --- /dev/null +++ b/app/Template/twofactor/disable.php @@ -0,0 +1,15 @@ + + +
+

+ +

+ + modal->confirmButtons( + 'TwoFactorController', + 'disable', + array('user_id' => $user['id'], 'disable' => 'yes') + ) ?> +
diff --git a/app/Template/twofactor/index.php b/app/Template/twofactor/index.php new file mode 100644 index 0000000..1ed414e --- /dev/null +++ b/app/Template/twofactor/index.php @@ -0,0 +1,15 @@ + + +
+ form->csrf() ?> +

text->e($provider) ?>

+
+ + + + + +
+
diff --git a/app/Template/twofactor/show.php b/app/Template/twofactor/show.php new file mode 100644 index 0000000..caa3588 --- /dev/null +++ b/app/Template/twofactor/show.php @@ -0,0 +1,32 @@ + + +app->isAjax()): ?> + app->flashMessage() ?> + + + +
+ +

text->e($secret) ?>

+ + + +

+

text->e($key_url) ?>

+ +
+ + +

+
+
+ + form->csrf() ?> + form->label(t('Code'), 'code') ?> + form->text('code', array(), array(), array('placeholder="123456"', 'autofocus'), 'form-numeric', 'autocomplete="one-time-code"', 'pattern="[0-9]*"', 'inputmode="numeric"') ?> + + modal->submitButtons(array('submitLabel' => t('Check my code'))) ?> +
+
diff --git a/app/Template/user_api_access/show.php b/app/Template/user_api_access/show.php new file mode 100644 index 0000000..39bf354 --- /dev/null +++ b/app/Template/user_api_access/show.php @@ -0,0 +1,17 @@ + + +

+ + + + + +

+ + + url->link(t('Remove your token'), 'UserApiAccessController', 'remove', array('user_id' => $user['id']), true, 'btn btn-red js-modal-replace') ?> + + +url->link(t('Generate a new token'), 'UserApiAccessController', 'generate', array('user_id' => $user['id']), true, 'btn btn-blue js-modal-replace') ?> diff --git a/app/Template/user_creation/show.php b/app/Template/user_creation/show.php new file mode 100644 index 0000000..2c6ad35 --- /dev/null +++ b/app/Template/user_creation/show.php @@ -0,0 +1,74 @@ + +
+ form->csrf() ?> + +
+
+
+ + + form->label(t('Username'), 'username') ?> + form->text('username', $values, $errors, array('autofocus', 'required', 'maxlength="191"', 'autocomplete="username"')) ?> + + form->label(t('Name'), 'name') ?> + form->text('name', $values, $errors, ['autocomplete="name"']) ?> + + form->label(t('Email'), 'email') ?> + form->email('email', $values, $errors, ['autocomplete="email"']) ?> +
+ +
+ + form->checkbox('is_ldap_user', t('Remote user'), 1, isset($values['is_ldap_user']) && $values['is_ldap_user'] == 1) ?> +

+ + form->label(t('Password'), 'password') ?> + form->password('password', $values, $errors, ['autocomplete="new-password"']) ?> +

+ + form->label(t('Confirmation'), 'confirmation') ?> + form->password('confirmation', $values, $errors) ?> +
+
+ +
+
+ + + form->label(t('Role'), 'role') ?> + form->select('role', $roles, $values, $errors) ?> + + form->checkbox('disable_login_form', t('Disallow login form'), 1, isset($values['disable_login_form']) && $values['disable_login_form'] == 1) ?> +
+ +
+ + form->label(t('Timezone'), 'timezone') ?> + form->select('timezone', $timezones, $values, $errors) ?> + + form->label(t('Language'), 'language') ?> + form->select('language', $languages, $values, $errors) ?> + + form->label(t('Filter'), 'filter') ?> + form->text('filter', $values, $errors) ?> + + app->config('notifications_enabled') == 1): ?> + + + form->checkbox('notifications_enabled', t('Enable email notifications'), 1, isset($values['notifications_enabled']) && $values['notifications_enabled'] == 1 ? true : false) ?> + +
+ +
+ + + form->label(t('Add this person to this project'), 'project_id') ?> + form->select('project_id', $projects, $values, $errors) ?> +
+
+
+ + modal->submitButtons() ?> +
diff --git a/app/Template/user_credential/authentication.php b/app/Template/user_credential/authentication.php new file mode 100644 index 0000000..32371bd --- /dev/null +++ b/app/Template/user_credential/authentication.php @@ -0,0 +1,25 @@ + +
+ form->csrf() ?> + +
+ form->hidden('id', $values) ?> + form->hidden('username', $values) ?> + + hook->render('template:user:authentication:form', array('values' => $values, 'errors' => $errors, 'user' => $user)) ?> + + form->checkbox('is_ldap_user', t('Remote user'), 1, isset($values['is_ldap_user']) && $values['is_ldap_user'] == 1) ?> + form->checkbox('disable_login_form', t('Disallow login form'), 1, isset($values['disable_login_form']) && $values['disable_login_form'] == 1) ?> +
+ + modal->submitButtons() ?> + +
+
    +
  • +
  • +
+
+
diff --git a/app/Template/user_credential/password.php b/app/Template/user_credential/password.php new file mode 100644 index 0000000..ee752af --- /dev/null +++ b/app/Template/user_credential/password.php @@ -0,0 +1,21 @@ + + +
+ form->hidden('id', $values) ?> + form->csrf() ?> + +
+ form->label(t('Current password for the user "%s"', $this->user->getFullname()), 'current_password') ?> + form->password('current_password', $values, $errors, array('autofocus', 'autocomplete="current-password"')) ?> + + form->label(t('New password for the user "%s"', $this->user->getFullname($user)), 'password') ?> + form->password('password', $values, $errors, ['autocomplete="new-password"']) ?> + + form->label(t('Confirmation'), 'confirmation') ?> + form->password('confirmation', $values, $errors) ?> +
+ + modal->submitButtons() ?> +
diff --git a/app/Template/user_import/show.php b/app/Template/user_import/show.php new file mode 100644 index 0000000..35eddc3 --- /dev/null +++ b/app/Template/user_import/show.php @@ -0,0 +1,38 @@ + + +
+
    +
  • +
  • +
  • +
  • +
  • +
  • +
+
+ +
+ form->csrf() ?> + + form->label(t('Delimiter'), 'delimiter') ?> + form->select('delimiter', $delimiters, $values) ?> + + form->label(t('Enclosure'), 'enclosure') ?> + form->select('enclosure', $enclosures, $values) ?> + + form->label(t('CSV File'), 'file') ?> + form->file('file', $errors) ?> + + 0): ?> +

text->bytes($max_size) : $max_size ?>

+ + + modal->submitButtons(array('submitLabel' => t('Import'))) ?> +
diff --git a/app/Template/user_invite/email.php b/app/Template/user_invite/email.php new file mode 100644 index 0000000..674e4a8 --- /dev/null +++ b/app/Template/user_invite/email.php @@ -0,0 +1,12 @@ +

+ +

+ +

+ url->absoluteLink(t('Click here to join your team'), 'UserInviteController', 'signup', array('token' => $token)) ?> +

+ +app->config('application_url')): ?> +
+ Kanboard + diff --git a/app/Template/user_invite/show.php b/app/Template/user_invite/show.php new file mode 100644 index 0000000..9d82224 --- /dev/null +++ b/app/Template/user_invite/show.php @@ -0,0 +1,15 @@ + +
+ form->csrf() ?> + + form->label(t('Emails'), 'emails') ?> + form->textarea('emails', $values, $errors, array('required', 'autofocus')) ?> +

+ + form->label(t('Add these people to this project'), 'project_id') ?> + form->select('project_id', $projects, $values, $errors) ?> + + modal->submitButtons() ?> +
diff --git a/app/Template/user_invite/signup.php b/app/Template/user_invite/signup.php new file mode 100644 index 0000000..2fb9a09 --- /dev/null +++ b/app/Template/user_invite/signup.php @@ -0,0 +1,50 @@ + \ No newline at end of file diff --git a/app/Template/user_list/dropdown.php b/app/Template/user_list/dropdown.php new file mode 100644 index 0000000..b58c056 --- /dev/null +++ b/app/Template/user_list/dropdown.php @@ -0,0 +1,100 @@ + diff --git a/app/Template/user_list/header.php b/app/Template/user_list/header.php new file mode 100644 index 0000000..9dbcc2a --- /dev/null +++ b/app/Template/user_list/header.php @@ -0,0 +1,13 @@ + +
+
+ getTotal() > 1): ?> + getTotal()) ?> + + getTotal()) ?> + +
+
+ render('user_list/sort_menu', array('paginator' => $paginator)) ?> +
+
diff --git a/app/Template/user_list/listing.php b/app/Template/user_list/listing.php new file mode 100644 index 0000000..2495517 --- /dev/null +++ b/app/Template/user_list/listing.php @@ -0,0 +1,51 @@ + + +
+ +
+ +isEmpty()): ?> +

+isEmpty()): ?> +
+ render('user_list/header', array('paginator' => $paginator)) ?> + getCollection() as $user): ?> +
+ render('user_list/user_title', array( + 'user' => $user, + )) ?> + + render('user_list/user_details', array( + 'user' => $user, + )) ?> + + render('user_list/user_icons', array( + 'user' => $user, + )) ?> +
+ +
+ + + diff --git a/app/Template/user_list/sort_menu.php b/app/Template/user_list/sort_menu.php new file mode 100644 index 0000000..250832f --- /dev/null +++ b/app/Template/user_list/sort_menu.php @@ -0,0 +1,29 @@ + diff --git a/app/Template/user_list/user_details.php b/app/Template/user_list/user_details.php new file mode 100644 index 0000000..3156bc3 --- /dev/null +++ b/app/Template/user_list/user_details.php @@ -0,0 +1,26 @@ +
+ + user->getRoleName($user['role']) ?> + + + + text->e($user['username']) ?> + + + + text->e($user['email']) ?> + + + + user->getUsersGroupNames($user['id']); ?> + + + + text->implode(', ', $users_groups['limited_list']) ?> + + ‑  + + + + +
diff --git a/app/Template/user_list/user_icons.php b/app/Template/user_list/user_icons.php new file mode 100644 index 0000000..2b15b25 --- /dev/null +++ b/app/Template/user_list/user_icons.php @@ -0,0 +1,59 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + dt->datetime($user['lock_expiration_date']); ?> + + + + + + + user->getRoleName($user['role']); ?> + + + + + + + user->getRoleName($user['role']); ?> + + + + + + + user->getRoleName($user['role']); ?> + + + + + + + + + + +
\ No newline at end of file diff --git a/app/Template/user_list/user_title.php b/app/Template/user_list/user_title.php new file mode 100644 index 0000000..02a065c --- /dev/null +++ b/app/Template/user_list/user_title.php @@ -0,0 +1,14 @@ +
+ render('user_list/dropdown', array('user' => $user)) ?> + + avatar->small( + $user['id'], + $user['username'], + $user['name'], + $user['email'], + $user['avatar_path'], + 'avatar-inline' + ) ?> + url->link($this->text->e($user['name'] ?: $user['username']), 'UserViewController', 'show', array('user_id' => $user['id'])) ?> + +
diff --git a/app/Template/user_modification/show.php b/app/Template/user_modification/show.php new file mode 100644 index 0000000..c9d54ff --- /dev/null +++ b/app/Template/user_modification/show.php @@ -0,0 +1,44 @@ + +
+ form->csrf() ?> + form->hidden('id', $values) ?> + +
+ + form->label(t('Username'), 'username') ?> + form->text('username', $values, $errors, array('autofocus', 'required', 'autocomplete="username"', isset($user['is_ldap_user']) && $user['is_ldap_user'] == 1 ? 'readonly' : '', 'maxlength="191"')) ?> + + form->label(t('Name'), 'name') ?> + form->text('name', $values, $errors, array($this->user->hasAccess('UserModificationController', 'show/edit_name') ? 'autocomplete="name"' : 'readonly')) ?> + + form->label(t('Email'), 'email') ?> + form->email('email', $values, $errors, array($this->user->hasAccess('UserModificationController', 'show/edit_email') ? 'autocomplete="email"' : 'readonly')) ?> +
+ +
+ + form->label(t('Theme'), 'theme') ?> + form->select('theme', $themes, $values, $errors, array($this->user->hasAccess('UserModificationController', 'show/edit_theme') ? '' : 'disabled')) ?> + + form->label(t('Timezone'), 'timezone') ?> + form->select('timezone', $timezones, $values, $errors, array($this->user->hasAccess('UserModificationController', 'show/edit_timezone') ? '' : 'disabled')) ?> + + form->label(t('Language'), 'language') ?> + form->select('language', $languages, $values, $errors, array($this->user->hasAccess('UserModificationController', 'show/edit_language') ? '' : 'disabled')) ?> + + form->label(t('Filter'), 'filter') ?> + form->text('filter', $values, $errors, array($this->user->hasAccess('UserModificationController', 'show/edit_filter') ? '' : 'readonly')) ?> +
+ + user->isAdmin()): ?> +
+ + form->label(t('Application role'), 'role') ?> + form->select('role', $roles, $values, $errors) ?> +
+ + + modal->submitButtons() ?> +
diff --git a/app/Template/user_status/disable.php b/app/Template/user_status/disable.php new file mode 100644 index 0000000..1309b08 --- /dev/null +++ b/app/Template/user_status/disable.php @@ -0,0 +1,13 @@ + + +
+

+ + modal->confirmButtons( + 'UserStatusController', + 'disable', + array('user_id' => $user['id']) + ) ?> +
diff --git a/app/Template/user_status/enable.php b/app/Template/user_status/enable.php new file mode 100644 index 0000000..2413739 --- /dev/null +++ b/app/Template/user_status/enable.php @@ -0,0 +1,13 @@ + + +
+

+ + modal->confirmButtons( + 'UserStatusController', + 'enable', + array('user_id' => $user['id']) + ) ?> +
diff --git a/app/Template/user_status/remove.php b/app/Template/user_status/remove.php new file mode 100644 index 0000000..6cd3f63 --- /dev/null +++ b/app/Template/user_status/remove.php @@ -0,0 +1,13 @@ + + +
+

+ + modal->confirmButtons( + 'UserStatusController', + 'remove', + array('user_id' => $user['id']) + ) ?> +
diff --git a/app/Template/user_view/external.php b/app/Template/user_view/external.php new file mode 100644 index 0000000..22c25af --- /dev/null +++ b/app/Template/user_view/external.php @@ -0,0 +1,11 @@ + + +hook->render('template:user:external', array('user' => $user)) ?> + + +

+ + + diff --git a/app/Template/user_view/integrations.php b/app/Template/user_view/integrations.php new file mode 100644 index 0000000..4a23734 --- /dev/null +++ b/app/Template/user_view/integrations.php @@ -0,0 +1,13 @@ + + +
+ form->csrf() ?> + hook->render('template:user:integrations', array('values' => $values)) ?> + + + +

+ +
diff --git a/app/Template/user_view/last.php b/app/Template/user_view/last.php new file mode 100644 index 0000000..72f59bf --- /dev/null +++ b/app/Template/user_view/last.php @@ -0,0 +1,24 @@ + + + +

+ + + + + + + + + + + + + + + + +
dt->datetime($login['date_creation']) ?>text->e($login['auth_type']) ?>text->e($login['ip']) ?>text->e($login['user_agent']) ?>
+ diff --git a/app/Template/user_view/layout.php b/app/Template/user_view/layout.php new file mode 100644 index 0000000..8f24adc --- /dev/null +++ b/app/Template/user_view/layout.php @@ -0,0 +1,26 @@ +
+ + +
diff --git a/app/Template/user_view/notifications.php b/app/Template/user_view/notifications.php new file mode 100644 index 0000000..1a8d9be --- /dev/null +++ b/app/Template/user_view/notifications.php @@ -0,0 +1,22 @@ + + +
+ form->csrf() ?> + +

+ form->checkboxes('notification_types', $types, $notifications) ?> + +
+

+ form->radios('notifications_filter', $filters, $notifications) ?> + +
+ +

+ form->checkboxes('notification_projects', $projects, $notifications) ?> + + + modal->submitButtons() ?> +
diff --git a/app/Template/user_view/password_reset.php b/app/Template/user_view/password_reset.php new file mode 100644 index 0000000..de7047e --- /dev/null +++ b/app/Template/user_view/password_reset.php @@ -0,0 +1,26 @@ + + + +

+ + + + + + + + + + + + + + + + + + +
dt->datetime($token['date_creation']) ?>dt->datetime($token['date_expiration']) ?>text->e($token['ip']) ?>text->e($token['user_agent']) ?>
+ diff --git a/app/Template/user_view/profile.php b/app/Template/user_view/profile.php new file mode 100644 index 0000000..d931e3e --- /dev/null +++ b/app/Template/user_view/profile.php @@ -0,0 +1,12 @@ +
+
+ avatar->render($user['id'], $user['username'], $user['name'], $user['email'], $user['avatar_path']) ?> +
+
    +
  • text->e($user['username']) ?>
  • +
  • text->e($user['name']) ?: t('None') ?>
  • +
  • text->e($user['email']) ?: t('None') ?>
  • + hook->render('template:user:show:profile:info', array('user' => $user)) ?> +
+
+
diff --git a/app/Template/user_view/sessions.php b/app/Template/user_view/sessions.php new file mode 100644 index 0000000..dec1175 --- /dev/null +++ b/app/Template/user_view/sessions.php @@ -0,0 +1,26 @@ + + + +

+ + + + + + + + + + + + + + + + + + +
dt->datetime($session['date_creation']) ?>dt->datetime($session['expiration']) ?>text->e($session['ip']) ?>text->e($session['user_agent']) ?>url->link(t('Remove'), 'UserViewController', 'removeSession', array('user_id' => $user['id'], 'id' => $session['id']), true, 'js-modal-replace') ?>
+ diff --git a/app/Template/user_view/share.php b/app/Template/user_view/share.php new file mode 100644 index 0000000..784e0eb --- /dev/null +++ b/app/Template/user_view/share.php @@ -0,0 +1,15 @@ + + + +
+
    +
  • url->icon('rss-square', t('RSS feed'), 'FeedController', 'user', array('token' => $user['token']), false, '', '', true) ?>
  • +
  • url->icon('calendar', t('iCal feed'), 'ICalendarController', 'user', array('token' => $user['token']), false, '', '', true) ?>
  • +
+
+ url->link(t('Disable public access'), 'UserViewController', 'share', array('user_id' => $user['id'], 'switch' => 'disable'), true, 'btn btn-red js-modal-replace') ?> + + url->link(t('Enable public access'), 'UserViewController', 'share', array('user_id' => $user['id'], 'switch' => 'enable'), true, 'btn btn-blue js-modal-replace') ?> + diff --git a/app/Template/user_view/show.php b/app/Template/user_view/show.php new file mode 100644 index 0000000..af06772 --- /dev/null +++ b/app/Template/user_view/show.php @@ -0,0 +1,53 @@ + +
    +
  • text->e($user['username']) ?>
  • +
  • text->e($user['name']) ?: t('None') ?>
  • +
  • text->e($user['email']) ?: t('None') ?>
  • +
  • + hook->render('template:user:show:profile:info', array('user' => $user)) ?> +
+ + +
    +
  • text->e($this->user->getRoleName($user['role'])) ?>
  • +
  • text->e(implode(', ', $this->user->getUsersGroupNames($user['id'])['full_list'])) ?>
  • +
  • +
  • +
  • + +
  • dt->datetime($user['lock_expiration_date']) ?>
  • + user->isAdmin()): ?> +
  • + url->link(t('Unlock this user'), 'UserCredentialController', 'unlock', array('user_id' => $user['id']), true) ?> +
  • + + +
+ + +
    +
  • text->in($user['theme'], $themes) ?>
  • +
  • text->in($user['timezone'], $timezones) ?>
  • +
  • text->in($user['language'], $languages) ?>
  • +
  • text->e($user['filter']) ?>
  • +
  • +
+ + + + +
+
    +
  • url->icon('rss-square', t('RSS feed'), 'FeedController', 'user', array('token' => $user['token']), false, '', '', true) ?>
  • +
  • url->icon('calendar', t('iCal feed'), 'ICalendarController', 'user', array('token' => $user['token']), false, '', '', true) ?>
  • +
+
+ diff --git a/app/Template/user_view/sidebar.php b/app/Template/user_view/sidebar.php new file mode 100644 index 0000000..a204ec5 --- /dev/null +++ b/app/Template/user_view/sidebar.php @@ -0,0 +1,108 @@ + diff --git a/app/Template/user_view/timesheet.php b/app/Template/user_view/timesheet.php new file mode 100644 index 0000000..c78cb0c --- /dev/null +++ b/app/Template/user_view/timesheet.php @@ -0,0 +1,29 @@ + + +

+isEmpty()): ?> +

+ + + + + + + + + + getCollection() as $record): ?> + + + + + + + + +
order(t('Task'), 'task_title') ?>order(t('Subtask'), 'subtask_title') ?>order(t('Start'), 'start') ?>order(t('End'), 'end') ?>order(t('Time spent'), 'time_spent') ?>
url->link($this->text->e($record['task_title']), 'TaskViewController', 'show', array('task_id' => $record['task_id'])) ?>url->link($this->text->e($record['subtask_title']), 'TaskViewController', 'show', array('task_id' => $record['task_id'])) ?>dt->datetime($record['start']) ?>dt->datetime($record['end']) ?>
+ + + diff --git a/app/Template/web_notification/show.php b/app/Template/web_notification/show.php new file mode 100644 index 0000000..72ebdb2 --- /dev/null +++ b/app/Template/web_notification/show.php @@ -0,0 +1,68 @@ + + + +

+ +
+
+
+ 1): ?> + + + + +
+   +
+ +
+ + text->contains($notification['event_name'], 'subtask')): ?> + + text->contains($notification['event_name'], 'task.move')): ?> + + text->contains($notification['event_name'], 'task.overdue')): ?> + + text->contains($notification['event_name'], 'task')): ?> + + text->contains($notification['event_name'], 'comment')): ?> + + text->contains($notification['event_name'], 'file')): ?> + + + + + url->link( + $this->text->e($notification['event_data']['task']['project_name']), + 'BoardViewController', + 'show', + array('project_id' => $notification['event_data']['task']['project_id']) + ) ?> > + + text->e($notification['event_data']['project_name']) ?> > + + + text->contains($notification['event_name'], 'task.overdue') && count($notification['event_data']['tasks']) > 1): ?> + + + url->link($notification['title'], 'WebNotificationController', 'redirect', array('notification_id' => $notification['id'], 'user_id' => $user['id'])) ?> + + +
+ dt->datetime($notification['date_creation']) ?> + modal->replaceIconLink('check', t('Mark as read'), 'WebNotificationController', 'remove', array('user_id' => $user['id'], 'notification_id' => $notification['id'], 'csrf_token' => $this->app->getToken()->getReusableCSRFToken())) ?> +
+
+ +
+ \ No newline at end of file diff --git a/app/User/Avatar/AvatarFileProvider.php b/app/User/Avatar/AvatarFileProvider.php new file mode 100644 index 0000000..932de07 --- /dev/null +++ b/app/User/Avatar/AvatarFileProvider.php @@ -0,0 +1,42 @@ +helper->url->href('AvatarFileController', 'image', array('user_id' => $user['id'], 'hash' => md5($user['avatar_path'].$size), 'size' => $size)); + $title = $this->helper->text->e($user['name'] ?: $user['username']); + return '' . $title . ''; + } + + /** + * Determine if the provider is active + * + * @access public + * @param array $user + * @return boolean + */ + public function isActive(array $user) + { + return !empty($user['avatar_path']); + } +} diff --git a/app/User/Avatar/LetterAvatarProvider.php b/app/User/Avatar/LetterAvatarProvider.php new file mode 100644 index 0000000..b9f6f2d --- /dev/null +++ b/app/User/Avatar/LetterAvatarProvider.php @@ -0,0 +1,172 @@ +helper->user->getInitials($user['name'] ?: $user['username']); + $rgb = $this->getBackgroundColor($user['name'] ?: $user['username']); + $name = $this->helper->text->e($user['name'] ?: $user['username']); + + return sprintf( + '', + $rgb[0], + $rgb[1], + $rgb[2], + $name, + $name, + $this->helper->text->e($initials) + ); + } + + /** + * Determine if the provider is active + * + * @access public + * @param array $user + * @return boolean + */ + public function isActive(array $user) + { + return true; + } + + /** + * Get background color based on a string + * + * @param string $str + * @return array + */ + public function getBackgroundColor($str) + { + $hsl = $this->getHSL($str); + return $this->getRGB($hsl[0], $hsl[1], $hsl[2]); + } + + /** + * Convert HSL to RGB + * + * @access protected + * @param integer $hue Hue ∈ [0, 360) + * @param integer $saturation Saturation ∈ [0, 1] + * @param integer $lightness Lightness ∈ [0, 1] + * @return array + */ + protected function getRGB($hue, $saturation, $lightness) + { + $hue /= 360; + $q = $lightness < 0.5 ? $lightness * (1 + $saturation) : $lightness + $saturation - $lightness * $saturation; + $p = 2 * $lightness - $q; + + return array_map(function ($color) use ($q, $p) { + if ($color < 0) { + $color++; + } + + if ($color > 1) { + $color--; + } + + if ($color < 1/6) { + $color = $p + ($q - $p) * 6 * $color; + } elseif ($color < 0.5) { + $color = $q; + } elseif ($color < 2/3) { + $color = $p + ($q - $p) * 6 * (2/3 - $color); + } else { + $color = $p; + } + + return round($color * 255); + }, array($hue + 1/3, $hue, $hue - 1/3)); + } + + /** + * Returns the hash in [h, s, l]. + * Note that H ∈ [0, 360); S ∈ [0, 1]; L ∈ [0, 1]; + * + * @access protected + * @param string $str + * @return array + */ + protected function getHSL($str) + { + $hash = $this->hash($str); + $hue = $hash % 359; + + $hash = intval($hash / 360); + $saturation = $this->saturation[$hash % count($this->saturation)]; + + $hash = intval($hash / count($this->saturation)); + $lightness = $this->lightness[$hash % count($this->lightness)]; + + return array($hue, $saturation, $lightness); + } + + /** + * BKDR Hash (modified version) + * + * @access protected + * @param string $str + * @return integer + */ + protected function hash($str) + { + $seed = 131; + $seed2 = 137; + $hash = 0; + + // Make hash more sensitive for short string like 'a', 'b', 'c' + $str .= 'x'; + $max = intval(PHP_INT_MAX / $seed2); + + for ($i = 0, $ilen = mb_strlen($str, 'UTF-8'); $i < $ilen; $i++) { + if ($hash > $max) { + $hash = intval($hash / $seed2); + } + + $hash = $hash * $seed + $this->getCharCode(mb_substr($str, $i, 1, 'UTF-8')); + } + + return $hash; + } + + /** + * Backport of Javascript function charCodeAt() + * + * @access protected + * @param string $c + * @return integer + */ + protected function getCharCode($c) + { + list(, $ord) = unpack('N', mb_convert_encoding($c, 'UCS-4BE', 'UTF-8')); + return $ord; + } +} diff --git a/app/User/DatabaseBackendUserProvider.php b/app/User/DatabaseBackendUserProvider.php new file mode 100644 index 0000000..835d90b --- /dev/null +++ b/app/User/DatabaseBackendUserProvider.php @@ -0,0 +1,43 @@ +userQuery->withFilter(new UserNameFilter($input)) + ->getQuery() + ->columns(UserModel::TABLE.'.id', UserModel::TABLE.'.username', UserModel::TABLE.'.name') + ->eq(UserModel::TABLE.'.is_active', 1) + ->asc(UserModel::TABLE.'.name') + ->asc(UserModel::TABLE.'.username') + ->findAll(); + + foreach ($users as $user) { + $result[] = new DatabaseUserProvider($user); + } + + return $result; + } +} diff --git a/app/User/DatabaseUserProvider.php b/app/User/DatabaseUserProvider.php new file mode 100644 index 0000000..99b4599 --- /dev/null +++ b/app/User/DatabaseUserProvider.php @@ -0,0 +1,143 @@ +user = $user; + } + + /** + * Return true to allow automatic user creation + * + * @access public + * @return boolean + */ + public function isUserCreationAllowed() + { + return false; + } + + /** + * Get internal id + * + * @access public + * @return integer + */ + public function getInternalId() + { + return $this->user['id']; + } + + /** + * Get external id column name + * + * @access public + * @return string + */ + public function getExternalIdColumn() + { + return ''; + } + + /** + * Get external id + * + * @access public + * @return string + */ + public function getExternalId() + { + return ''; + } + + /** + * Get user role + * + * @access public + * @return string + */ + public function getRole() + { + return empty($this->user['role']) ? '' : $this->user['role']; + } + + /** + * Get username + * + * @access public + * @return string + */ + public function getUsername() + { + return empty($this->user['username']) ? '' : $this->user['username']; + } + + /** + * Get full name + * + * @access public + * @return string + */ + public function getName() + { + return empty($this->user['name']) ? '' : $this->user['name']; + } + + /** + * Get user email + * + * @access public + * @return string + */ + public function getEmail() + { + return empty($this->user['email']) ? '' : $this->user['email']; + } + + /** + * Get external group ids + * + * @access public + * @return array + */ + public function getExternalGroupIds() + { + return array(); + } + + /** + * Get extra user attributes + * + * @access public + * @return array + */ + public function getExtraAttributes() + { + return array(); + } +} diff --git a/app/User/LdapUserProvider.php b/app/User/LdapUserProvider.php new file mode 100644 index 0000000..4b658eb --- /dev/null +++ b/app/User/LdapUserProvider.php @@ -0,0 +1,242 @@ +dn = $dn; + $this->username = $username; + $this->name = $name; + $this->email = $email; + $this->role = $role; + $this->groupIds = $groupIds; + $this->photo = $photo; + $this->language = $language; + } + + /** + * Return true to allow automatic user creation + * + * @access public + * @return boolean + */ + public function isUserCreationAllowed() + { + return LDAP_USER_CREATION; + } + + /** + * Get internal id + * + * @access public + * @return integer + */ + public function getInternalId() + { + return 0; + } + + /** + * Get external id column name + * + * @access public + * @return string + */ + public function getExternalIdColumn() + { + return 'username'; + } + + /** + * Get external id + * + * @access public + * @return string + */ + public function getExternalId() + { + return $this->getUsername(); + } + + /** + * Get user role + * + * @access public + * @return string + */ + public function getRole() + { + return $this->role; + } + + /** + * Get username + * + * @access public + * @return string + */ + public function getUsername() + { + return LDAP_USERNAME_CASE_SENSITIVE ? $this->username : strtolower($this->username); + } + + /** + * Get full name + * + * @access public + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Get user email + * + * @access public + * @return string + */ + public function getEmail() + { + return $this->email; + } + + /** + * Get groups DN + * + * @access public + * @return string[] + */ + public function getExternalGroupIds() + { + return $this->groupIds; + } + + /** + * Get extra user attributes + * + * @access public + * @return array + */ + public function getExtraAttributes() + { + $attributes = array('is_ldap_user' => 1); + + if (! empty($this->language)) { + $attributes['language'] = LanguageModel::findCode($this->language); + } + + return $attributes; + } + + /** + * Get User DN + * + * @access public + * @return string + */ + public function getDn() + { + return $this->dn; + } + + /** + * Get user photo + * + * @access public + * @return string + */ + public function getPhoto() + { + return $this->photo; + } +} diff --git a/app/User/OAuthUserProvider.php b/app/User/OAuthUserProvider.php new file mode 100644 index 0000000..718cfac --- /dev/null +++ b/app/User/OAuthUserProvider.php @@ -0,0 +1,132 @@ +user = $user; + } + + /** + * Return true to allow automatic user creation + * + * @access public + * @return boolean + */ + public function isUserCreationAllowed() + { + return false; + } + + /** + * Get internal id + * + * @access public + * @return integer + */ + public function getInternalId() + { + return 0; + } + + /** + * Get external id + * + * @access public + * @return string + */ + public function getExternalId() + { + return isset($this->user['id']) ? $this->user['id'] : ''; + } + + /** + * Get user role + * + * @access public + * @return string + */ + public function getRole() + { + return ''; + } + + /** + * Get username + * + * @access public + * @return string + */ + public function getUsername() + { + return ''; + } + + /** + * Get full name + * + * @access public + * @return string + */ + public function getName() + { + return isset($this->user['name']) ? $this->user['name'] : ''; + } + + /** + * Get user email + * + * @access public + * @return string + */ + public function getEmail() + { + return isset($this->user['email']) ? $this->user['email'] : ''; + } + + /** + * Get external group ids + * + * @access public + * @return array + */ + public function getExternalGroupIds() + { + return array(); + } + + /** + * Get extra user attributes + * + * @access public + * @return array + */ + public function getExtraAttributes() + { + return array(); + } +} diff --git a/app/User/ReverseProxyUserProvider.php b/app/User/ReverseProxyUserProvider.php new file mode 100644 index 0000000..35b4798 --- /dev/null +++ b/app/User/ReverseProxyUserProvider.php @@ -0,0 +1,188 @@ +username = $username; + $this->email = $email; + $this->fullname = $fullname; + $this->userProfile = $userProfile; + } + + /** + * Return true to allow automatic user creation + * + * @access public + * @return boolean + */ + public function isUserCreationAllowed() + { + return true; + } + + /** + * Get internal id + * + * @access public + * @return integer + */ + public function getInternalId() + { + return 0; + } + + /** + * Get external id column name + * + * @access public + * @return string + */ + public function getExternalIdColumn() + { + return 'username'; + } + + /** + * Get external id + * + * @access public + * @return string + */ + public function getExternalId() + { + return $this->username; + } + + /** + * Get user role + * + * @access public + * @return string + */ + public function getRole() + { + if (REVERSE_PROXY_DEFAULT_ADMIN === $this->username) { + return Role::APP_ADMIN; + } + + if (isset($this->userProfile['role'])) { + return $this->userProfile['role']; + } + + return Role::APP_USER; + } + + /** + * Get username + * + * @access public + * @return string + */ + public function getUsername() + { + return $this->username; + } + + /** + * Get full name + * + * @access public + * @return string + */ + public function getName() + { + return $this->fullname; + } + + /** + * Get user email + * + * @access public + * @return string + */ + public function getEmail() + { + if (REVERSE_PROXY_DEFAULT_DOMAIN !== '' && $this->email === '') { + return $this->username.'@'.REVERSE_PROXY_DEFAULT_DOMAIN; + } + + return $this->email; + } + + /** + * Get external group ids + * + * @access public + * @return array + */ + public function getExternalGroupIds() + { + return array(); + } + + /** + * Get extra user attributes + * + * @access public + * @return array + */ + public function getExtraAttributes() + { + return array( + 'is_ldap_user' => 1, + 'disable_login_form' => 1, + ); + } +} diff --git a/app/Validator/ActionValidator.php b/app/Validator/ActionValidator.php new file mode 100644 index 0000000..4ce5db4 --- /dev/null +++ b/app/Validator/ActionValidator.php @@ -0,0 +1,38 @@ +execute(), + $v->getErrors() + ); + } +} diff --git a/app/Validator/AuthValidator.php b/app/Validator/AuthValidator.php new file mode 100644 index 0000000..dbf0261 --- /dev/null +++ b/app/Validator/AuthValidator.php @@ -0,0 +1,119 @@ +executeValidators(array('validateFields', 'validateLocking', 'validateCaptcha', 'validateCredentials'), $values); + } + + /** + * Validate credentials syntax + * + * @access protected + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + protected function validateFields(array $values) + { + $v = new Validator($values, array( + new Validators\Required('username', t('The username is required')), + new Validators\MaxLength('username', t('The maximum length is %d characters', 191), 191), + new Validators\Required('password', t('The password is required')), + )); + + return array( + $v->execute(), + $v->getErrors(), + ); + } + + /** + * Validate user locking + * + * @access protected + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + protected function validateLocking(array $values) + { + $result = true; + $errors = array(); + + if ($this->userLockingModel->isLocked($values['username'])) { + $result = false; + $errors['login'] = t('Your account is locked for %d minutes', BRUTEFORCE_LOCKDOWN_DURATION); + $this->logger->error('Account locked: '.$values['username']); + } + + return array($result, $errors); + } + + /** + * Validate password syntax + * + * @access protected + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + protected function validateCredentials(array $values) + { + $result = true; + $errors = array(); + + if (! $this->authenticationManager->passwordAuthentication($values['username'], $values['password'])) { + $result = false; + $errors['login'] = t('Bad username or password'); + } + + return array($result, $errors); + } + + /** + * Validate captcha + * + * @access protected + * @param array $values Form values + * @return array + */ + protected function validateCaptcha(array $values) + { + $result = true; + $errors = array(); + + if ($this->userLockingModel->hasCaptcha($values['username']) || $this->captchaModel->isLocked($this->request->getIpAddress())) { + if (! session_exists('captcha')) { + $result = false; + } else { + $builder = new CaptchaBuilder; + $builder->setPhrase(session_get('captcha')); + $result = $builder->testPhrase(isset($values['captcha']) ? $values['captcha'] : ''); + + if (! $result) { + $errors['login'] = t('Bad username or password'); + } + } + } + + return array($result, $errors); + } +} diff --git a/app/Validator/BaseValidator.php b/app/Validator/BaseValidator.php new file mode 100644 index 0000000..6088538 --- /dev/null +++ b/app/Validator/BaseValidator.php @@ -0,0 +1,55 @@ +$method($values); + + if (! $result) { + break; + } + } + + return array($result, $errors); + } + + /** + * Common password validation rules + * + * @access protected + * @return array + */ + protected function commonPasswordValidationRules() + { + return array( + new Validators\Required('password', t('The password is required')), + new Validators\MinLength('password', t('The minimum length is %d characters', 6), 6), + new Validators\Required('confirmation', t('The confirmation is required')), + new Validators\Equals('password', 'confirmation', t('Passwords don\'t match')), + ); + } +} diff --git a/app/Validator/CategoryValidator.php b/app/Validator/CategoryValidator.php new file mode 100644 index 0000000..1d14891 --- /dev/null +++ b/app/Validator/CategoryValidator.php @@ -0,0 +1,74 @@ +commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate category modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The id is required')), + new Validators\Required('name', t('The name is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Common validation rules + * + * @access private + * @return array + */ + private function commonValidationRules() + { + return array( + new Validators\Integer('id', t('The id must be an integer')), + new Validators\Integer('project_id', t('The project id must be an integer')), + new Validators\MaxLength('name', t('The maximum length is %d characters', 191), 191) + ); + } +} diff --git a/app/Validator/ColumnMoveRestrictionValidator.php b/app/Validator/ColumnMoveRestrictionValidator.php new file mode 100644 index 0000000..99769c6 --- /dev/null +++ b/app/Validator/ColumnMoveRestrictionValidator.php @@ -0,0 +1,41 @@ +execute(), + $v->getErrors() + ); + } +} diff --git a/app/Validator/ColumnRestrictionValidator.php b/app/Validator/ColumnRestrictionValidator.php new file mode 100644 index 0000000..b1b2e5a --- /dev/null +++ b/app/Validator/ColumnRestrictionValidator.php @@ -0,0 +1,40 @@ +execute(), + $v->getErrors() + ); + } +} diff --git a/app/Validator/ColumnValidator.php b/app/Validator/ColumnValidator.php new file mode 100644 index 0000000..59d3ddf --- /dev/null +++ b/app/Validator/ColumnValidator.php @@ -0,0 +1,75 @@ +commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate column creation + * + * @access public + * @param array $values Required parameters to save an action + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateCreation(array $values) + { + $rules = array( + new Validators\Required('project_id', t('The project id is required')), + new Validators\Integer('project_id', t('This value must be an integer')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Common validation rules + * + * @access private + * @return array + */ + private function commonValidationRules() + { + return array( + new Validators\Integer('task_limit', t('This value must be an integer')), + new Validators\GreaterThan('task_limit', t('This value must be greater than %d', -1), -1), + new Validators\Required('title', t('The title is required')), + new Validators\MaxLength('title', t('The maximum length is %d characters', 191), 191), + ); + } +} diff --git a/app/Validator/CommentValidator.php b/app/Validator/CommentValidator.php new file mode 100644 index 0000000..081d48b --- /dev/null +++ b/app/Validator/CommentValidator.php @@ -0,0 +1,103 @@ +commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate comment creation + * + * @access public + * @param array $values Required parameters to save an action + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateCreation(array $values) + { + $rules = array( + new Validators\Required('task_id', t('This value is required')), + new Validators\Required('visibility', t('Visibility is required')), + new Validators\InArray('visibility', array(Role::APP_USER, Role::APP_MANAGER, Role::APP_ADMIN), t('The visibility should be an app role')) + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate comment modification + * + * @access public + * @param array $values Required parameters to save an action + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $rules = array( + new Validators\Required('id', t('This value is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Common validation rules + * + * @access private + * @return array + */ + private function commonValidationRules() + { + return array( + new Validators\Integer('id', t('This value must be an integer')), + new Validators\Integer('task_id', t('This value must be an integer')), + new Validators\Integer('user_id', t('This value must be an integer')), + new Validators\MaxLength('reference', t('The maximum length is %d characters', 191), 191), + new Validators\Required('comment', t('Comment is required')) + ); + } +} diff --git a/app/Validator/ConfigValidator.php b/app/Validator/ConfigValidator.php new file mode 100644 index 0000000..c9c102e --- /dev/null +++ b/app/Validator/ConfigValidator.php @@ -0,0 +1,40 @@ +languageModel->getLanguages()), t('This language is invalid')), + new Validators\Timezone('application_timezone', t('This timezone is invalid')), + new Validators\InArray('application_date_format', $this->dateParser->getDateFormats(true), t('Date format invalid')), + new Validators\InArray('application_time_format', $this->dateParser->getTimeFormats(), t('Time format invalid')), + new Validators\Email('mail_sender_address', t('Email address invalid')), + new Validators\InArray('mail_transport', array_keys($this->emailClient->getAvailableTransports()), t('Invalid Mail transport')), + new Validators\InArray('default_color', array_keys($this->colorModel->getList()), t('Color invalid')), + new Validators\Integer('board_highlight_period', t('This value must be an integer')), + new Validators\Integer('board_public_refresh_interval', t('This value must be an integer')), + new Validators\Integer('board_private_refresh_interval', t('This value must be an integer')), + new Validators\GreaterThanOrEqual('board_highlight_period', t('This value must be greater or equal to %d', 0), 0), + new Validators\GreaterThanOrEqual('board_public_refresh_interval', t('This value must be greater or equal to %d', 0), 0), + new Validators\GreaterThanOrEqual('board_private_refresh_interval', t('This value must be greater or equal to %d', 0), 0), + ]); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} diff --git a/app/Validator/CurrencyValidator.php b/app/Validator/CurrencyValidator.php new file mode 100644 index 0000000..4f375c5 --- /dev/null +++ b/app/Validator/CurrencyValidator.php @@ -0,0 +1,36 @@ +execute(), + $v->getErrors() + ); + } +} diff --git a/app/Validator/CustomFilterValidator.php b/app/Validator/CustomFilterValidator.php new file mode 100644 index 0000000..eb66197 --- /dev/null +++ b/app/Validator/CustomFilterValidator.php @@ -0,0 +1,74 @@ +commonValidationRules()); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate filter modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $rules = array( + new Validators\Required('id', t('Field required')), + new Validators\Integer('id', t('This value must be an integer')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} diff --git a/app/Validator/ExternalLinkValidator.php b/app/Validator/ExternalLinkValidator.php new file mode 100644 index 0000000..885fb05 --- /dev/null +++ b/app/Validator/ExternalLinkValidator.php @@ -0,0 +1,77 @@ +commonValidationRules()); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The id is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Common validation rules + * + * @access private + * @return array + */ + private function commonValidationRules() + { + return array( + new Validators\Required('url', t('Field required')), + new Validators\MaxLength('url', t('The maximum length is %d characters', 65535), 65535), + new Validators\URL('url', t('This URL is invalid')), + new Validators\Required('title', t('Field required')), + new Validators\MaxLength('title', t('The maximum length is %d characters', 65535), 65535), + new Validators\Required('link_type', t('Field required')), + new Validators\MaxLength('link_type', t('The maximum length is %d characters', 100), 100), + new Validators\Required('dependency', t('Field required')), + new Validators\MaxLength('dependency', t('The maximum length is %d characters', 100), 100), + new Validators\Integer('id', t('This value must be an integer')), + new Validators\Required('task_id', t('Field required')), + new Validators\Integer('task_id', t('This value must be an integer')), + ); + } +} diff --git a/app/Validator/GroupValidator.php b/app/Validator/GroupValidator.php new file mode 100644 index 0000000..d867801 --- /dev/null +++ b/app/Validator/GroupValidator.php @@ -0,0 +1,71 @@ +commonValidationRules()); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The id is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Common validation rules + * + * @access private + * @return array + */ + private function commonValidationRules() + { + return array( + new Validators\Required('name', t('The name is required')), + new Validators\MaxLength('name', t('The maximum length is %d characters', 191), 191), + new Validators\Unique('name', t('The name must be unique'), $this->db->getConnection(), $this->db->escapeIdentifier(GroupModel::TABLE), $this->db->escapeIdentifier('id')), + new Validators\MaxLength('external_id', t('The maximum length is %d characters', 255), 255), + new Validators\Integer('id', t('This value must be an integer')), + ); + } +} diff --git a/app/Validator/LinkValidator.php b/app/Validator/LinkValidator.php new file mode 100644 index 0000000..8e1c878 --- /dev/null +++ b/app/Validator/LinkValidator.php @@ -0,0 +1,59 @@ +db->getConnection(), LinkModel::TABLE), + new Validators\NotEquals('label', 'opposite_label', t('The labels must be different')), + )); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $v = new Validator($values, array( + new Validators\Required('id', t('Field required')), + new Validators\Required('opposite_id', t('Field required')), + new Validators\Required('label', t('Field required')), + new Validators\Unique('label', t('This label must be unique'), $this->db->getConnection(), LinkModel::TABLE), + )); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} diff --git a/app/Validator/PasswordResetValidator.php b/app/Validator/PasswordResetValidator.php new file mode 100644 index 0000000..bc816f4 --- /dev/null +++ b/app/Validator/PasswordResetValidator.php @@ -0,0 +1,95 @@ +executeValidators(array('validateFields', 'validateCaptcha'), $values); + } + + /** + * Validate modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $v = new Validator($values, $this->commonPasswordValidationRules()); + + return array( + $v->execute(), + $v->getErrors(), + ); + } + + /** + * Validate fields + * + * @access protected + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + protected function validateFields(array $values) + { + $v = new Validator($values, array( + new Validators\Required('captcha', t('This value is required')), + new Validators\Required('username', t('The username is required')), + new Validators\MaxLength('username', t('The maximum length is %d characters', 191), 191), + )); + + return array( + $v->execute(), + $v->getErrors(), + ); + } + + /** + * Validate captcha + * + * @access protected + * @param array $values Form values + * @return array + */ + protected function validateCaptcha(array $values) + { + $errors = array(); + + if (! session_exists('captcha')) { + $result = false; + } else { + $builder = new CaptchaBuilder; + $builder->setPhrase(session_get('captcha')); + $result = $builder->testPhrase(isset($values['captcha']) ? $values['captcha'] : ''); + + if (! $result) { + $errors['captcha'] = array(t('Invalid captcha')); + } + + // Invalidate captcha to avoid reuse. + session_remove('captcha'); + } + + return array($result, $errors); + } +} diff --git a/app/Validator/PredefinedTaskDescriptionValidator.php b/app/Validator/PredefinedTaskDescriptionValidator.php new file mode 100644 index 0000000..8a65822 --- /dev/null +++ b/app/Validator/PredefinedTaskDescriptionValidator.php @@ -0,0 +1,22 @@ +execute(), + $v->getErrors() + ); + } +} diff --git a/app/Validator/ProjectRoleValidator.php b/app/Validator/ProjectRoleValidator.php new file mode 100644 index 0000000..4db630d --- /dev/null +++ b/app/Validator/ProjectRoleValidator.php @@ -0,0 +1,70 @@ +commonValidationRules()); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $rules = array( + new Validators\Required('role_id', t('The id is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Common validation rules + * + * @access private + * @return array + */ + private function commonValidationRules() + { + return array( + new Validators\Required('role', t('This field is required')), + new Validators\MaxLength('role', t('The maximum length is %d characters', 100), 100), + new Validators\Required('project_id', t('This field is required')), + new Validators\Integer('project_id', t('This value must be an integer')), + new Validators\Integer('role_id', t('This value must be an integer')), + ); + } +} diff --git a/app/Validator/ProjectValidator.php b/app/Validator/ProjectValidator.php new file mode 100644 index 0000000..a0e094f --- /dev/null +++ b/app/Validator/ProjectValidator.php @@ -0,0 +1,92 @@ +db->getConnection(), ProjectModel::TABLE), + new Validators\Email('email', t('Email address invalid')), + new Validators\Unique('email', t('The project email must be unique across all projects'), $this->db->getConnection(), ProjectModel::TABLE), + ); + } + + /** + * Validate project 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) + { + if (! empty($values['identifier'])) { + $values['identifier'] = strtoupper($values['identifier']); + } + + $rules = array( + new Validators\Required('name', t('The project name is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate project modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + if (! empty($values['identifier'])) { + $values['identifier'] = strtoupper($values['identifier']); + } + + $rules = array( + new Validators\NotEmpty('name', t('This field cannot be empty')), + new Validators\Required('id', t('This value is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} diff --git a/app/Validator/SubtaskValidator.php b/app/Validator/SubtaskValidator.php new file mode 100644 index 0000000..d3e814d --- /dev/null +++ b/app/Validator/SubtaskValidator.php @@ -0,0 +1,101 @@ +commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The subtask id is required')), + new Validators\Required('task_id', t('The task id is required')), + new Validators\Required('title', t('The title is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate API modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateApiModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The subtask id is required')), + new Validators\Required('task_id', t('The task id is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Common validation rules + * + * @access private + * @return array + */ + private function commonValidationRules() + { + 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', 65535), 65535), + 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')), + new Validators\Numeric('time_spent', t('The time must be a numeric value')), + ); + } +} diff --git a/app/Validator/SwimlaneValidator.php b/app/Validator/SwimlaneValidator.php new file mode 100644 index 0000000..0df433f --- /dev/null +++ b/app/Validator/SwimlaneValidator.php @@ -0,0 +1,74 @@ +commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The id is required')), + new Validators\Required('name', t('The name is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Common validation rules + * + * @access private + * @return array + */ + private function commonValidationRules() + { + return array( + new Validators\Integer('id', t('The id must be an integer')), + new Validators\Integer('project_id', t('The project id must be an integer')), + new Validators\MaxLength('name', t('The maximum length is %d characters', 191), 191) + ); + } +} diff --git a/app/Validator/TagValidator.php b/app/Validator/TagValidator.php new file mode 100644 index 0000000..dddd076 --- /dev/null +++ b/app/Validator/TagValidator.php @@ -0,0 +1,76 @@ +commonValidationRules()); + $result = $v->execute(); + $errors = $v->getErrors(); + + if ($result && $this->tagModel->exists($values['project_id'], $values['name'])) { + $result = false; + $errors = array('name' => array(t('The name must be unique'))); + } + + return array($result, $errors); + } + + /** + * Validate modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $rules = array( + new Validators\Required('id', t('Field required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + $result = $v->execute(); + $errors = $v->getErrors(); + + if ($result && $this->tagModel->exists($values['project_id'], $values['name'], $values['id'])) { + $result = false; + $errors = array('name' => array(t('The name must be unique'))); + } + + return array($result, $errors); + } + + /** + * Common validation rules + * + * @access protected + * @return array + */ + protected function commonValidationRules() + { + return array( + new Validators\Required('project_id', t('Field required')), + new Validators\Required('name', t('Field required')), + new Validators\MaxLength('name', t('The maximum length is %d characters', 191), 191), + ); + } +} diff --git a/app/Validator/TaskLinkValidator.php b/app/Validator/TaskLinkValidator.php new file mode 100644 index 0000000..6da257b --- /dev/null +++ b/app/Validator/TaskLinkValidator.php @@ -0,0 +1,71 @@ +db->getConnection(), TaskModel::TABLE, 'id') + ); + } + + /** + * 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, $this->commonValidationRules()); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $rules = array( + new Validators\Required('id', t('Field required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} diff --git a/app/Validator/TaskValidator.php b/app/Validator/TaskValidator.php new file mode 100644 index 0000000..33d8098 --- /dev/null +++ b/app/Validator/TaskValidator.php @@ -0,0 +1,230 @@ +dateParser->getParserFormats()), + new Validators\Date('date_started', t('Invalid date'), $this->dateParser->getParserFormats()), + new Validators\Numeric('time_spent', t('This value must be numeric')), + new Validators\Numeric('time_estimated', t('This value must be numeric')), + ); + } + + public function validateStartAndDueDate(array $values) + { + if (!empty($values['date_started']) && !empty($values['date_due'])) { + $startDate = $this->dateParser->getTimestamp($values['date_started']); + $endDate = $this->dateParser->getTimestamp($values['date_due']); + + if ($startDate > $endDate) { + return array(false, array('date_started' => array(t('The start date is greater than the end date')))); + } + } + + return array(true, array()); + } + + /** + * Validate task 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) + { + $rules = array( + new Validators\Required('project_id', t('The project is required')), + new Validators\Required('title', t('The title is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + $result = $v->execute(); + $errors = $v->getErrors(); + + if ($result) { + list($result, $errors) = $this->validateStartAndDueDate($values); + } + + return array($result, $errors); + } + + /** + * Validate task creation + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateBulkCreation(array $values) + { + $rules = array( + new Validators\Required('tasks', t('Field required')), + new Validators\Required('column_id', t('Field required')), + new Validators\Required('swimlane_id', t('Field required')), + new Validators\Integer('category_id', t('This value must be an integer')), + new Validators\Integer('swimlane_id', t('This value must be an integer')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate edit recurrence + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateEditRecurrence(array $values) + { + $rules = array( + new Validators\Required('id', t('The id is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + + /** + * Validate task modification (form) + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The id is required')), + new Validators\Required('title', t('The title is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + $result = $v->execute(); + $errors = $v->getErrors(); + + if ($result) { + list($result, $errors) = $this->validateStartAndDueDate($values); + } + + return array($result, $errors); + } + + /** + * Validate task modification (Api) + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateApiModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The id is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + $result = $v->execute(); + $errors = $v->getErrors(); + + if ($result) { + list($result, $errors) = $this->validateStartAndDueDate($values); + } + + return array($result, $errors); + } + + /** + * Validate project modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateProjectModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The id is required')), + new Validators\Required('project_id', t('The project is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate task email creation + * + * @access public + * @param array $values Required parameters to save an action + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateEmailCreation(array $values) + { + $rules = array( + new Validators\Required('subject', t('This field is required')), + new Validators\Required('emails', t('This field is required')), + ); + + $v = new Validator($values, $rules); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} diff --git a/app/Validator/UserValidator.php b/app/Validator/UserValidator.php new file mode 100644 index 0000000..68772ac --- /dev/null +++ b/app/Validator/UserValidator.php @@ -0,0 +1,135 @@ +db->getConnection(), UserModel::TABLE, 'id'), + new Validators\Email('email', t('Email address invalid')), + new Validators\Integer('is_ldap_user', t('This value must be an integer')), + new Validators\InArray('theme', array_keys($this->themeModel->getThemes()), t('This theme is invalid')), + new Validators\InArray('role', array_keys($this->role->getApplicationRoles()), t('This role is invalid')), + new Validators\Timezone('timezone', t('This timezone is invalid')), + new Validators\InArray('language', array_keys($this->languageModel->getLanguages()), t('This language is invalid')), + ); + } + + /** + * Validate user 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) + { + $rules = array( + new Validators\Required('username', t('The username is required')), + ); + + if (isset($values['is_ldap_user']) && $values['is_ldap_user'] == 1) { + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + } else { + $v = new Validator($values, array_merge($rules, $this->commonValidationRules(), $this->commonPasswordValidationRules())); + } + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate user modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The user id is required')), + new Validators\Required('username', t('The username is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate user API modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateApiModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The user id is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate password modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validatePasswordModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The user id is required')), + new Validators\Required('current_password', t('The current password is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonPasswordValidationRules())); + + if ($v->execute()) { + if (! $this->userSession->isAdmin() && $values['id'] != $this->userSession->getId()) { + return array(false, array('current_password' => array('Invalid User ID'))); + } + + if ($this->authenticationManager->passwordAuthentication($this->userSession->getUsername(), $values['current_password'], false)) { + return array(true, array()); + } else { + return array(false, array('current_password' => array(t('Wrong password')))); + } + } + + return array(false, $v->getErrors()); + } +} diff --git a/app/check_setup.php b/app/check_setup.php new file mode 100644 index 0000000..e5cdc46 --- /dev/null +++ b/app/check_setup.php @@ -0,0 +1,49 @@ +isEnvironmentVariableDefined()) { + $dbSettings = $dbUrlParser->getSettings(); + + define('DB_DRIVER', $dbSettings['driver']); + define('DB_USERNAME', $dbSettings['username']); + define('DB_PASSWORD', $dbSettings['password']); + define('DB_HOSTNAME', $dbSettings['hostname']); + define('DB_PORT', $dbSettings['port']); + define('DB_NAME', $dbSettings['database']); +} + +$config_file = implode(DIRECTORY_SEPARATOR, array(__DIR__, '..', 'data', 'config.php')); + +if (file_exists($config_file)) { + require $config_file; +} + +$config_file = implode(DIRECTORY_SEPARATOR, array(__DIR__, '..', 'config.php')); + +if (file_exists($config_file)) { + require $config_file; +} + +require __DIR__.'/constants.php'; +require __DIR__.'/check_setup.php'; + +$container = new Pimple\Container; +$container->register(new Kanboard\ServiceProvider\MailProvider()); +$container->register(new Kanboard\ServiceProvider\HelperProvider()); +$container->register(new Kanboard\ServiceProvider\SessionProvider()); +$container->register(new Kanboard\ServiceProvider\LoggingProvider()); +$container->register(new Kanboard\ServiceProvider\CacheProvider()); +$container->register(new Kanboard\ServiceProvider\DatabaseProvider()); +$container->register(new Kanboard\ServiceProvider\AuthenticationProvider()); +$container->register(new Kanboard\ServiceProvider\NotificationProvider()); +$container->register(new Kanboard\ServiceProvider\ClassProvider()); +$container->register(new Kanboard\ServiceProvider\EventDispatcherProvider()); +$container->register(new Kanboard\ServiceProvider\GroupProvider()); +$container->register(new Kanboard\ServiceProvider\UserProvider()); +$container->register(new Kanboard\ServiceProvider\RouteProvider()); +$container->register(new Kanboard\ServiceProvider\ActionProvider()); +$container->register(new Kanboard\ServiceProvider\ExternalLinkProvider()); +$container->register(new Kanboard\ServiceProvider\ExternalTaskProvider()); +$container->register(new Kanboard\ServiceProvider\AvatarProvider()); +$container->register(new Kanboard\ServiceProvider\FilterProvider()); +$container->register(new Kanboard\ServiceProvider\FormatterProvider()); +$container->register(new Kanboard\ServiceProvider\JobProvider()); +$container->register(new Kanboard\ServiceProvider\QueueProvider()); +$container->register(new Kanboard\ServiceProvider\ApiProvider()); +$container->register(new Kanboard\ServiceProvider\CommandProvider()); +$container->register(new Kanboard\ServiceProvider\ObjectStorageProvider()); +$container->register(new Kanboard\ServiceProvider\PluginProvider()); diff --git a/app/constants.php b/app/constants.php new file mode 100644 index 0000000..95453fa --- /dev/null +++ b/app/constants.php @@ -0,0 +1,193 @@ + 1, 'k2' => 2], ['k1' => 3, 'k2' => 4], ['k1' => 1, 'k2' => 5] + * ] + * + * array_column_index($input, 'k1') will returns: + * + * [ + * 1 => [['k1' => 1, 'k2' => 2], ['k1' => 1, 'k2' => 5]], + * 3 => [['k1' => 3, 'k2' => 4]], + * ] + * + * @param array $input + * @param string $column + * @return array + */ +function array_column_index(array &$input, $column) +{ + $result = array(); + + foreach ($input as &$row) { + if (isset($row[$column])) { + $result[$row[$column]][] = $row; + } + } + + return $result; +} + +/** + * Create indexed array from a list of dict with unique values + * + * $input = [ + * ['k1' => 1, 'k2' => 2], ['k1' => 3, 'k2' => 4], ['k1' => 1, 'k2' => 5] + * ] + * + * array_column_index_unique($input, 'k1') will returns: + * + * [ + * 1 => ['k1' => 1, 'k2' => 2], + * 3 => ['k1' => 3, 'k2' => 4], + * ] + * + * @param array $input + * @param string $column + * @return array + */ +function array_column_index_unique(array &$input, $column) +{ + $result = array(); + + foreach ($input as &$row) { + if (isset($row[$column]) && ! isset($result[$row[$column]])) { + $result[$row[$column]] = $row; + } + } + + return $result; +} + +/** + * Sum all values from a single column in the input array + * + * $input = [ + * ['column' => 2], ['column' => 3] + * ] + * + * array_column_sum($input, 'column') returns 5 + * + * @param array $input + * @param string $column + * @return double + */ +function array_column_sum(array &$input, $column) +{ + $sum = 0.0; + + foreach ($input as &$row) { + if (isset($row[$column])) { + $sum += (float) $row[$column]; + } + } + + return $sum; +} + +/** + * Build version number from git-archive output + * + * @param string $ref + * @param string $commit_hash + * @return string + */ +function build_app_version($ref, $commit_hash) +{ + if ($ref !== '$Format:%d$') { + $tag = preg_replace('/\s*\(.*tag:\sv([^,]+).*\)/i', '\1', $ref); + + if (!is_null($tag) && $tag !== $ref) { + return $tag; + } + } + + if ($commit_hash !== '$Format:%H$') { + return 'main.'.$commit_hash; + } elseif (file_exists(__DIR__ . '/version.txt')) { + return rtrim(file_get_contents(__DIR__ . '/version.txt')); + } + + return 'main.unknown_revision'; +} + +/** + * Get upload max size. + * + * @return string + */ +function get_upload_max_size() +{ + $upload_max_filesize = convert_php_size_to_bytes(ini_get('upload_max_filesize')); + $post_max_size = convert_php_size_to_bytes(ini_get('post_max_size')); + + if ($post_max_size == 0) { + return $upload_max_filesize; + } + + if ($upload_max_filesize == 0) { + return $post_max_size; + } + + return min($post_max_size, $upload_max_filesize); +} + +/** + * Get the number of bytes from PHP size + * + * @param integer $value PHP size (example: 2M) + * @return integer + */ +function convert_php_size_to_bytes($value) +{ + // Remove the non-unit characters from the size + $unit = preg_replace('/[^bkmgtpezy]/i', '', $value); + + // Remove the non-numeric characters from the size + $size = preg_replace('/[^0-9\.]/', '', $value); + + switch (strtoupper($unit)) { + case 'G': + $size *= 1024; + // no break + case 'M': + $size *= 1024; + // no break + case 'K': + $size *= 1024; + } + + return $size; +} + +/** + * Get file extension + * + * @param $filename + * @return string + */ +function get_file_extension($filename) +{ + return strtolower(pathinfo($filename, PATHINFO_EXTENSION)); +} + +/** + * Translate a string + * + * @return string + */ +function t() +{ + return call_user_func_array(array(Translator::getInstance(), 'translate'), func_get_args()); +} + +/** + * Translate a string with no HTML escaping + * + * @return string + */ +function e() +{ + return call_user_func_array(array(Translator::getInstance(), 'translateNoEscaping'), func_get_args()); +} + +/** + * Translate a number + * + * @param mixed $value + * @return string + */ +function n($value) +{ + return Translator::getInstance()->number($value); +} + +/** + * Sanitize a file path + * + * @param string $path + * @return string|false + */ +function sanitize_path(string $path): string|false +{ + // Handle empty path + if (empty($path)) { + return false; + } + + $dirSeparator = '/'; + + // Get Windows drive letter (C:/ or C:\) + $driveLetter = ''; + if (preg_match('/^([a-zA-Z]:)([\/\\\].*)$/', $path, $matches)) { + $driveLetter = $matches[1]; + $path = $matches[2]; + $dirSeparator = '\\'; + } + + // If path is not absolute, make it relative to current working directory + if ($driveLetter === '' && substr($path, 0, 1) !== '/') { + $path = getcwd() . $dirSeparator . $path; + } + + // Split path into components + $parts = explode($dirSeparator, $path); + $resolved = []; + + foreach ($parts as $part) { + // Skip empty parts (caused by multiple slashes) + if ($part === '' || $part === '.') { + continue; + } + + // Handle parent directory + if ($part === '..') { + if (count($resolved) > 0) { + array_pop($resolved); + } + // If we're at root and encounter .., ignore it + continue; + } + + // Add normal directory/file component + $resolved[] = $part; + } + + // Reconstruct the path + $normalized = ($driveLetter !== '' ? $driveLetter . $dirSeparator : $dirSeparator) . implode($dirSeparator, $resolved); + + // Handle root case + if ($normalized === $dirSeparator) { + return $dirSeparator; + } + + return $normalized; +} diff --git a/assets/css/auto.min.css b/assets/css/auto.min.css new file mode 100644 index 0000000..746bffc --- /dev/null +++ b/assets/css/auto.min.css @@ -0,0 +1 @@ +:root{--body-background-color:#FFF;--header-background-color:#fbfbfb;--color-primary:#333;--color-light:#999;--color-lighter:#dedede;--color-dark:#000;--color-medium:#555;--color-error:#b94a48;--link-color-primary:#36C;--link-color-focus:#DF5353;--link-color-hover:#333;--alert-color-default:#c09853;--alert-color-success:#468847;--alert-color-error:#b94a48;--alert-color-info:#3a87ad;--alert-color-normal:#333;--alert-background-color-default:#fcf8e3;--alert-background-color-success:#dff0d8;--alert-background-color-error:#f2dede;--alert-background-color-info:#d9edf7;--alert-background-color-normal:#f0f0f0;--alert-border-color-default:#fbeed5;--alert-border-color-success:#d6e9c6;--alert-border-color-error:#eed3d7;--alert-border-color-info:#bce8f1;--alert-border-color-normal:#ddd;--button-default-color:#333;--button-default-background-color:#f5f5f5;--button-default-border-color:#ddd;--button-default-color-focus:#000;--button-default-background-color-focus:#fafafa;--button-default-border-color-focus:#bbb;--button-primary-color:#fff;--button-primary-background-color:#4d90fe;--button-primary-border-color:#3079ed;--button-primary-color-focus:#fff;--button-primary-background-color-focus:#357ae8;--button-primary-border-color-focus:#3079ed;--button-danger-color:#fff;--button-danger-background-color:#d14836;--button-danger-border-color:#b0281a;--button-danger-color-focus:#fff;--button-danger-background-color-focus:#c53727;--button-danger-border-color-focus:#b0281a;--button-disabled-color:#ccc;--button-disabled-background-color:#f7f7f7;--button-disabled-border-color:#ccc;--table-header-background-color:#fbfbfb;--table-nth-background-color:#fefefe;--table-border-color:#eee;--avatar-color-letter:#fff;--activity-title-color:#000;--activity-title-border-color:#efefef;--activity-event-background-color:#fafafa;--activity-event-hover-color:#fff8dc;--user-mention-color:#000;--board-task-limit-color:#DF5353;--table-list-header-border-color:#e5e5e5;--table-list-header-background-color:#fbfbfb;--table-list-nth-background-color:#fefefe;--table-list-border-color:#e5e5e5;--table-list-row-hover-border-color:#ffeb8e;--table-list-row-background-color:#fff8dc;--sidebar-border-color:#efefef;--dropdown-background-color:#fff;--dropdown-border-color:#b2b2b2;--dropdown-li-border-color:#f8f8f8;--input-addon-background-color:rgba(147,128,108,.1);--input-addon-color:#666;--views-background-color:#fafafa;--views-border-color:#ddd;--views-active-color:#000;--input-focus-color:#000;--input-focus-border-color:rgba(82,168,236,.8);--input-focus-shadow-color:rgba(82,168,236,.6);--input-background-color:#fff;--input-border-color:#ccc;--input-placeholder-color:#dedede;--tooltip-background-color:#fff;--tooltip-border-color:#ddd;--tooltip-shadow-color:#aaa;--panel-background-color:#fcfcfc;--panel-border-color:#ddd;--draggable-item-selected-background-color:#fff;--draggable-item-selected-border-color:#666;--draggable-item-hover-background-color:#FEFFF2;--draggable-row-handle-color:#dedede;--draggable-placeholder-background-color:#fafafa;--draggable-placeholder-border-color:#000;--task-list-icons-color:#999;--form-help-color:brown;--form-error-color:#b94a48;--comment-title-border-color:#eee;--comment-nth-background-color:#fbfbfb;--comment-highlighted-background-color:#fff8dc;--comment-highlighted-hover-background-color:#fff8dc;--comment-highlighted-border-color:#ffeb8e}html{color-scheme:light}@media (prefers-color-scheme:dark){:root{--body-background-color:#222;--header-background-color:#222;--color-primary:#a0a0a0;--color-light:#a0a0a0;--color-lighter:#efefef;--color-dark:#000;--color-medium:#4f4c4c;--color-error:#b94a48;--link-color-primary:#aaa;--link-color-focus:#ddd;--link-color-hover:#ddd;--alert-color-default:#efefef;--alert-color-success:#def6de;--alert-color-error:#de9393;--alert-color-info:#3a87ad;--alert-color-normal:#333;--alert-background-color-default:#333;--alert-background-color-success:#304b27;--alert-background-color-error:#500606;--alert-background-color-info:#d9edf7;--alert-background-color-normal:#f0f0f0;--alert-border-color-default:#444;--alert-border-color-success:#3c621b;--alert-border-color-error:#7e0315;--alert-border-color-info:#bce8f1;--alert-border-color-normal:#ddd;--button-default-color:#333;--button-default-background-color:#f5f5f5;--button-default-border-color:#ddd;--button-default-color-focus:#000;--button-default-background-color-focus:#fafafa;--button-default-border-color-focus:#bbb;--button-primary-color:#efefef;--button-primary-background-color:#333;--button-primary-border-color:#444;--button-primary-color-focus:#fff;--button-primary-background-color-focus:#555;--button-primary-border-color-focus:#888;--button-danger-color:#fff;--button-danger-background-color:#d14836;--button-danger-border-color:#b0281a;--button-danger-color-focus:#fff;--button-danger-background-color-focus:#c53727;--button-danger-border-color-focus:#b0281a;--button-disabled-color:#ccc;--button-disabled-background-color:#f7f7f7;--button-disabled-border-color:#ccc;--table-header-background-color:#1a1a1a;--table-nth-background-color:#2d2c2c;--table-border-color:rgba(147,128,108,.25);--avatar-color-letter:#fff;--activity-title-color:#e3e2e2;--activity-title-border-color:#efefef;--activity-event-background-color:#313131;--activity-event-hover-color:#000;--user-mention-color:#fff;--board-task-limit-color:#DF5353;--table-list-header-border-color:rgba(147,128,108,.25);--table-list-header-background-color:rgb(59,59,59);--table-list-nth-background-color:#2d2c2c;--table-list-border-color:rgba(147,128,108,.25);--table-list-row-hover-border-color:rgba(147,128,108,.25);--table-list-row-background-color:#434343;--sidebar-border-color:rgba(147,128,108,.25);--dropdown-background-color:#222;--dropdown-border-color:#000;--dropdown-li-border-color:#555;--input-addon-background-color:#1a1a1a;--input-addon-color:rgba(147,128,108,.25);--views-background-color:#1a1a1a;--views-border-color:rgba(147,128,108,.25);--views-active-color:#949494;--input-focus-color:#e6edf3;--input-focus-border-color:rgba(82,168,236,.8);--input-focus-shadow-color:rgba(82,168,236,.6);--input-background-color:rgb(59,59,59);--input-border-color:#777575;--input-placeholder-color:#666;--tooltip-background-color:#333;--tooltip-border-color:#555;--tooltip-shadow-color:#111;--panel-background-color:#2c2c2c;--panel-border-color:#000;--draggable-item-selected-background-color:#222;--draggable-item-selected-border-color:#111;--draggable-item-hover-background-color:#555;--draggable-row-handle-color:#444;--draggable-placeholder-background-color:#444;--draggable-placeholder-border-color:#666;--task-list-icons-color:#ccc;--form-help-color:#a8a12f;--form-error-color:#f2332f;--comment-title-border-color:#eee;--comment-nth-background-color:#2b2a2a;--comment-highlighted-background-color:#2b2901;--comment-highlighted-hover-background-color:#000;--comment-highlighted-border-color:#c09e05}html{color-scheme:dark}.select2-dropdown,.select2-close-mask{background-color:var(--input-background-color)}.select2-container--default .select2-selection--multiple,.select2-container--default .select2-selection--single{background-color:var(--input-background-color);border-color:var(--input-border-color)}.select2-container--default.select2-container--focus .select2-selection--multiple{border-color:var(--input-focus-border-color)}.select2-container--default .select2-selection--single .select2-selection__rendered,.select2-container--classic .select2-selection--single .select2-selection__rendered{color:#fff}.task-board-title{color:#000}.task-summary-column a,.task-summary-column a:hover{color:#000}}h1,li,ul,ol,table,tr,td,th,p,blockquote,body{margin:0;padding:0}body{background-color:var(--body-background-color);font-size:100%;padding-bottom:10px;color:var(--color-primary);font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;text-rendering:optimizeLegibility;overflow-x:hidden}small{font-size:.8em}hr{border:0;height:0;border-top:1px solid rgba(0,0,0,.1);border-bottom:1px solid rgba(255,255,255,.3)}.page{margin-left:10px;margin-right:10px}.margin-top{margin-top:20px}.margin-bottom{margin-bottom:20px}.pull-right{text-align:right;margin-left:auto}ul.no-bullet li{list-style-type:none;margin-left:0}#app-loading-icon{position:fixed;right:3px;bottom:3px}.assign-me{vertical-align:bottom}a{color:var(--link-color-primary);border:none}a:focus{color:var(--link-color-focus);outline:0;text-decoration:none}a:hover{color:var(--link-color-hover);text-decoration:none}a .fa{color:var(--color-primary);padding-right:3px;text-decoration:none}h1,h2,h3{font-weight:400;color:var(--color-primary)}h1{font-size:1.5em}h2{font-size:1.4em;margin-bottom:10px}h3{margin-top:10px;font-size:1.2em}table{width:100%;border-collapse:collapse;border-spacing:0;margin-bottom:20px}table.table-fixed{table-layout:fixed;white-space:nowrap}table.table-fixed th{overflow:hidden}table.table-fixed td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}table.table-small{font-size:.8em}table.table-striped tr:nth-child(odd){background:var(--table-nth-background-color)}@media (max-width:768px){table.table-scrolling{overflow-x:auto;display:inline-block;vertical-align:top;max-width:100%;white-space:nowrap}}table th{text-align:left;padding:.5em 3px;border:1px solid var(--table-border-color);background-color:var(--table-header-background-color)}table th a{text-decoration:none;color:var(--color-primary)}table th a:focus,table th a:hover{text-decoration:underline}table td{border:1px solid var(--table-border-color);padding:.5em 3px;vertical-align:top}table td li{margin-left:20px}table td .color-picker-square{display:inline-block;width:12px;height:12px;border:1px solid #000}.task-table a{color:#000}.column-1{width:1%}.column-2{width:2%}.column-3{width:3%}.column-4{width:4%}.column-5{width:5%}.column-6{width:6%}.column-7{width:7%}.column-8{width:8%}.column-9{width:9%}.column-10{width:10%}.column-11{width:11%}.column-12{width:12%}.column-13{width:13%}.column-14{width:14%}.column-15{width:15%}.column-16{width:16%}.column-17{width:17%}.column-18{width:18%}.column-19{width:19%}.column-20{width:20%}.column-21{width:21%}.column-22{width:22%}.column-23{width:23%}.column-24{width:24%}.column-25{width:25%}.column-26{width:26%}.column-27{width:27%}.column-28{width:28%}.column-29{width:29%}.column-30{width:30%}.column-31{width:31%}.column-32{width:32%}.column-33{width:33%}.column-34{width:34%}.column-35{width:35%}.column-36{width:36%}.column-37{width:37%}.column-38{width:38%}.column-39{width:39%}.column-40{width:40%}.column-41{width:41%}.column-42{width:42%}.column-43{width:43%}.column-44{width:44%}.column-45{width:45%}.column-46{width:46%}.column-47{width:47%}.column-48{width:48%}.column-49{width:49%}.column-50{width:50%}.column-51{width:51%}.column-52{width:52%}.column-53{width:53%}.column-54{width:54%}.column-55{width:55%}.column-56{width:56%}.column-57{width:57%}.column-58{width:58%}.column-59{width:59%}.column-60{width:60%}.column-61{width:61%}.column-62{width:62%}.column-63{width:63%}.column-64{width:64%}.column-65{width:65%}.column-66{width:66%}.column-67{width:67%}.column-68{width:68%}.column-69{width:69%}.column-70{width:70%}.column-71{width:71%}.column-72{width:72%}.column-73{width:73%}.column-74{width:74%}.column-75{width:75%}.column-76{width:76%}.column-77{width:77%}.column-78{width:78%}.column-79{width:79%}.column-80{width:80%}.column-81{width:81%}.column-82{width:82%}.column-83{width:83%}.column-84{width:84%}.column-85{width:85%}.column-86{width:86%}.column-87{width:87%}.column-88{width:88%}.column-89{width:89%}.column-90{width:90%}.column-91{width:91%}.column-92{width:92%}.column-93{width:93%}.column-94{width:94%}.column-95{width:95%}.column-96{width:96%}.column-97{width:97%}.column-98{width:98%}.column-99{width:99%}.column-100{width:100%}.draggable-row-handle{cursor:move;color:var(--draggable-row-handle-color)}.draggable-row-handle:hover{color:var(--color-primary)}tr.draggable-item-selected{background:var(--draggable-item-selected-background-color);border:2px solid var(--draggable-item-selected-border-color);box-shadow:4px 2px 10px -4px rgba(0,0,0,.55)}tr.draggable-item-selected td{border-top:none;border-bottom:none}tr.draggable-item-selected td:first-child{border-left:none}tr.draggable-item-selected td:last-child{border-right:none}.table-stripped tr.draggable-item-hover,.table-stripped tr.draggable-item-hover{background:var(--draggable-item-hover-background-color)}.table-list{font-size:.85em;margin-bottom:20px}.table-list-header{background:var(--table-list-header-background-color);border:1px solid var(--table-list-header-border-color);border-radius:5px 5px 0 0;line-height:28px;padding-left:3px;padding-right:3px}.table-list-header a{color:var(--color-primary);font-weight:500;text-decoration:none;margin-right:10px}.table-list-header a:hover,.table-list-header a:focus{color:#767676}.table-list-header .table-list-header-count{color:#767676;display:inline-block;float:left}.table-list-header .table-list-header-menu{text-align:right}.table-list-row{padding-left:3px;padding-right:3px;border-bottom:1px solid var(--table-list-border-color);border-right:1px solid var(--table-list-border-color)}.table-list-row.table-border-left{border-left:1px solid var(--table-list-border-color)}.table-list-row:nth-child(odd){background:var(--table-list-nth-background-color)}.table-list-row:last-child{border-radius:0 0 5px 5px}.table-list-row:hover{background:var(--table-list-row-background-color);border-bottom:1px solid var(--table-list-row-hover-border-color);border-right:1px solid var(--table-list-row-hover-border-color)}.table-list-row .table-list-title{font-weight:500;line-height:23px}.table-list-row .table-list-title.status-closed{text-decoration:line-through;margin-right:10px}.table-list-row .table-list-title.status-closed a{font-style:italic}.table-list-row .table-list-title a{color:var(--color-primary);text-decoration:none}.table-list-row .table-list-title a:hover,.table-list-row .table-list-title a:focus{text-decoration:underline}.table-list-row .table-list-details{color:#999;font-weight:300;line-height:20px}.table-list-row .table-list-details span{margin-left:5px}.table-list-row .table-list-details span:first-child{margin-left:0}.table-list-row .table-list-details li{display:inline;list-style-type:none}.table-list-row .table-list-details li:after{content:', '}.table-list-row .table-list-details li:last-child:after{content:''}.table-list-row .table-list-details strong{font-weight:400;color:#555}.table-list-row .table-list-details-with-icons{float:left}@media (max-width:768px){.table-list-row .table-list-details-with-icons{float:none}}.table-list-row .table-list-icons{font-size:.8em;text-align:right;line-height:30px}@media (max-width:768px){.table-list-row .table-list-icons{text-align:left;line-height:20px}}.table-list-row .table-list-icons span{margin-left:5px}.table-list-row .table-list-icons a{text-decoration:none}.table-list-row .table-list-icons a:hover{color:var(--color-primary)}.table-list-row .table-list-icons a:hover i{color:var(--color-primary)}.table-list-category{font-size:.9em;font-weight:500;color:#000;padding:1px 2px 1px 2px;border-radius:3px;background:#fcfcfc;border:1px solid #ccc}.table-list-category a{text-decoration:none;color:#000}.table-list-category a:hover{color:#36c}fieldset{border:1px solid #ddd;margin-top:10px}legend{font-weight:500;font-size:1.2em}label{cursor:pointer;display:block;margin-top:10px;font-weight:400}input,textarea{font-family:sans-serif;background-color:var(--input-background-color)}input[type="number"],input[type="date"],input[type="email"],input[type="password"],input[type="text"]:not(.input-addon-field){color:var(--color-light);border:1px solid var(--input-border-color);width:300px;max-width:95%;font-size:1em;height:25px;padding-bottom:0;padding-left:4px;-webkit-appearance:none;-moz-appearance:none}input[type="number"]::placeholder,input[type="date"]::placeholder,input[type="email"]::placeholder,input[type="password"]::placeholder,input[type="text"]:not(.input-addon-field)::placeholder{color:var(--input-placeholder-color)}input[type="number"]:focus,input[type="date"]:focus,input[type="email"]:focus,input[type="password"]:focus,input[type="text"]:focus{color:var(--input-focus-color);border-color:var(--input-focus-border-color);outline:0;box-shadow:0 0 8px var(--input-focus-shadow-color)}input[type="number"]{width:70px}input[type="text"]:not(.input-addon-field).form-numeric{width:70px}input[type="text"]:not(.input-addon-field).form-datetime,input[type="text"]:not(.input-addon-field).form-date{width:150px}input[type="text"]:not(.input-addon-field).form-input-large{width:400px}input[type="text"]:not(.input-addon-field).form-input-small{width:150px}textarea:focus{color:var(--input-focus-color);border-color:var(--input-focus-border-color);outline:0;box-shadow:0 0 8px var(--input-focus-shadow-color)}textarea{padding:4px;border:1px solid var(--input-border-color);width:400px;max-width:99%;height:200px;font-size:1em}textarea::placeholder{color:var(--input-placeholder-color)}select{font-size:1em;max-width:95%}select:focus{outline:0}select[multiple]{width:300px}.tag-autocomplete{width:400px}span.select2-container{margin-top:2px}.form-actions{padding-top:20px;clear:both}.form-required{color:red;padding-left:5px;font-weight:700}@media (max-width:480px){.form-required{display:none}}input[type="text"].form-max-width{width:100%}input.form-error,textarea.form-error{border:2px solid var(--form-error-color)}input.form-error:focus,textarea.form-error:focus{box-shadow:none;border:2px solid var(--form-error-color)}.form-errors{color:var(--form-error-color);list-style-type:none}ul.form-errors li{margin-left:0}.form-help{font-size:.8em;color:var(--form-help-color);margin-bottom:15px}.form-inline{padding:0;margin:0;border:none}.form-inline label{display:inline;padding-right:3px}.form-inline input,.form-inline select{margin:0 15px 0 0}.form-inline .form-required{display:none}.form-inline .form-actions{display:inline-block}.form-inline .js-submit-buttons-rendered{display:inline-block}.form-inline-group{display:inline}.form-columns{display:-webkit-flex;display:flex;-webkit-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;flex-wrap:wrap;-webkit-justify-content:flex-start;justify-content:flex-start}.form-columns .form-column{margin-right:25px;flex-grow:1}.form-columns fieldset{margin-top:0}.form-login{max-width:350px;margin:5% auto 0}@media (max-width:480px){.form-login{margin-left:5px}}.form-login li{margin-left:25px;line-height:25px}.form-login h2{margin-bottom:30px;font-weight:700}.reset-password{margin-top:20px;margin-bottom:20px}.reset-password a{color:var(--color-light)}.input-addon{display:flex}.input-addon-field{flex:1;font-size:1em;color:var(--color-light);margin:0;-webkit-appearance:none;-moz-appearance:none}.input-addon-field:first-child{border-radius:5px 0 0 5px}.input-addon-field:last-child{border-radius:0 5px 5px 0}.input-addon-item{background-color:var(--input-addon-background-color);color:var(--input-addon-color);font:inherit;font-weight:400}.input-addon-item:first-child{border-radius:5px 0 0 5px}.input-addon-item:last-child{border-radius:0 5px 5px 0}@media (max-width:480px){.input-addon-item .dropdown .fa-caret-down{display:none}}.input-addon-field,.input-addon-item{border:1px solid rgba(147,128,108,.25);padding:4px .75em}.input-addon-field:not(:first-child),.input-addon-item:not(:first-child){border-left:0}.input-addon .input-addon-field{flex:1 1 auto;width:1%!important}@media (max-width:400px){.input-addon-item{padding:3px}}.icon-success{color:#468847}.icon-error{color:#b94a48}.icon-fade-out{opacity:1;animation:icon-fadeout 5s linear forwards}@keyframes icon-fadeout{0%{opacity:1}100%{opacity:0}}.alert{padding:8px 35px 8px 14px;margin-top:5px;margin-bottom:5px;color:var(--alert-color-default);background-color:var(--alert-background-color-default);border:1px solid var(--alert-border-color-default);border-radius:4px}.alert-success{color:var(--alert-color-success);background-color:var(--alert-background-color-success);border-color:var(--alert-border-color-success)}.alert-error{color:var(--alert-color-error);background-color:var(--alert-background-color-error);border-color:var(--alert-border-color-error)}.alert-info{color:var(--alert-color-info);background-color:var(--alert-background-color-info);border-color:var(--alert-border-color-info)}.alert-normal{color:var(--alert-color-normal);background-color:var(--alert-background-color-normal);border-color:var(--alert-border-color-normal)}.alert ul{margin-top:10px;margin-bottom:10px}.alert li{margin-left:25px}.alert-fade-out{text-align:center;position:fixed;bottom:0;left:20%;width:60%;padding-top:5px;padding-bottom:5px;margin-bottom:0;border-width:1px 0 0;border-radius:4px 4px 0 0;z-index:9999;opacity:1;animation:fadeout 5s linear forwards}@keyframes fadeout{0%{opacity:1}100%{opacity:0;visibility:hidden}}a.btn{text-decoration:none}.btn{-webkit-appearance:none;-moz-appearance:none;font-size:1.2em;font-weight:400;cursor:pointer;display:inline-block;border-radius:2px;padding:3px 10px;margin:0;border:1px solid var(--button-default-border-color);background:var(--button-default-background-color);color:var(--button-default-color)}.btn:hover,.btn:focus{border-color:var(--button-default-border-color-focus);background:var(--button-default-background-color-focus);color:var(--button-default-color-focus)}.btn-red{border-color:var(--button-danger-border-color);background:var(--button-danger-background-color);color:var(--button-danger-color)}.btn-red:hover,.btn-red:focus{border-color:var(--button-danger-border-color-focus);background:var(--button-danger-background-color-focus);color:var(--button-danger-color-focus)}.btn-blue{border-color:var(--button-primary-border-color);background:var(--button-primary-background-color);color:var(--button-primary-color)}.btn-blue:hover,.btn-blue:focus{border-color:var(--button-primary-border-color-focus);background:var(--button-primary-background-color-focus);color:var(--button-primary-color-focus)}.btn:disabled{color:var(--button-disabled-color);border-color:var(--button-disabled-border-color);background:var(--button-disabled-background-color)}.buttons-header{font-size:.8em;margin-top:5px;margin-bottom:15px}.tooltip i.fa{cursor:pointer}.tooltip .fa-info-circle{color:var(--color-light)}#tooltip-container{padding:5px;background:var(--tooltip-background-color);border:1px solid var(--tooltip-border-color);border-radius:4px;box-shadow:0 6px 12px var(--tooltip-shadow-color);position:absolute;min-width:350px}#tooltip-container .markdown p:last-child{margin-bottom:0}#tooltip-container .tooltip-large{width:600px}h2 .dropdown ul{display:none}.dropdown{display:inline;position:relative}.dropdown ul{display:none}.dropdown-smaller{font-size:.85em}ul.dropdown-submenu-open{display:block;position:absolute;z-index:1000;min-width:285px;list-style:none;margin:3px 0 0 1px;padding:6px 0;background-color:var(--dropdown-background-color);border:1px solid var(--dropdown-border-color);border-radius:3px;box-shadow:0 1px 3px rgba(0,0,0,.15)}.dropdown-submenu-open li{display:block;margin:0;padding:8px 10px;font-size:.9em;border-bottom:1px solid var(--dropdown-li-border-color);cursor:pointer}.dropdown-submenu-open li.no-hover{cursor:default}.dropdown-submenu-open li:last-child{border:none}.dropdown-submenu-open li:not(.no-hover):hover{background:#4078C0;color:#fff}.dropdown-submenu-open li:hover a{color:#fff}.dropdown-submenu-open li:hover i{color:#fff}.dropdown-submenu-open a{text-decoration:none;color:var(--color-primary)}.dropdown-submenu-open a:focus{text-decoration:underline}.dropdown-menu-link-text,.dropdown-menu-link-icon{color:var(--color-primary);text-decoration:none}.dropdown-menu-link-icon{display:inline-flex}.dropdown-menu-link-text:hover{text-decoration:underline}td a.dropdown-menu strong{color:var(--color-primary)}td a.dropdown-menu strong i{color:var(--color-primary)}td a.dropdown-menu i{color:#dedede}td a.dropdown-menu:hover strong{color:#555}td a.dropdown-menu:hover strong i{color:#555}td a.dropdown-menu:hover i{color:#333}.accordion-title{font-size:1.2em;cursor:pointer;margin-top:10px}.accordion-content{margin-top:15px;margin-bottom:25px}#select-dropdown-menu{position:absolute;display:block;z-index:1000;min-width:160px;padding:5px 0;background:var(--dropdown-background-color);list-style:none;border:1px solid var(--dropdown-border-color);border-radius:3px;box-shadow:0 6px 12px rgba(0,0,0,.175);overflow:scroll}.select-dropdown-menu-item{white-space:nowrap;overflow:hidden;padding:3px 10px;color:var(--color-medium);cursor:pointer;border-bottom:1px solid var(--dropdown-li-border-color);line-height:1.5em;font-weight:400}.select-dropdown-menu-item.active{color:#fff;background:#428bca}.select-dropdown-menu-item:last-child{border:none}.select-dropdown-input-container{position:relative;border:1px solid var(--input-border-color);border-radius:5px;background-color:var(--input-background-color);max-width:300px}.select-dropdown-input-container input.select-dropdown-input{margin:0 0 0 5px;border:none;height:23px;width:270px}.select-dropdown-input-container input.select-dropdown-input:focus{border:none;box-shadow:none}.select-dropdown-input-container .select-dropdown-chevron{color:var(--color-medium);position:absolute;top:4px;right:5px;cursor:pointer}.select-dropdown-input-container .select-loading-icon{color:var(--color-medium);position:absolute;top:4px;right:5px}#suggest-menu{position:absolute;display:block;z-index:1000;min-width:160px;padding:5px 0;background:#fff;list-style:none;border:1px solid #ccc;border-radius:3px;box-shadow:0 6px 12px rgba(0,0,0,.175)}.suggest-menu-item{white-space:nowrap;padding:3px 10px;color:var(--color-primary);font-weight:700;cursor:pointer}.suggest-menu-item.active{color:#fff;background:#428bca}.suggest-menu-item.active small{color:#fff}.suggest-menu-item small{color:var(--color-light);font-weight:400}#modal-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.9);overflow:auto;z-index:100}#modal-box{position:fixed;max-height:calc(100% - 30px);top:2%;left:50%;transform:translateX(-50%);background:var(--body-background-color);overflow:auto;border-radius:5px}#modal-content{padding:0 5px 5px}#modal-header{text-align:right;padding-right:5px}#modal-close-button{color:var(--color-primary)}#modal-close-button:hover{color:var(--color-error)}.pagination{text-align:center;font-size:.9em}.pagination-showing{margin-right:5px;padding-right:5px;border-right:1px solid #999}.pagination-next{margin-left:5px}.pagination-previous{margin-right:5px}header{display:flex;flex-wrap:wrap;padding:5px 10px;margin-bottom:5px;border-bottom:1px solid #dedede;background-color:var(--header-background-color)}header .title-container{flex:1;min-width:300px}@media (max-width:480px){header .title-container{order:3}}header .board-selector-container{min-width:320px;display:flex;align-items:center}@media (max-width:480px){header .board-selector-container{order:2;min-width:300px}header .board-selector-container input[type=text]{max-width:280px}}header .menus-container{min-width:120px;display:flex;align-items:center;justify-content:flex-end}@media (max-width:480px){header .menus-container{order:1;margin-bottom:5px;margin-left:auto}}header h1{font-size:1.5em}header h1 .tooltip{opacity:.3;font-size:.7em}a i.web-notification-icon{color:var(--link-color-primary)}a i.web-notification-icon:focus,a i.web-notification-icon:hover{color:#000}.logo a{opacity:.5;color:#d40000;text-decoration:none}.logo span{color:var(--color-primary)}.logo a:hover{opacity:.8;color:var(--color-primary)}.logo a:focus span,.logo a:hover span{color:#d40000}.page-header{margin-bottom:20px}.page-header .dropdown{padding-right:10px}.page-header h2{margin:0;padding:0;font-weight:700;border-bottom:1px dotted #ccc}.page-header h2 a{color:var(--color-primary);text-decoration:none}.page-header h2 a:focus,.page-header h2 a:hover{color:var(--color-light)}.page-header ul{text-align:left;margin-top:5px;display:inline-block}.page-header li{display:inline;padding-right:15px}@media (max-width:480px){.page-header li{display:block;line-height:1.5em}}.page-header li.active a{color:var(--color-primary);text-decoration:none;font-weight:700}.page-header li.active a:hover,.page-header li.active a:focus{text-decoration:underline}.menu-inline{margin-bottom:5px}.menu-inline li{display:inline;padding-right:15px}.menu-inline li .active a{font-weight:700;color:#000;text-decoration:none}.sidebar-container{height:100%;display:flex;flex-flow:row}@media (max-width:768px){.sidebar-container{flex-flow:wrap}}.sidebar-content{padding-left:10px;flex:1 100%;max-width:85%;overflow-wrap:break-word}@media (max-width:768px){.sidebar-content{padding-left:0;order:1;max-width:100%}}@media only screen and (min-device-width:768px) and (max-device-width:1024px) and (orientation:landscape) and (-webkit-min-device-pixel-ratio:1){.sidebar-content{max-width:75%}}.sidebar{max-width:25%;min-width:230px}@media (max-width:768px){.sidebar{flex:1 auto;order:2}}.sidebar h2{margin-top:0}.sidebar>ul a{text-decoration:none;color:var(--color-light);font-weight:300}.sidebar>ul a:hover{color:var(--color-primary)}.sidebar>ul li{list-style-type:none;line-height:35px;border-bottom:1px dotted var(--sidebar-border-color);padding-left:13px}.sidebar>ul li:hover{border-left:5px solid #555;padding-left:8px}.sidebar>ul li.active{border-left:5px solid #333;padding-left:8px}.sidebar>ul li.active a{color:var(--color-primary);font-weight:400}.sidebar-icons>ul li{padding-left:0}.sidebar-icons>ul li:hover,.sidebar-icons>ul li.active{padding-left:0;border-left:none}.sidebar>ul li.active a:focus,.sidebar>ul li.active a:hover{color:var(--color-medium)}.sidebar>ul li:last-child{margin-bottom:15px}.avatar img{vertical-align:bottom}.avatar-left{float:left;margin-right:10px}.avatar-inline{display:inline-block;margin-right:3px}.avatar-48 img,.avatar-48 div{border-radius:30px}.avatar-48 .avatar-letter{line-height:48px;width:48px;font-size:25px}.avatar-20 img,.avatar-20 div{border-radius:10px}.avatar-20 .avatar-letter{line-height:20px;width:20px;font-size:11px}.avatar-letter{color:var(--avatar-color-letter);text-align:center}#file-dropzone,#screenshot-zone{position:relative;border:2px dashed #ccc;width:99%;height:250px;overflow:auto}#file-dropzone-inner,#screenshot-inner{position:absolute;left:0;bottom:48%;width:100%;text-align:center;color:#aaa}#screenshot-zone.screenshot-pasted{border:2px solid #333}#file-list{margin:20px}#file-list li{list-style-type:none;padding-top:8px;padding-bottom:8px;border-bottom:1px dotted #ddd;width:95%}#file-list li .file-error{font-weight:700;color:#b94a48}.file-thumbnails{display:-webkit-flex;display:flex;-webkit-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;flex-wrap:wrap;-webkit-justify-content:flex-start;justify-content:flex-start}.file-thumbnail{width:250px;border:1px solid #efefef;border-radius:5px;margin-bottom:20px;box-shadow:4px 2px 10px -6px rgba(0,0,0,.55);margin-right:15px}.file-thumbnail img{cursor:pointer;border-top-left-radius:5px;border-top-right-radius:5px}.file-thumbnail img:hover{opacity:.5}.file-thumbnail-content{padding-left:8px;padding-right:8px}.file-thumbnail-title{font-weight:700;font-size:.9em;color:var(--color-medium);overflow:hidden;text-overflow:ellipsis}.file-thumbnail-description{font-size:.8em;color:var(--color-light);margin-top:8px;margin-bottom:5px}.file-viewer{position:relative}.file-viewer img{max-width:95%;max-height:85%;margin-top:10px}.color-picker{width:220px}.color-picker-option{height:25px}.color-picker-square{display:inline-block;width:18px;height:18px;margin-right:5px;border:1px solid #000}.color-picker-label{display:inline-block;vertical-align:bottom;padding-bottom:3px}.filter-box{max-width:100%}.action-menu{color:var(--color-primary);text-decoration:none}.action-menu:hover,.action-menu:focus{text-decoration:underline}.js-project-creation-options{max-width:500px;border-left:3px dotted #efefef;margin-top:20px;padding-left:15px;padding-bottom:5px;padding-top:5px}.project-overview-columns{display:-webkit-flex;display:flex;-webkit-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;flex-wrap:wrap;-webkit-align-items:center;align-items:center;-webkit-justify-content:center;justify-content:center;margin-bottom:20px;font-size:1.4em}@media (max-width:480px){.project-overview-columns{display:block}}.project-overview-column{text-align:center;margin-right:3%;margin-top:5px;padding:3px 15px 3px 15px;border:1px dashed #ddd}@media (max-width:480px){.project-overview-column{text-align:left}}.project-overview-column small{color:var(--color-light)}.project-overview-column strong{color:var(--color-medium);display:block}@media (max-width:480px){.project-overview-column strong{display:inline}}.project-header{margin-bottom:8px}.project-header .dropdown-component{margin-top:4px;margin-right:5px;float:left}@media (max-width:768px){.project-header .dropdown-component{float:none}}.project-header .views-switcher-component{margin-top:4px;margin-bottom:10px;float:left}@media (max-width:768px){.project-header .views-switcher-component{float:none;margin-bottom:10px}}.project-header .filter-box-component form{margin:0}.views{margin-right:10px;margin-top:1px;font-size:.9em}@media (max-width:560px){.views{width:100%}}@media (max-width:768px){.views{margin-top:10px;font-size:1em}}@media (max-width:480px){.views{margin-top:5px}}.views li{white-space:nowrap;background:var(--views-background-color);border:1px solid var(--views-border-color);border-right:none;padding:4px 8px;display:inline}@media (max-width:560px){.views li{display:block;margin-top:5px;border-radius:5px;border:1px solid var(--views-border-color)}}.views li.active a{font-weight:700;color:var(--views-active-color);text-decoration:none}.views li:first-child{border-top-left-radius:5px;border-bottom-left-radius:5px}.views li:last-child{border-right:1px solid var(--views-border-color);border-top-right-radius:5px;border-bottom-right-radius:5px}.views a{color:var(--color-ligth);text-decoration:none}.views a:hover{color:var(--color-primary);text-decoration:underline}.dashboard-project-stats small{margin-right:10px;color:var(--color-light)}.dashboard-table-link{font-weight:700;color:#000;text-decoration:none}.dashboard-table-link:focus,.dashboard-table-link:hover{color:var(--color-light)}.public-board{margin-top:5px}.public-task{max-width:800px;margin:5px auto 0}#board-container{overflow-x:auto}#board{table-layout:fixed;margin-bottom:0}#board tr.board-swimlane-columns-first{visibility:hidden;padding:0}#board th.board-column-header{width:240px}#board th.board-column-header-first{visibility:hidden;padding:0}#board td{vertical-align:top}.board-container-compact{overflow-x:initial}@media all and (-ms-high-contrast:active),(-ms-high-contrast:none){.board-container-compact #board{table-layout:auto}}#board th.board-column-header.board-column-compact{width:initial}.board-column-collapsed{display:none}.board-column-expanded-header{display:flex;align-items:center}td.board-column-task-collapsed{font-weight:700;background-color:var(--table-header-background-color)}#board th.board-column-header-collapsed{width:28px;min-width:28px;text-align:center;overflow:hidden}.board-rotation-wrapper{position:relative;padding:8px 4px;min-height:150px;overflow:hidden}.board-rotation{white-space:nowrap;transform:rotate(90deg);transform-origin:0 100%}.board-column-title .dropdown-menu{text-decoration:none}.board-add-icon{float:left;padding:0 5px}.board-add-icon i{text-decoration:none;color:var(--link-color-primary);font-size:1.4em}.board-add-icon i:focus,.board-add-icon i:hover{text-decoration:none;color:red}.board-column-header-task-count{color:var(--color-light);font-weight:400;font-size:.85em}a.board-swimlane-toggle{text-decoration:none}a.board-swimlane-toggle:hover,a.board-swimlane-toggle:focus{color:#000;text-decoration:none;border:none}.board-task-list{min-height:60px}.board-task-list-compact{max-height:90vh;overflow-y:auto}.board-task-list .task-board:last-child{margin-bottom:0}.board-task-list-limit{background-color:var(--board-task-limit-color)}.draggable-item{cursor:pointer;user-select:none;-webkit-user-select:none;-moz-user-select:none}.draggable-placeholder{border:2px dashed var(--draggable-placeholder-border-color);background:var(--draggable-placeholder-background-color);height:70px;margin-bottom:10px}div.draggable-item-selected{border:1px solid #000}.task-board-sort-handle{float:left;padding-right:5px}.task-board{position:relative;margin-bottom:4px;border:1px solid #000;padding:2px;word-wrap:break-word;font-size:.9em;border-radius:6px}div.task-board-recent{border-width:2px}div.task-board-status-closed{user-select:none;border:1px dotted #555}.task-board a{color:#000;text-decoration:none}.task-board-collapsed{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.task-board-title{margin-top:5px;margin-bottom:8px}.task-board-title a:hover{text-decoration:underline}.task-board-saving-state{opacity:.3}.task-board-saving-icon{position:absolute;margin:auto;width:100%;text-align:center;color:#000}.task-board-avatars{text-align:right;float:right}.task-board-change-assignee{cursor:pointer}.task-board-change-assignee:hover{opacity:.6}.task-list-avatars{display:inline-block;float:left}@media (max-width:768px){.task-list-avatars{float:none;display:block}}.task-list-avatars .task-avatar-assignee{font-weight:300;color:#999}.task-list-avatars:hover .task-avatar-assignee{font-weight:400;color:#000}.task-board-icons,.task-list-icons{font-size:.8em;text-align:right}.task-board-icons a,.task-board-icons span.tooltip,.task-list-icons a,.task-list-icons span.tooltip{text-decoration:none}.task-board-icons a:hover,.task-board-icons span.tooltip:hover,.task-list-icons a:hover,.task-list-icons span.tooltip:hover{color:var(--color-primary)}.task-board-icons a:hover i,.task-board-icons span.tooltip:hover i,.task-list-icons a:hover i,.task-list-icons span.tooltip:hover i{color:var(--color-primary)}.task-board-icons .task-score,.task-list-icons .task-score{font-weight:700}.task-board-icons .flag-milestone,.task-list-icons .flag-milestone{color:green}.task-board-icons{margin-top:7px}.task-board-icons a{opacity:.5}.task-board-icons span{opacity:.5;margin-left:4px}.task-board-icons a:hover,.task-board-icons span.tooltip:hover{opacity:1;font-weight:700}.task-board-icons .task-board-icons-row{line-height:22px}.task-list-icons{line-height:22px}.task-list-icons a,.task-list-icons span,.task-list-icons i{color:var(--task-list-icons-color);opacity:1}.task-list-icons span{margin-left:5px}@media (max-width:768px){.task-list-icons{text-align:left}}.task-icon-age{display:inline-block}span.task-icon-age-total{border:1px solid #e5e5e5;padding:1px 3px 1px 3px;border-top-left-radius:3px;border-bottom-left-radius:3px}span.task-icon-age-column{border:1px solid #e5e5e5;border-left:none;margin-left:-5px;padding:1px 3px 1px 3px;border-top-right-radius:3px;border-bottom-right-radius:3px}.task-board span.task-icon-age-total,.task-board span.task-icon-age-column{border-color:#666}.task-board-category-container{text-align:right;margin-top:8px;margin-bottom:8px}.task-board-category{border:1px solid #555;font-size:.9em;font-weight:500;color:#000;padding:1px 3px 1px 2px;border-radius:3px}.task-board-category a:hover{text-decoration:underline}.task-date{font-weight:500;color:#000}span.task-date-today{opacity:1;color:var(--link-color-primary)}span.task-date-overdue{opacity:1;color:#b94a48}.task-tags li{display:inline-block;margin:3px 3px 0 0;padding:1px 3px 1px 3px;color:var(--color-primary);border:1px solid #333;border-radius:4px}.task-summary-container .task-tags{margin-top:10px}#task-summary{margin-bottom:15px}#task-summary h2{color:var(--color-medium);font-size:1.6em;margin-top:0;padding-top:0}.task-summary-container{border:2px solid #000;border-radius:8px;padding:10px}.task-summary-columns{display:flex;flex-flow:row;justify-content:space-between}@media (max-width:768px){.task-summary-columns{flex-flow:column}}.task-summary-column{color:var(--color-dark)}.task-summary-column span{color:var(--color-medium)}.task-summary-column li{line-height:23px}#external-task-view{padding:10px;margin-top:10px;margin-bottom:10px;border:1px dotted #ccc}.task-form-container{box-sizing:border-box;display:flex;flex-wrap:wrap}.task-form-container>*{box-sizing:border-box}.task-form-container>*{width:1%}.task-form-main-column{width:60%}@media (max-width:1000px){.task-form-main-column{width:100%}}.task-form-main-column input[type="text"]{width:700px;max-width:99%}.task-form-secondary-column{max-width:250px;min-width:200px;max-height:600px;padding-left:10px;overflow:auto;width:20%}@media (max-width:1000px){.task-form-secondary-column{width:100%;max-width:99%;max-height:none}}@media (max-width:768px){.task-form-secondary-column{padding-left:0}}.task-form-secondary-column label:first-child{margin-top:0}@media (max-width:1000px){.task-form-secondary-column label:first-child{margin-top:10px}}.task-form-bottom{width:100%}.task-form-bottom label{display:inline-block}.task-form-bottom-column{display:inline-block;width:49%;margin-left:5px;margin-right:5px}.comment-sorting{text-align:right}.comment-sorting a{color:var(--color-medium);font-weight:400;text-decoration:none}.comment-sorting a:hover{color:var(--color-light)}.comment{padding:5px;margin-bottom:15px}.comment-title{border-bottom:1px dotted var(--comment-title-border-color);margin-left:55px}.comment-date{color:var(--color-light);font-weight:200}.comment-visibility{color:var(--color-light);font-weight:200}.comment-actions{text-align:right}.comment-content{margin-left:55px}.comments .text-editor textarea{height:90px}.comments .text-editor .text-editor-preview-area{height:90px}.comments .comment-highlighted{background-color:var(--comment-highlighted-background-color);border:2px solid var(--comment-highlighted-border-color)}.comments .comment-highlighted:hover{background-color:var(--comment-highlighted-hover-background-color)}.comments .comment:hover{background:var(--comment-highlighted-hover-background-color)}.comments .comment:nth-child(even):not(.comment-highlighted){background:var(--comment-nth-background-color)}.comments .comment:nth-child(even):not(.comment-highlighted):hover{background:var(--comment-highlighted-hover-background-color)}.subtask-cell{padding:4px 10px;border-top:1px dotted #dedede;border-left:1px dotted #dedede;display:table-cell;vertical-align:middle}.subtask-cell a{color:var(--color-primary);text-decoration:none}.subtask-cell a:hover,.subtask-cell a:focus{color:var(--link-color-primary)}.subtask-cell:first-child{border-left:none}@media (max-width:768px){.subtask-cell{width:90%;display:block;border-left:none}}.subtasks-table .subtask-table-td{display:flex;white-space:normal;min-width:400px}.subtasks-table .subtask-submenu{display:flex}.js-subtask-toggle-status{display:flex;text-decoration:none}.task-list-subtasks{display:table;width:100%}@media (max-width:768px){.task-list-subtasks{display:block}}.task-list-subtask{display:table-row}@media (max-width:768px){.task-list-subtask{display:block}}@media (max-width:768px){.subtask-assignee,.subtask-time-tracking-cell{display:none}}.subtask-time-tracking{white-space:normal}.task-links-table td{vertical-align:middle}.task-links-task-count{color:var(--color-light);font-weight:400}.task-link-closed{text-decoration:line-through}.task-links-table-td{display:flex}.text-editor{margin-top:10px}.text-editor a{font-size:1em;color:var(--color-light);text-decoration:none;margin-right:10px}.text-editor a:hover{color:var(--link-color-primary)}.text-editor .text-editor-preview-area{border:1px solid #dedede;width:700px;max-width:99%;height:250px;overflow:auto;padding:2px}.text-editor textarea{width:700px;max-width:98%;height:250px}.markdown{line-height:1.4em}.markdown h1{margin-top:5px;margin-bottom:10px;font-weight:700}.markdown h2{font-weight:700}.markdown p{margin-bottom:10px}.markdown ol,.markdown ul{margin-left:25px;margin-top:10px;margin-bottom:10px}.markdown pre{background:#fbfbfb;padding:10px;border-radius:5px;border:1px solid #ddd;overflow:auto;overflow-wrap:initial;color:var(--color-medium)}.markdown blockquote{font-style:italic;border-left:3px solid #ddd;padding-left:10px;margin-bottom:10px;margin-left:20px}.markdown img{display:block;max-width:80%;margin-top:10px}.panel{border-radius:4px;padding:8px 35px 8px 10px;margin-top:10px;margin-bottom:15px;border:1px solid var(--panel-border-color);color:var(--color-primary);background-color:var(--panel-background-color);overflow:auto}.panel li{list-style-type:square;margin-left:20px;line-height:1.35em}.activity-event{margin-bottom:15px;padding:10px}.activity-event:nth-child(even){background:var(--activity-event-background-color)}.activity-event:hover{background:var(--activity-event-hover-color)}.activity-date{margin-left:10px;font-weight:400;color:var(--color-light)}.activity-content{margin-left:55px}.activity-title{font-weight:700;color:var(--activity-title-color);border-bottom:1px dotted var(--activity-title-border-color)}.activity-description{color:var(--color-light);margin-top:10px}@media (max-width:480px){.activity-description{overflow:auto}}.activity-description li{list-style-type:circle}.activity-description ul{margin-top:10px;margin-left:20px}.user-mention-link{font-weight:700;color:var(--user-mention-color);text-decoration:none}.user-mention-link:hover{color:var(--color-medium)}.image-slideshow-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.95);overflow:auto;z-index:100}.image-slideshow-overlay img{display:block;margin:auto}.image-slideshow-overlay figcaption{color:#fff;opacity:.7;position:absolute;bottom:5px;right:15px}.slideshow-icon{color:#fff;position:absolute;font-size:2.5em;opacity:.6}.slideshow-icon:hover{opacity:.9;cursor:pointer}.slideshow-previous-icon{left:10px;top:45%}.slideshow-next-icon{right:10px;top:45%}.slideshow-close-icon{right:10px;top:10px;font-size:1.4em}.slideshow-download-icon{left:10px;bottom:10px;font-size:1.3em}.list-item-links,.list-item-actions{display:inline-block;float:left;margin-left:10px}.list-item-links a{margin:0}.list-item-action-hidden{display:none}.bulk-change-checkbox{float:left}.bulk-change-inputs{float:left;padding-left:10px}.bulk-change-inputs label{margin-top:0;margin-bottom:3px} \ No newline at end of file diff --git a/assets/css/custom_dashboard.css b/assets/css/custom_dashboard.css new file mode 100644 index 0000000..734088b --- /dev/null +++ b/assets/css/custom_dashboard.css @@ -0,0 +1,834 @@ +/* custom_dashboard.css */ + +/* Global Dashboard Background */ +body { + background: var(--login-bg) !important; + color: var(--login-text-color) !important; +} + +/* Header & Title Stack Restructuring */ +.header-left { + display: flex; + align-items: center; +} + +.header-left .logo { + font-size: 32px; + margin-right: 15px; + line-height: 1; +} + +.header-titles-stack { + display: flex; + flex-direction: column; + justify-content: center; +} + +.header-titles-stack .title { + font-size: 15px; + font-weight: 600; + color: var(--login-text-color); + margin-bottom: 2px; + line-height: 1; +} + +/* Custom Board Selector */ +.custom-board-selector { + margin: 0; + line-height: 1; +} + +.custom-board-selector .board-selector-btn { + display: inline-block; + padding: 4px 12px; + border-radius: 6px; /* Match standard dropdown shapes better */ + background: rgba(255, 255, 255, 0.5) !important; + color: #333 !important; + font-size: 13px; + border: 1px solid rgba(0,0,0,0.15); + text-decoration: none; + transition: all 0.2s; + width: 140px; /* Fixed width to align with menu */ + box-sizing: border-box; + text-align: left; +} + +.custom-board-selector .board-selector-btn i.fa-caret-down { + float: right; + margin-top: 2px; +} + +.custom-board-selector .board-selector-btn:hover { + background: rgba(0, 0, 0, 0.05) !important; +} + +ul.custom-board-selector-menu { + overflow: hidden !important; /* Strictly no scrollbars */ + border-radius: 6px !important; + box-shadow: 0 4px 15px rgba(0,0,0,0.15) !important; + border: 1px solid rgba(0,0,0,0.15) !important; + background: #fff !important; + padding: 4px 0 !important; + width: 140px !important; /* Fixed width to match button */ + min-width: 140px !important; + margin-top: 8px !important; /* Increased spacing to prevent overlap */ + white-space: nowrap !important; /* Prevent text wrapping so it shrinks to fit */ +} + +/* Fix Kanboard native li hover nesting */ +ul.custom-board-selector-menu li { + padding: 0 !important; + margin: 0 !important; + background: transparent !important; + border: none !important; +} + +ul.custom-board-selector-menu li:hover { + background: transparent !important; +} + +/* Style the links inside the dropdown beautifully */ +ul.custom-board-selector-menu li a { + padding: 8px 12px !important; /* More compact vertically */ + color: #333 !important; + text-decoration: none !important; + display: block !important; + font-size: 13px !important; /* Slightly smaller font */ + transition: background 0.2s, color 0.2s !important; + border-radius: 0 !important; /* Override default Kanboard link styling if needed */ + box-sizing: border-box !important; + width: 100% !important; +} + +ul.custom-board-selector-menu li a:hover { + background: var(--login-btn-bg) !important; + color: var(--login-btn-text) !important; +} + +/* Custom divider */ +ul.custom-board-selector-menu li hr { + margin: 4px 0 !important; + border-top: 1px solid rgba(0,0,0,0.08) !important; + border-bottom: none !important; +} + +/* Sidebar restructuring */ +.sidebar-container { + display: flex; + margin-top: 20px; +} + +.sidebar { + border-right: none !important; + width: 160px; /* Slimmer fixed width, no longer 250px */ + padding-right: 20px; +} + +.sidebar ul { + border: none !important; +} + +.sidebar ul li { + margin-bottom: 5px; + border-radius: 12px; + transition: all 0.2s ease; + border-left: none !important; +} + +.sidebar ul li:hover, .sidebar ul li.active { + background: rgba(255, 224, 102, 0.15) !important; + border-left: none !important; +} + +.sidebar ul li a { + position: relative; + display: block; + padding: 10px 0; /* Remove horizontal padding so text centers relative to the full width */ + color: var(--login-text-color) !important; + font-weight: 500; + text-align: center; + border-radius: 8px; +} + +.sidebar ul li a i { + position: absolute; + left: 15px; /* Fixed position on the left */ + top: 50%; + transform: translateY(-50%); + width: 20px; + text-align: center; + margin: 0; /* Remove previous margins */ +} + +.sidebar-text { + display: inline-block; + width: 5em; /* Exactly 5 characters wide */ + text-align: center; /* Center the text inside the 5-char block */ + margin: 0 auto; /* Center the block inside the a tag */ +} + +.sidebar ul li.active a { + color: #f39c12 !important; + font-weight: 600; +} + +/* Page Header links */ +.page-header { + border-bottom: 1px solid var(--login-icon-color); + padding-bottom: 15px; + margin-bottom: 20px; +} +.page-header ul { + border: none !important; +} +.page-header ul li { + margin-right: 10px !important; +} +.page-header ul li a { + border-radius: 50px; + padding: 6px 15px; + transition: all 0.2s; + color: var(--login-text-color) !important; +} +.page-header ul li a:hover { + background: rgba(255, 224, 102, 0.2); +} + +/* Cards and Tables (Overview) */ +.sidebar-content .table-list { + background: var(--login-card-bg); + border-radius: 16px; + box-shadow: var(--login-shadow); + border: none !important; + overflow: hidden; + margin-top: 15px; +} + +.table-list-header { + background: var(--login-input-bg) !important; + border: none !important; + padding: 15px 20px !important; + color: var(--login-text-color); +} + +.table-list-row { + border: none !important; + border-bottom: 1px solid var(--login-icon-color) !important; + padding: 15px 20px !important; + transition: background 0.2s; + background: var(--login-card-bg); +} + +.table-list-row:hover { + background: var(--login-input-bg) !important; +} + +.table-list-row:last-child { + border-bottom: none !important; +} + +/* Empty state */ +.alert { + border-radius: 12px; + padding: 20px !important; + border: none !important; + box-shadow: 0 4px 15px rgba(0,0,0,0.02); +} + +.alert-info { + background: rgba(255, 224, 102, 0.15) !important; + color: #d35400 !important; + text-align: center; + font-weight: 500; +} + +/* Filter bar / Search */ +.filter-box { + background: var(--login-card-bg); + border-radius: 50px; + box-shadow: var(--login-shadow); + padding: 5px 20px; + display: flex; + align-items: center; + border: 1px solid var(--login-icon-color); +} + +.filter-box .form-input-group { + flex-grow: 1; +} + +.filter-box input[type="text"] { + border: none !important; + background: transparent !important; + box-shadow: none !important; + width: 100%; + color: var(--login-input-text); +} + +/* Header general styling */ +header { + background: var(--login-card-bg) !important; + box-shadow: var(--login-shadow); + padding: 12px 15px !important; + border-bottom: none !important; + display: flex; + justify-content: space-between; + align-items: center; +} + +.logo-container { + flex: 0 0 auto; + display: flex; + justify-content: flex-start; + align-items: center; + margin-right: 20px; + font-size: 1.8em; /* Enlarge logo */ +} + +.title-container { + flex: 1; + display: flex; + justify-content: flex-start; + align-items: center; + font-size: 1.5em; /* Enlarge title */ + font-weight: 500; +} + +.menus-container { + flex: 0 0 auto; + display: flex; + justify-content: flex-end; + align-items: center; + font-size: 1.2em; /* Enlarge buttons */ + gap: 10px; /* Add spacing between buttons if they don't have it */ +} + +.header-center-description { + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; + font-size: 15px; /* Enlarge info text */ + color: #666; + margin: 0 0 0 15px; /* Stick close to title container */ + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-height: 40px; /* Prevent header stretching */ + font-weight: normal; /* Override title font weight */ +} + +.header-center-description hr { + display: inline-block; + width: 1px; + height: 12px; + margin: 0 10px; + background: #ccc; + border: none; + vertical-align: middle; +} + +.header-center-description p, .header-center-description strong, .header-center-description em { + display: inline; + margin: 0 5px; +} + +.header-center-description ul, .header-center-description ol { + display: inline; + padding: 0; + margin: 0 5px; + list-style: none; +} + +.header-center-description li { + display: inline; + margin-right: 8px; +} + + + +/* Stylings for injected Project Header components */ +.menus-container .dropdown-component { + margin-right: 10px; + font-size: 14px; +} + +.menus-container .views-switcher-component { + margin-right: 15px; +} + +.menus-container .views-switcher-component ul.views { + display: flex; + margin: 0; + padding: 0; + list-style: none; + gap: 8px; +} + +.menus-container .views-switcher-component ul.views li { + background: transparent; + border: none; + margin: 0; +} + +.menus-container .views-switcher-component ul.views li a { + padding: 4px 10px; + border-radius: 6px; + font-size: 14px; + font-weight: normal; + color: #555; + background: rgba(0,0,0,0.04); + text-decoration: none; + display: flex; + align-items: center; + gap: 5px; + transition: all 0.2s ease; +} + +.menus-container .views-switcher-component ul.views li.active a, +.menus-container .views-switcher-component ul.views li:hover a { + background: var(--color-primary); + color: white; +} + +.menus-container .views-switcher-component ul.views li.active a i, +.menus-container .views-switcher-component ul.views li:hover a i { + color: white; +} + +.filter-box-component form { + display: flex; + align-items: center; + gap: 5px; + margin: 0; +} + +.filter-box-component .input-addon-item { + background: transparent; + border: none; +} + +.filter-box-component .input-addon-field { + border-radius: 20px; + padding: 4px 12px; + border: 1px solid #ccc; + font-size: 13px; + width: 200px; + transition: width 0.3s; +} + +.filter-box-component .input-addon-field:focus { + width: 250px; + outline: none; + border-color: var(--color-primary); +} +/* ========================================================= + Project Overview Dashboard Redesign + ========================================================= */ + +/* Main Container padding */ +.project-overview-dashboard { + padding: 20px 30px; +} + +/* --- KPI Cards Row --- */ +.dashboard-kpi-row { + margin-bottom: 20px; +} + +.dashboard-kpi-row .project-overview-columns { + display: flex; + gap: 15px; + border: none; + background: transparent; + padding: 0; + margin: 0; +} + +.dashboard-kpi-row .project-overview-column { + flex: 1; + background: white; + border-radius: 8px; + padding: 15px 20px; + box-shadow: 0 2px 10px rgba(0,0,0,0.04); + border: 1px solid rgba(0,0,0,0.05); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + transition: transform 0.2s, box-shadow 0.2s; +} + +.dashboard-kpi-row .project-overview-column:hover { + transform: translateY(-2px); + box-shadow: 0 4px 15px rgba(0,0,0,0.08); +} + +.dashboard-kpi-row .project-overview-column strong { + font-size: 28px; + font-weight: 700; + color: var(--color-primary); + line-height: 1; + margin-bottom: 6px; +} + +.dashboard-kpi-row .project-overview-column small { + font-size: 13px; + color: #666; + font-weight: 500; +} + +/* --- Pure CSS Tabs Layout --- */ +.tab-radio { + display: none; +} + +.dashboard-tabs-header { + display: flex; + gap: 8px; + margin-bottom: 15px; + border-bottom: 2px solid #f0f0f0; + padding-bottom: 8px; +} + +.dashboard-tabs-header .tab-btn { + flex: 1; + text-align: center; + background: transparent; + border: none; + padding: 8px 16px; + font-size: 14px; + font-weight: 500; + color: #666; + cursor: pointer; + border-radius: 6px; + transition: all 0.2s ease; + display: block; +} + +.dashboard-tabs-header .tab-btn:hover { + background: #f5f5f5; + color: #333; +} + +/* Radio button active states mapping to labels */ +#tab-desc:checked ~ .dashboard-tabs-header [for="tab-desc"], +#tab-attach:checked ~ .dashboard-tabs-header [for="tab-attach"], +#tab-info:checked ~ .dashboard-tabs-header [for="tab-info"], +#tab-act:checked ~ .dashboard-tabs-header [for="tab-act"] { + background: var(--color-primary); + color: white; + box-shadow: 0 4px 10px rgba(0,0,0,0.1); +} + +.dashboard-tabs-content .tab-pane { + display: none; +} + +/* Radio button active states mapping to panes */ +#tab-desc:checked ~ .dashboard-tabs-content .pane-desc, +#tab-attach:checked ~ .dashboard-tabs-content .pane-attach, +#tab-info:checked ~ .dashboard-tabs-content .pane-info, +#tab-act:checked ~ .dashboard-tabs-content .pane-act { + display: block; + animation: fadeIn 0.3s ease; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(5px); } + to { opacity: 1; transform: translateY(0); } +} + +/* --- Card Styles (Replaced details with div) --- */ +.dashboard-tabs-content .accordion-section { + background: white; + border-radius: 10px; + box-shadow: 0 2px 10px rgba(0,0,0,0.04); + border: 1px solid rgba(0,0,0,0.05); + overflow: hidden; + margin-bottom: 0; +} + +.dashboard-tabs-content .accordion-section .accordion-content { + padding: 25px; +} + +.project-overview-dashboard .accordion-content > .panel { + border: none; + background: transparent; + padding: 0; + margin: 0; + box-shadow: none; +} + +/* Styling specific content inside cards */ +.project-overview-dashboard .accordion-content ul { + list-style-type: none; + padding: 0; + margin: 0; +} + +.project-overview-dashboard .accordion-content ul li { + padding: 8px 0; + border-bottom: 1px solid #f5f5f5; + color: #555; +} + +.project-overview-dashboard .accordion-content ul li:last-child { + border-bottom: none; +} + +.project-overview-dashboard .accordion-content ul li strong { + color: #222; +} + +/* Activity feed timeline style */ +.project-overview-dashboard .activity-event { + margin-bottom: 15px; + padding-bottom: 15px; + border-bottom: 1px dashed #eee; +} + +.project-overview-dashboard .activity-event:last-child { + margin-bottom: 0; + padding-bottom: 0; + border-bottom: none; +} + +.project-overview-dashboard .activity-date { + font-size: 12px; + color: #999; +} + +@media (max-width: 900px) { + .dashboard-kpi-row .project-overview-columns { + flex-direction: column; + } + + .dashboard-tabs-header { + flex-wrap: wrap; + } +} + +/* ========================================================= + Kanban Board Redesign + ========================================================= */ + +/* Main Board Container */ +#board { + border-collapse: separate !important; + border-spacing: 15px 0 !important; + margin: 10px 0 !important; /* Removed side margin so 100% width fits perfectly */ + background: transparent !important; + width: 100% !important; + table-layout: fixed !important; +} + +/* Column Headers */ +.board-column-header { + background-color: #f4f5f7 !important; + border: none !important; + border-radius: 8px 8px 0 0 !important; + padding: 15px !important; +} + +/* Column Body */ +.board-column { + background-color: #f4f5f7 !important; + border: none !important; + border-radius: 0 0 8px 8px !important; + padding: 0 10px 15px 10px !important; + vertical-align: top; +} + +/* Remove default column borders */ +.board-column-header th, .board-column td { + border: none !important; +} + +/* Header Flexbox Layout */ +.board-column-expanded-header { + display: flex !important; + align-items: center; + justify-content: space-between; +} + +.board-column-title { + font-weight: 700; + font-size: 15px; + color: #333; + flex: 1; + text-align: left; + margin-left: 8px; +} + +/* Task Count Badge */ +.board-column-header-task-count { + background: #e0e4e8; + color: #555; + padding: 3px 10px; + border-radius: 12px; + font-size: 12px; + font-weight: 600; + margin-left: 10px; +} + +/* Task Cards */ +.task-board { + background-color: #ffffff !important; + border-top: none !important; + border-right: none !important; + border-bottom: none !important; + border-left-width: 4px !important; + border-left-style: solid !important; + border-radius: 6px !important; + box-shadow: 0 1px 3px rgba(0,0,0,0.1) !important; + padding: 12px 15px !important; + margin-bottom: 12px !important; + margin-top: 0 !important; + transition: transform 0.2s, box-shadow 0.2s; +} + +.task-board:hover { + box-shadow: 0 3px 8px rgba(0,0,0,0.15) !important; + transform: translateY(-2px); +} + +/* Typography inside Tasks */ +.task-board-header { + font-size: 12px; + color: #888; + margin-bottom: 8px; +} + +.task-board-header a { + color: #888 !important; + font-weight: 500; +} + +.task-board-title { + font-size: 14px; + font-weight: 600; + line-height: 1.4; + margin-bottom: 4px; +} + +.task-board-title a { + color: #222 !important; +} + +.task-board-title a:hover { + color: var(--color-primary) !important; + text-decoration: none; +} + +/* Clean up add button */ +.board-add-icon { + color: var(--color-primary) !important; + font-size: 16px; + opacity: 0.8; +} + +.board-add-icon:hover { + opacity: 1; +} + +/* ========================================================= + New Task / Form Modal Redesign + ========================================================= */ + +/* Use CSS Grid for the form container */ +.task-form-container { + display: grid !important; + grid-template-columns: 2fr 1fr 1fr; + gap: 20px; +} + +/* Make columns fill their grid cells */ +.task-form-main-column, +.task-form-secondary-column { + width: 100% !important; + float: none !important; +} + +/* Bottom area spans across all columns */ +.task-form-bottom { + grid-column: 1 / -1; + margin-top: 15px; + padding-top: 20px; + border-top: 1px solid #f0f0f0; +} + +/* Labels */ +.task-form-container label { + display: block; + font-size: 13px; + font-weight: 600; + color: #444; + margin-bottom: 6px; + margin-top: 12px; +} + +/* Inputs, Selects, Textareas */ +.task-form-container input[type="text"], +.task-form-container input[type="number"], +.task-form-container input[type="password"], +.task-form-container select, +.task-form-container textarea { + width: 100% !important; + padding: 10px 12px !important; + border: 1px solid #d1d5db !important; + border-radius: 6px !important; + box-sizing: border-box !important; + font-size: 14px; + color: #333; + transition: all 0.2s ease !important; + background-color: #fff !important; +} + +/* Focus states for inputs */ +.task-form-container input[type="text"]:focus, +.task-form-container input[type="number"]:focus, +.task-form-container input[type="password"]:focus, +.task-form-container select:focus, +.task-form-container textarea:focus { + border-color: var(--color-primary) !important; + box-shadow: 0 0 0 3px rgba(52, 104, 192, 0.1) !important; + outline: none !important; +} + +/* Checkboxes */ +.task-form-bottom label { + display: inline-block; + margin-top: 0; + margin-right: 15px; + font-weight: 500; +} + +/* Primary Button in Form */ +.task-form-bottom .btn-blue { + padding: 10px 24px !important; + font-size: 15px !important; + font-weight: 600 !important; + border-radius: 6px !important; + margin-right: 10px; +} + +/* Markdown Editor Toolbar */ +.text-editor-toolbar { + background-color: #f9f9f9; + border: 1px solid #d1d5db; + border-bottom: none; + border-radius: 6px 6px 0 0; + padding: 6px 10px; +} + +.text-editor-write-mode { + margin-top: 0 !important; +} + +.text-editor-write-mode textarea { + border-radius: 0 0 6px 6px !important; + border-top: none !important; +} diff --git a/assets/css/custom_login.css b/assets/css/custom_login.css new file mode 100644 index 0000000..f0ec5ec --- /dev/null +++ b/assets/css/custom_login.css @@ -0,0 +1,223 @@ +/* custom_login.css */ + +/* Define variables for Light Theme */ +:root { + --login-bg: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); + --login-card-bg: #ffffff; + --login-text-color: #333333; + --login-input-bg: #f1f3f5; + --login-input-text: #495057; + --login-input-placeholder: #adb5bd; + --login-icon-color: #ced4da; + --login-icon-focus: #f39c12; /* Rich yellow/orange for light mode focus */ + --login-btn-bg: #ffe066; /* Bright yellow */ + --login-btn-hover: #ffd43b; + --login-btn-text: #212529; + --login-shadow: 0 15px 35px rgba(0,0,0,0.05); + --login-btn-shadow: 0 8px 24px rgba(255, 224, 102, 0.4); +} + +/* Define variables for Dark Theme using prefers-color-scheme */ +@media (prefers-color-scheme: dark) { + :root { + --login-bg: #1f2029; /* Dark background */ + --login-card-bg: #2a2b38; /* Slightly lighter card */ + --login-text-color: #f8f9fa; + --login-input-bg: #1f2029; + --login-input-text: #f8f9fa; + --login-input-placeholder: #6c757d; + --login-icon-color: #495057; + --login-icon-focus: #ffeba7; + --login-btn-bg: #ffeba7; + --login-btn-hover: #ffe066; + --login-btn-text: #1f2029; + --login-shadow: 0 15px 35px rgba(0,0,0,0.2); + --login-btn-shadow: 0 8px 24px rgba(255, 235, 167, 0.15); + } +} + +/* Ensure body takes full height and uses Flexbox for centering on login page */ +body:has(.login-page-wrapper) { + background: var(--login-bg); + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + margin: 0; + font-family: 'Inter', 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; + color: var(--login-text-color); +} + +/* Hide header if it appears on login page */ +body:has(.login-page-wrapper) header { + display: none; +} +body:has(.login-page-wrapper) .page { + margin: 0; + padding: 0; + width: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +/* Card Container */ +.login-page-wrapper { + width: 100%; + max-width: 400px; + padding: 20px; + box-sizing: border-box; +} + +.login-page-wrapper .form-login { + background: var(--login-card-bg); + border-radius: 20px; + padding: 25px 30px 25px 30px; + box-shadow: var(--login-shadow); + text-align: center; + border: none; + margin: 0; +} + +.login-page-wrapper .form-login h2 { + margin-top: 0; + margin-bottom: 20px; + font-size: 26px; + font-weight: 600; + color: var(--login-text-color); + border: none; +} + +.login-page-wrapper .form-login h2::after { + display: none; +} + +/* Input Icon Wrapper */ +.input-icon-wrapper { + position: relative; + margin-bottom: 25px; + text-align: left; +} + +.input-icon-wrapper label { + display: none; /* Hide original labels, rely on placeholders and icons */ +} + +.input-icon-wrapper i { + position: absolute; + left: 20px; + top: 50%; + transform: translateY(-50%); + color: var(--login-icon-color); + transition: color 0.3s ease; + font-size: 18px; + pointer-events: none; /* Let clicks pass through to input */ +} + +.input-icon-wrapper input[type="text"], +.input-icon-wrapper input[type="password"] { + width: 100%; + background: var(--login-input-bg); + border: 2px solid transparent; + border-radius: 50px; + padding: 16px 20px 16px 50px; /* Space for icon */ + font-size: 15px; + color: var(--login-input-text); + transition: all 0.3s ease; + box-sizing: border-box; + box-shadow: none; +} + +/* Add placeholder styling */ +.input-icon-wrapper input::placeholder { + color: var(--login-input-placeholder); + opacity: 1; +} + +.input-icon-wrapper input:focus { + outline: none; + background: var(--login-card-bg); + border-color: var(--login-icon-focus); + box-shadow: 0 0 0 4px rgba(243, 156, 18, 0.1); +} + +.input-icon-wrapper:focus-within i { + color: var(--login-icon-focus); +} + +/* Button */ +.login-page-wrapper .form-actions { + margin-top: 25px; + text-align: center; +} + +.login-page-wrapper .btn { + background: var(--login-btn-bg); + color: var(--login-btn-text); + border: none; + border-radius: 50px; + padding: 15px 40px; + font-size: 16px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 1px; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: var(--login-btn-shadow); + width: 100%; /* Full width */ +} + +.login-page-wrapper .btn:hover { + background: var(--login-btn-hover); + transform: translateY(-2px); + box-shadow: 0 10px 25px rgba(255, 235, 167, 0.3); +} + +/* Links */ +.login-page-wrapper .reset-password { + margin-top: 15px; +} + +.login-page-wrapper .reset-password a { + color: var(--login-input-placeholder); + text-decoration: none; + font-size: 14px; + transition: color 0.3s ease; +} + +.login-page-wrapper .reset-password a:hover { + color: var(--login-icon-focus); +} + +/* Remember me */ +.login-page-wrapper .remember-me-wrapper { + display: flex; + align-items: center; + justify-content: flex-start; + padding-left: 10px; +} + +.login-page-wrapper input[type="checkbox"] { + margin: 0; + margin-right: 10px; + accent-color: var(--login-icon-focus); + width: 16px; + height: 16px; +} +.login-page-wrapper label.remember-me-label { + color: var(--login-input-placeholder); + font-size: 14px; + display: inline-block; + cursor: pointer; +} + +/* Error messages */ +.login-page-wrapper .alert-error { + background: rgba(231, 76, 60, 0.1); + color: #e74c3c; + border: none; + border-radius: 10px; + padding: 15px; + margin-bottom: 20px; + font-size: 14px; +} diff --git a/assets/css/dark.min.css b/assets/css/dark.min.css new file mode 100644 index 0000000..a5d03c6 --- /dev/null +++ b/assets/css/dark.min.css @@ -0,0 +1 @@ +:root{--body-background-color:#222;--header-background-color:#222;--color-primary:#a0a0a0;--color-light:#a0a0a0;--color-lighter:#efefef;--color-dark:#000;--color-medium:#4f4c4c;--color-error:#b94a48;--link-color-primary:#aaa;--link-color-focus:#ddd;--link-color-hover:#ddd;--alert-color-default:#efefef;--alert-color-success:#def6de;--alert-color-error:#de9393;--alert-color-info:#3a87ad;--alert-color-normal:#333;--alert-background-color-default:#333;--alert-background-color-success:#304b27;--alert-background-color-error:#500606;--alert-background-color-info:#d9edf7;--alert-background-color-normal:#f0f0f0;--alert-border-color-default:#444;--alert-border-color-success:#3c621b;--alert-border-color-error:#7e0315;--alert-border-color-info:#bce8f1;--alert-border-color-normal:#ddd;--button-default-color:#333;--button-default-background-color:#f5f5f5;--button-default-border-color:#ddd;--button-default-color-focus:#000;--button-default-background-color-focus:#fafafa;--button-default-border-color-focus:#bbb;--button-primary-color:#efefef;--button-primary-background-color:#333;--button-primary-border-color:#444;--button-primary-color-focus:#fff;--button-primary-background-color-focus:#555;--button-primary-border-color-focus:#888;--button-danger-color:#fff;--button-danger-background-color:#d14836;--button-danger-border-color:#b0281a;--button-danger-color-focus:#fff;--button-danger-background-color-focus:#c53727;--button-danger-border-color-focus:#b0281a;--button-disabled-color:#ccc;--button-disabled-background-color:#f7f7f7;--button-disabled-border-color:#ccc;--table-header-background-color:#1a1a1a;--table-nth-background-color:#2d2c2c;--table-border-color:rgba(147,128,108,.25);--avatar-color-letter:#fff;--activity-title-color:#e3e2e2;--activity-title-border-color:#efefef;--activity-event-background-color:#313131;--activity-event-hover-color:#000;--user-mention-color:#fff;--board-task-limit-color:#DF5353;--table-list-header-border-color:rgba(147,128,108,.25);--table-list-header-background-color:rgb(59,59,59);--table-list-nth-background-color:#2d2c2c;--table-list-border-color:rgba(147,128,108,.25);--table-list-row-hover-border-color:rgba(147,128,108,.25);--table-list-row-background-color:#434343;--sidebar-border-color:rgba(147,128,108,.25);--dropdown-background-color:#222;--dropdown-border-color:#000;--dropdown-li-border-color:#555;--input-addon-background-color:#1a1a1a;--input-addon-color:rgba(147,128,108,.25);--views-background-color:#1a1a1a;--views-border-color:rgba(147,128,108,.25);--views-active-color:#949494;--input-focus-color:#e6edf3;--input-focus-border-color:rgba(82,168,236,.8);--input-focus-shadow-color:rgba(82,168,236,.6);--input-background-color:rgb(59,59,59);--input-border-color:#777575;--input-placeholder-color:#666;--tooltip-background-color:#333;--tooltip-border-color:#555;--tooltip-shadow-color:#111;--panel-background-color:#2c2c2c;--panel-border-color:#000;--draggable-item-selected-background-color:#222;--draggable-item-selected-border-color:#111;--draggable-item-hover-background-color:#555;--draggable-row-handle-color:#444;--draggable-placeholder-background-color:#444;--draggable-placeholder-border-color:#666;--task-list-icons-color:#ccc;--form-help-color:#a8a12f;--form-error-color:#f2332f;--comment-title-border-color:#eee;--comment-nth-background-color:#2b2a2a;--comment-highlighted-background-color:#2b2901;--comment-highlighted-hover-background-color:#000;--comment-highlighted-border-color:#c09e05}html{color-scheme:dark}.select2-dropdown,.select2-close-mask{background-color:var(--input-background-color)}.select2-container--default .select2-selection--multiple,.select2-container--default .select2-selection--single{background-color:var(--input-background-color);border-color:var(--input-border-color)}.select2-container--default.select2-container--focus .select2-selection--multiple{border-color:var(--input-focus-border-color)}.select2-container--default .select2-selection--single .select2-selection__rendered,.select2-container--classic .select2-selection--single .select2-selection__rendered{color:#fff}.task-board-title{color:#000}.task-summary-column a,.task-summary-column a:hover{color:#000}h1,li,ul,ol,table,tr,td,th,p,blockquote,body{margin:0;padding:0}body{background-color:var(--body-background-color);font-size:100%;padding-bottom:10px;color:var(--color-primary);font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;text-rendering:optimizeLegibility;overflow-x:hidden}small{font-size:.8em}hr{border:0;height:0;border-top:1px solid rgba(0,0,0,.1);border-bottom:1px solid rgba(255,255,255,.3)}.page{margin-left:10px;margin-right:10px}.margin-top{margin-top:20px}.margin-bottom{margin-bottom:20px}.pull-right{text-align:right;margin-left:auto}ul.no-bullet li{list-style-type:none;margin-left:0}#app-loading-icon{position:fixed;right:3px;bottom:3px}.assign-me{vertical-align:bottom}a{color:var(--link-color-primary);border:none}a:focus{color:var(--link-color-focus);outline:0;text-decoration:none}a:hover{color:var(--link-color-hover);text-decoration:none}a .fa{color:var(--color-primary);padding-right:3px;text-decoration:none}h1,h2,h3{font-weight:400;color:var(--color-primary)}h1{font-size:1.5em}h2{font-size:1.4em;margin-bottom:10px}h3{margin-top:10px;font-size:1.2em}table{width:100%;border-collapse:collapse;border-spacing:0;margin-bottom:20px}table.table-fixed{table-layout:fixed;white-space:nowrap}table.table-fixed th{overflow:hidden}table.table-fixed td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}table.table-small{font-size:.8em}table.table-striped tr:nth-child(odd){background:var(--table-nth-background-color)}@media (max-width:768px){table.table-scrolling{overflow-x:auto;display:inline-block;vertical-align:top;max-width:100%;white-space:nowrap}}table th{text-align:left;padding:.5em 3px;border:1px solid var(--table-border-color);background-color:var(--table-header-background-color)}table th a{text-decoration:none;color:var(--color-primary)}table th a:focus,table th a:hover{text-decoration:underline}table td{border:1px solid var(--table-border-color);padding:.5em 3px;vertical-align:top}table td li{margin-left:20px}table td .color-picker-square{display:inline-block;width:12px;height:12px;border:1px solid #000}.task-table a{color:#000}.column-1{width:1%}.column-2{width:2%}.column-3{width:3%}.column-4{width:4%}.column-5{width:5%}.column-6{width:6%}.column-7{width:7%}.column-8{width:8%}.column-9{width:9%}.column-10{width:10%}.column-11{width:11%}.column-12{width:12%}.column-13{width:13%}.column-14{width:14%}.column-15{width:15%}.column-16{width:16%}.column-17{width:17%}.column-18{width:18%}.column-19{width:19%}.column-20{width:20%}.column-21{width:21%}.column-22{width:22%}.column-23{width:23%}.column-24{width:24%}.column-25{width:25%}.column-26{width:26%}.column-27{width:27%}.column-28{width:28%}.column-29{width:29%}.column-30{width:30%}.column-31{width:31%}.column-32{width:32%}.column-33{width:33%}.column-34{width:34%}.column-35{width:35%}.column-36{width:36%}.column-37{width:37%}.column-38{width:38%}.column-39{width:39%}.column-40{width:40%}.column-41{width:41%}.column-42{width:42%}.column-43{width:43%}.column-44{width:44%}.column-45{width:45%}.column-46{width:46%}.column-47{width:47%}.column-48{width:48%}.column-49{width:49%}.column-50{width:50%}.column-51{width:51%}.column-52{width:52%}.column-53{width:53%}.column-54{width:54%}.column-55{width:55%}.column-56{width:56%}.column-57{width:57%}.column-58{width:58%}.column-59{width:59%}.column-60{width:60%}.column-61{width:61%}.column-62{width:62%}.column-63{width:63%}.column-64{width:64%}.column-65{width:65%}.column-66{width:66%}.column-67{width:67%}.column-68{width:68%}.column-69{width:69%}.column-70{width:70%}.column-71{width:71%}.column-72{width:72%}.column-73{width:73%}.column-74{width:74%}.column-75{width:75%}.column-76{width:76%}.column-77{width:77%}.column-78{width:78%}.column-79{width:79%}.column-80{width:80%}.column-81{width:81%}.column-82{width:82%}.column-83{width:83%}.column-84{width:84%}.column-85{width:85%}.column-86{width:86%}.column-87{width:87%}.column-88{width:88%}.column-89{width:89%}.column-90{width:90%}.column-91{width:91%}.column-92{width:92%}.column-93{width:93%}.column-94{width:94%}.column-95{width:95%}.column-96{width:96%}.column-97{width:97%}.column-98{width:98%}.column-99{width:99%}.column-100{width:100%}.draggable-row-handle{cursor:move;color:var(--draggable-row-handle-color)}.draggable-row-handle:hover{color:var(--color-primary)}tr.draggable-item-selected{background:var(--draggable-item-selected-background-color);border:2px solid var(--draggable-item-selected-border-color);box-shadow:4px 2px 10px -4px rgba(0,0,0,.55)}tr.draggable-item-selected td{border-top:none;border-bottom:none}tr.draggable-item-selected td:first-child{border-left:none}tr.draggable-item-selected td:last-child{border-right:none}.table-stripped tr.draggable-item-hover,.table-stripped tr.draggable-item-hover{background:var(--draggable-item-hover-background-color)}.table-list{font-size:.85em;margin-bottom:20px}.table-list-header{background:var(--table-list-header-background-color);border:1px solid var(--table-list-header-border-color);border-radius:5px 5px 0 0;line-height:28px;padding-left:3px;padding-right:3px}.table-list-header a{color:var(--color-primary);font-weight:500;text-decoration:none;margin-right:10px}.table-list-header a:hover,.table-list-header a:focus{color:#767676}.table-list-header .table-list-header-count{color:#767676;display:inline-block;float:left}.table-list-header .table-list-header-menu{text-align:right}.table-list-row{padding-left:3px;padding-right:3px;border-bottom:1px solid var(--table-list-border-color);border-right:1px solid var(--table-list-border-color)}.table-list-row.table-border-left{border-left:1px solid var(--table-list-border-color)}.table-list-row:nth-child(odd){background:var(--table-list-nth-background-color)}.table-list-row:last-child{border-radius:0 0 5px 5px}.table-list-row:hover{background:var(--table-list-row-background-color);border-bottom:1px solid var(--table-list-row-hover-border-color);border-right:1px solid var(--table-list-row-hover-border-color)}.table-list-row .table-list-title{font-weight:500;line-height:23px}.table-list-row .table-list-title.status-closed{text-decoration:line-through;margin-right:10px}.table-list-row .table-list-title.status-closed a{font-style:italic}.table-list-row .table-list-title a{color:var(--color-primary);text-decoration:none}.table-list-row .table-list-title a:hover,.table-list-row .table-list-title a:focus{text-decoration:underline}.table-list-row .table-list-details{color:#999;font-weight:300;line-height:20px}.table-list-row .table-list-details span{margin-left:5px}.table-list-row .table-list-details span:first-child{margin-left:0}.table-list-row .table-list-details li{display:inline;list-style-type:none}.table-list-row .table-list-details li:after{content:', '}.table-list-row .table-list-details li:last-child:after{content:''}.table-list-row .table-list-details strong{font-weight:400;color:#555}.table-list-row .table-list-details-with-icons{float:left}@media (max-width:768px){.table-list-row .table-list-details-with-icons{float:none}}.table-list-row .table-list-icons{font-size:.8em;text-align:right;line-height:30px}@media (max-width:768px){.table-list-row .table-list-icons{text-align:left;line-height:20px}}.table-list-row .table-list-icons span{margin-left:5px}.table-list-row .table-list-icons a{text-decoration:none}.table-list-row .table-list-icons a:hover{color:var(--color-primary)}.table-list-row .table-list-icons a:hover i{color:var(--color-primary)}.table-list-category{font-size:.9em;font-weight:500;color:#000;padding:1px 2px 1px 2px;border-radius:3px;background:#fcfcfc;border:1px solid #ccc}.table-list-category a{text-decoration:none;color:#000}.table-list-category a:hover{color:#36c}fieldset{border:1px solid #ddd;margin-top:10px}legend{font-weight:500;font-size:1.2em}label{cursor:pointer;display:block;margin-top:10px;font-weight:400}input,textarea{font-family:sans-serif;background-color:var(--input-background-color)}input[type="number"],input[type="date"],input[type="email"],input[type="password"],input[type="text"]:not(.input-addon-field){color:var(--color-light);border:1px solid var(--input-border-color);width:300px;max-width:95%;font-size:1em;height:25px;padding-bottom:0;padding-left:4px;-webkit-appearance:none;-moz-appearance:none}input[type="number"]::placeholder,input[type="date"]::placeholder,input[type="email"]::placeholder,input[type="password"]::placeholder,input[type="text"]:not(.input-addon-field)::placeholder{color:var(--input-placeholder-color)}input[type="number"]:focus,input[type="date"]:focus,input[type="email"]:focus,input[type="password"]:focus,input[type="text"]:focus{color:var(--input-focus-color);border-color:var(--input-focus-border-color);outline:0;box-shadow:0 0 8px var(--input-focus-shadow-color)}input[type="number"]{width:70px}input[type="text"]:not(.input-addon-field).form-numeric{width:70px}input[type="text"]:not(.input-addon-field).form-datetime,input[type="text"]:not(.input-addon-field).form-date{width:150px}input[type="text"]:not(.input-addon-field).form-input-large{width:400px}input[type="text"]:not(.input-addon-field).form-input-small{width:150px}textarea:focus{color:var(--input-focus-color);border-color:var(--input-focus-border-color);outline:0;box-shadow:0 0 8px var(--input-focus-shadow-color)}textarea{padding:4px;border:1px solid var(--input-border-color);width:400px;max-width:99%;height:200px;font-size:1em}textarea::placeholder{color:var(--input-placeholder-color)}select{font-size:1em;max-width:95%}select:focus{outline:0}select[multiple]{width:300px}.tag-autocomplete{width:400px}span.select2-container{margin-top:2px}.form-actions{padding-top:20px;clear:both}.form-required{color:red;padding-left:5px;font-weight:700}@media (max-width:480px){.form-required{display:none}}input[type="text"].form-max-width{width:100%}input.form-error,textarea.form-error{border:2px solid var(--form-error-color)}input.form-error:focus,textarea.form-error:focus{box-shadow:none;border:2px solid var(--form-error-color)}.form-errors{color:var(--form-error-color);list-style-type:none}ul.form-errors li{margin-left:0}.form-help{font-size:.8em;color:var(--form-help-color);margin-bottom:15px}.form-inline{padding:0;margin:0;border:none}.form-inline label{display:inline;padding-right:3px}.form-inline input,.form-inline select{margin:0 15px 0 0}.form-inline .form-required{display:none}.form-inline .form-actions{display:inline-block}.form-inline .js-submit-buttons-rendered{display:inline-block}.form-inline-group{display:inline}.form-columns{display:-webkit-flex;display:flex;-webkit-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;flex-wrap:wrap;-webkit-justify-content:flex-start;justify-content:flex-start}.form-columns .form-column{margin-right:25px;flex-grow:1}.form-columns fieldset{margin-top:0}.form-login{max-width:350px;margin:5% auto 0}@media (max-width:480px){.form-login{margin-left:5px}}.form-login li{margin-left:25px;line-height:25px}.form-login h2{margin-bottom:30px;font-weight:700}.reset-password{margin-top:20px;margin-bottom:20px}.reset-password a{color:var(--color-light)}.input-addon{display:flex}.input-addon-field{flex:1;font-size:1em;color:var(--color-light);margin:0;-webkit-appearance:none;-moz-appearance:none}.input-addon-field:first-child{border-radius:5px 0 0 5px}.input-addon-field:last-child{border-radius:0 5px 5px 0}.input-addon-item{background-color:var(--input-addon-background-color);color:var(--input-addon-color);font:inherit;font-weight:400}.input-addon-item:first-child{border-radius:5px 0 0 5px}.input-addon-item:last-child{border-radius:0 5px 5px 0}@media (max-width:480px){.input-addon-item .dropdown .fa-caret-down{display:none}}.input-addon-field,.input-addon-item{border:1px solid rgba(147,128,108,.25);padding:4px .75em}.input-addon-field:not(:first-child),.input-addon-item:not(:first-child){border-left:0}.input-addon .input-addon-field{flex:1 1 auto;width:1%!important}@media (max-width:400px){.input-addon-item{padding:3px}}.icon-success{color:#468847}.icon-error{color:#b94a48}.icon-fade-out{opacity:1;animation:icon-fadeout 5s linear forwards}@keyframes icon-fadeout{0%{opacity:1}100%{opacity:0}}.alert{padding:8px 35px 8px 14px;margin-top:5px;margin-bottom:5px;color:var(--alert-color-default);background-color:var(--alert-background-color-default);border:1px solid var(--alert-border-color-default);border-radius:4px}.alert-success{color:var(--alert-color-success);background-color:var(--alert-background-color-success);border-color:var(--alert-border-color-success)}.alert-error{color:var(--alert-color-error);background-color:var(--alert-background-color-error);border-color:var(--alert-border-color-error)}.alert-info{color:var(--alert-color-info);background-color:var(--alert-background-color-info);border-color:var(--alert-border-color-info)}.alert-normal{color:var(--alert-color-normal);background-color:var(--alert-background-color-normal);border-color:var(--alert-border-color-normal)}.alert ul{margin-top:10px;margin-bottom:10px}.alert li{margin-left:25px}.alert-fade-out{text-align:center;position:fixed;bottom:0;left:20%;width:60%;padding-top:5px;padding-bottom:5px;margin-bottom:0;border-width:1px 0 0;border-radius:4px 4px 0 0;z-index:9999;opacity:1;animation:fadeout 5s linear forwards}@keyframes fadeout{0%{opacity:1}100%{opacity:0;visibility:hidden}}a.btn{text-decoration:none}.btn{-webkit-appearance:none;-moz-appearance:none;font-size:1.2em;font-weight:400;cursor:pointer;display:inline-block;border-radius:2px;padding:3px 10px;margin:0;border:1px solid var(--button-default-border-color);background:var(--button-default-background-color);color:var(--button-default-color)}.btn:hover,.btn:focus{border-color:var(--button-default-border-color-focus);background:var(--button-default-background-color-focus);color:var(--button-default-color-focus)}.btn-red{border-color:var(--button-danger-border-color);background:var(--button-danger-background-color);color:var(--button-danger-color)}.btn-red:hover,.btn-red:focus{border-color:var(--button-danger-border-color-focus);background:var(--button-danger-background-color-focus);color:var(--button-danger-color-focus)}.btn-blue{border-color:var(--button-primary-border-color);background:var(--button-primary-background-color);color:var(--button-primary-color)}.btn-blue:hover,.btn-blue:focus{border-color:var(--button-primary-border-color-focus);background:var(--button-primary-background-color-focus);color:var(--button-primary-color-focus)}.btn:disabled{color:var(--button-disabled-color);border-color:var(--button-disabled-border-color);background:var(--button-disabled-background-color)}.buttons-header{font-size:.8em;margin-top:5px;margin-bottom:15px}.tooltip i.fa{cursor:pointer}.tooltip .fa-info-circle{color:var(--color-light)}#tooltip-container{padding:5px;background:var(--tooltip-background-color);border:1px solid var(--tooltip-border-color);border-radius:4px;box-shadow:0 6px 12px var(--tooltip-shadow-color);position:absolute;min-width:350px}#tooltip-container .markdown p:last-child{margin-bottom:0}#tooltip-container .tooltip-large{width:600px}h2 .dropdown ul{display:none}.dropdown{display:inline;position:relative}.dropdown ul{display:none}.dropdown-smaller{font-size:.85em}ul.dropdown-submenu-open{display:block;position:absolute;z-index:1000;min-width:285px;list-style:none;margin:3px 0 0 1px;padding:6px 0;background-color:var(--dropdown-background-color);border:1px solid var(--dropdown-border-color);border-radius:3px;box-shadow:0 1px 3px rgba(0,0,0,.15)}.dropdown-submenu-open li{display:block;margin:0;padding:8px 10px;font-size:.9em;border-bottom:1px solid var(--dropdown-li-border-color);cursor:pointer}.dropdown-submenu-open li.no-hover{cursor:default}.dropdown-submenu-open li:last-child{border:none}.dropdown-submenu-open li:not(.no-hover):hover{background:#4078C0;color:#fff}.dropdown-submenu-open li:hover a{color:#fff}.dropdown-submenu-open li:hover i{color:#fff}.dropdown-submenu-open a{text-decoration:none;color:var(--color-primary)}.dropdown-submenu-open a:focus{text-decoration:underline}.dropdown-menu-link-text,.dropdown-menu-link-icon{color:var(--color-primary);text-decoration:none}.dropdown-menu-link-icon{display:inline-flex}.dropdown-menu-link-text:hover{text-decoration:underline}td a.dropdown-menu strong{color:var(--color-primary)}td a.dropdown-menu strong i{color:var(--color-primary)}td a.dropdown-menu i{color:#dedede}td a.dropdown-menu:hover strong{color:#555}td a.dropdown-menu:hover strong i{color:#555}td a.dropdown-menu:hover i{color:#333}.accordion-title{font-size:1.2em;cursor:pointer;margin-top:10px}.accordion-content{margin-top:15px;margin-bottom:25px}#select-dropdown-menu{position:absolute;display:block;z-index:1000;min-width:160px;padding:5px 0;background:var(--dropdown-background-color);list-style:none;border:1px solid var(--dropdown-border-color);border-radius:3px;box-shadow:0 6px 12px rgba(0,0,0,.175);overflow:scroll}.select-dropdown-menu-item{white-space:nowrap;overflow:hidden;padding:3px 10px;color:var(--color-medium);cursor:pointer;border-bottom:1px solid var(--dropdown-li-border-color);line-height:1.5em;font-weight:400}.select-dropdown-menu-item.active{color:#fff;background:#428bca}.select-dropdown-menu-item:last-child{border:none}.select-dropdown-input-container{position:relative;border:1px solid var(--input-border-color);border-radius:5px;background-color:var(--input-background-color);max-width:300px}.select-dropdown-input-container input.select-dropdown-input{margin:0 0 0 5px;border:none;height:23px;width:270px}.select-dropdown-input-container input.select-dropdown-input:focus{border:none;box-shadow:none}.select-dropdown-input-container .select-dropdown-chevron{color:var(--color-medium);position:absolute;top:4px;right:5px;cursor:pointer}.select-dropdown-input-container .select-loading-icon{color:var(--color-medium);position:absolute;top:4px;right:5px}#suggest-menu{position:absolute;display:block;z-index:1000;min-width:160px;padding:5px 0;background:#fff;list-style:none;border:1px solid #ccc;border-radius:3px;box-shadow:0 6px 12px rgba(0,0,0,.175)}.suggest-menu-item{white-space:nowrap;padding:3px 10px;color:var(--color-primary);font-weight:700;cursor:pointer}.suggest-menu-item.active{color:#fff;background:#428bca}.suggest-menu-item.active small{color:#fff}.suggest-menu-item small{color:var(--color-light);font-weight:400}#modal-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.9);overflow:auto;z-index:100}#modal-box{position:fixed;max-height:calc(100% - 30px);top:2%;left:50%;transform:translateX(-50%);background:var(--body-background-color);overflow:auto;border-radius:5px}#modal-content{padding:0 5px 5px}#modal-header{text-align:right;padding-right:5px}#modal-close-button{color:var(--color-primary)}#modal-close-button:hover{color:var(--color-error)}.pagination{text-align:center;font-size:.9em}.pagination-showing{margin-right:5px;padding-right:5px;border-right:1px solid #999}.pagination-next{margin-left:5px}.pagination-previous{margin-right:5px}header{display:flex;flex-wrap:wrap;padding:5px 10px;margin-bottom:5px;border-bottom:1px solid #dedede;background-color:var(--header-background-color)}header .title-container{flex:1;min-width:300px}@media (max-width:480px){header .title-container{order:3}}header .board-selector-container{min-width:320px;display:flex;align-items:center}@media (max-width:480px){header .board-selector-container{order:2;min-width:300px}header .board-selector-container input[type=text]{max-width:280px}}header .menus-container{min-width:120px;display:flex;align-items:center;justify-content:flex-end}@media (max-width:480px){header .menus-container{order:1;margin-bottom:5px;margin-left:auto}}header h1{font-size:1.5em}header h1 .tooltip{opacity:.3;font-size:.7em}a i.web-notification-icon{color:var(--link-color-primary)}a i.web-notification-icon:focus,a i.web-notification-icon:hover{color:#000}.logo a{opacity:.5;color:#d40000;text-decoration:none}.logo span{color:var(--color-primary)}.logo a:hover{opacity:.8;color:var(--color-primary)}.logo a:focus span,.logo a:hover span{color:#d40000}.page-header{margin-bottom:20px}.page-header .dropdown{padding-right:10px}.page-header h2{margin:0;padding:0;font-weight:700;border-bottom:1px dotted #ccc}.page-header h2 a{color:var(--color-primary);text-decoration:none}.page-header h2 a:focus,.page-header h2 a:hover{color:var(--color-light)}.page-header ul{text-align:left;margin-top:5px;display:inline-block}.page-header li{display:inline;padding-right:15px}@media (max-width:480px){.page-header li{display:block;line-height:1.5em}}.page-header li.active a{color:var(--color-primary);text-decoration:none;font-weight:700}.page-header li.active a:hover,.page-header li.active a:focus{text-decoration:underline}.menu-inline{margin-bottom:5px}.menu-inline li{display:inline;padding-right:15px}.menu-inline li .active a{font-weight:700;color:#000;text-decoration:none}.sidebar-container{height:100%;display:flex;flex-flow:row}@media (max-width:768px){.sidebar-container{flex-flow:wrap}}.sidebar-content{padding-left:10px;flex:1 100%;max-width:85%;overflow-wrap:break-word}@media (max-width:768px){.sidebar-content{padding-left:0;order:1;max-width:100%}}@media only screen and (min-device-width:768px) and (max-device-width:1024px) and (orientation:landscape) and (-webkit-min-device-pixel-ratio:1){.sidebar-content{max-width:75%}}.sidebar{max-width:25%;min-width:230px}@media (max-width:768px){.sidebar{flex:1 auto;order:2}}.sidebar h2{margin-top:0}.sidebar>ul a{text-decoration:none;color:var(--color-light);font-weight:300}.sidebar>ul a:hover{color:var(--color-primary)}.sidebar>ul li{list-style-type:none;line-height:35px;border-bottom:1px dotted var(--sidebar-border-color);padding-left:13px}.sidebar>ul li:hover{border-left:5px solid #555;padding-left:8px}.sidebar>ul li.active{border-left:5px solid #333;padding-left:8px}.sidebar>ul li.active a{color:var(--color-primary);font-weight:400}.sidebar-icons>ul li{padding-left:0}.sidebar-icons>ul li:hover,.sidebar-icons>ul li.active{padding-left:0;border-left:none}.sidebar>ul li.active a:focus,.sidebar>ul li.active a:hover{color:var(--color-medium)}.sidebar>ul li:last-child{margin-bottom:15px}.avatar img{vertical-align:bottom}.avatar-left{float:left;margin-right:10px}.avatar-inline{display:inline-block;margin-right:3px}.avatar-48 img,.avatar-48 div{border-radius:30px}.avatar-48 .avatar-letter{line-height:48px;width:48px;font-size:25px}.avatar-20 img,.avatar-20 div{border-radius:10px}.avatar-20 .avatar-letter{line-height:20px;width:20px;font-size:11px}.avatar-letter{color:var(--avatar-color-letter);text-align:center}#file-dropzone,#screenshot-zone{position:relative;border:2px dashed #ccc;width:99%;height:250px;overflow:auto}#file-dropzone-inner,#screenshot-inner{position:absolute;left:0;bottom:48%;width:100%;text-align:center;color:#aaa}#screenshot-zone.screenshot-pasted{border:2px solid #333}#file-list{margin:20px}#file-list li{list-style-type:none;padding-top:8px;padding-bottom:8px;border-bottom:1px dotted #ddd;width:95%}#file-list li .file-error{font-weight:700;color:#b94a48}.file-thumbnails{display:-webkit-flex;display:flex;-webkit-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;flex-wrap:wrap;-webkit-justify-content:flex-start;justify-content:flex-start}.file-thumbnail{width:250px;border:1px solid #efefef;border-radius:5px;margin-bottom:20px;box-shadow:4px 2px 10px -6px rgba(0,0,0,.55);margin-right:15px}.file-thumbnail img{cursor:pointer;border-top-left-radius:5px;border-top-right-radius:5px}.file-thumbnail img:hover{opacity:.5}.file-thumbnail-content{padding-left:8px;padding-right:8px}.file-thumbnail-title{font-weight:700;font-size:.9em;color:var(--color-medium);overflow:hidden;text-overflow:ellipsis}.file-thumbnail-description{font-size:.8em;color:var(--color-light);margin-top:8px;margin-bottom:5px}.file-viewer{position:relative}.file-viewer img{max-width:95%;max-height:85%;margin-top:10px}.color-picker{width:220px}.color-picker-option{height:25px}.color-picker-square{display:inline-block;width:18px;height:18px;margin-right:5px;border:1px solid #000}.color-picker-label{display:inline-block;vertical-align:bottom;padding-bottom:3px}.filter-box{max-width:100%}.action-menu{color:var(--color-primary);text-decoration:none}.action-menu:hover,.action-menu:focus{text-decoration:underline}.js-project-creation-options{max-width:500px;border-left:3px dotted #efefef;margin-top:20px;padding-left:15px;padding-bottom:5px;padding-top:5px}.project-overview-columns{display:-webkit-flex;display:flex;-webkit-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;flex-wrap:wrap;-webkit-align-items:center;align-items:center;-webkit-justify-content:center;justify-content:center;margin-bottom:20px;font-size:1.4em}@media (max-width:480px){.project-overview-columns{display:block}}.project-overview-column{text-align:center;margin-right:3%;margin-top:5px;padding:3px 15px 3px 15px;border:1px dashed #ddd}@media (max-width:480px){.project-overview-column{text-align:left}}.project-overview-column small{color:var(--color-light)}.project-overview-column strong{color:var(--color-medium);display:block}@media (max-width:480px){.project-overview-column strong{display:inline}}.project-header{margin-bottom:8px}.project-header .dropdown-component{margin-top:4px;margin-right:5px;float:left}@media (max-width:768px){.project-header .dropdown-component{float:none}}.project-header .views-switcher-component{margin-top:4px;margin-bottom:10px;float:left}@media (max-width:768px){.project-header .views-switcher-component{float:none;margin-bottom:10px}}.project-header .filter-box-component form{margin:0}.views{margin-right:10px;margin-top:1px;font-size:.9em}@media (max-width:560px){.views{width:100%}}@media (max-width:768px){.views{margin-top:10px;font-size:1em}}@media (max-width:480px){.views{margin-top:5px}}.views li{white-space:nowrap;background:var(--views-background-color);border:1px solid var(--views-border-color);border-right:none;padding:4px 8px;display:inline}@media (max-width:560px){.views li{display:block;margin-top:5px;border-radius:5px;border:1px solid var(--views-border-color)}}.views li.active a{font-weight:700;color:var(--views-active-color);text-decoration:none}.views li:first-child{border-top-left-radius:5px;border-bottom-left-radius:5px}.views li:last-child{border-right:1px solid var(--views-border-color);border-top-right-radius:5px;border-bottom-right-radius:5px}.views a{color:var(--color-ligth);text-decoration:none}.views a:hover{color:var(--color-primary);text-decoration:underline}.dashboard-project-stats small{margin-right:10px;color:var(--color-light)}.dashboard-table-link{font-weight:700;color:#000;text-decoration:none}.dashboard-table-link:focus,.dashboard-table-link:hover{color:var(--color-light)}.public-board{margin-top:5px}.public-task{max-width:800px;margin:5px auto 0}#board-container{overflow-x:auto}#board{table-layout:fixed;margin-bottom:0}#board tr.board-swimlane-columns-first{visibility:hidden;padding:0}#board th.board-column-header{width:240px}#board th.board-column-header-first{visibility:hidden;padding:0}#board td{vertical-align:top}.board-container-compact{overflow-x:initial}@media all and (-ms-high-contrast:active),(-ms-high-contrast:none){.board-container-compact #board{table-layout:auto}}#board th.board-column-header.board-column-compact{width:initial}.board-column-collapsed{display:none}.board-column-expanded-header{display:flex;align-items:center}td.board-column-task-collapsed{font-weight:700;background-color:var(--table-header-background-color)}#board th.board-column-header-collapsed{width:28px;min-width:28px;text-align:center;overflow:hidden}.board-rotation-wrapper{position:relative;padding:8px 4px;min-height:150px;overflow:hidden}.board-rotation{white-space:nowrap;transform:rotate(90deg);transform-origin:0 100%}.board-column-title .dropdown-menu{text-decoration:none}.board-add-icon{float:left;padding:0 5px}.board-add-icon i{text-decoration:none;color:var(--link-color-primary);font-size:1.4em}.board-add-icon i:focus,.board-add-icon i:hover{text-decoration:none;color:red}.board-column-header-task-count{color:var(--color-light);font-weight:400;font-size:.85em}a.board-swimlane-toggle{text-decoration:none}a.board-swimlane-toggle:hover,a.board-swimlane-toggle:focus{color:#000;text-decoration:none;border:none}.board-task-list{min-height:60px}.board-task-list-compact{max-height:90vh;overflow-y:auto}.board-task-list .task-board:last-child{margin-bottom:0}.board-task-list-limit{background-color:var(--board-task-limit-color)}.draggable-item{cursor:pointer;user-select:none;-webkit-user-select:none;-moz-user-select:none}.draggable-placeholder{border:2px dashed var(--draggable-placeholder-border-color);background:var(--draggable-placeholder-background-color);height:70px;margin-bottom:10px}div.draggable-item-selected{border:1px solid #000}.task-board-sort-handle{float:left;padding-right:5px}.task-board{position:relative;margin-bottom:4px;border:1px solid #000;padding:2px;word-wrap:break-word;font-size:.9em;border-radius:6px}div.task-board-recent{border-width:2px}div.task-board-status-closed{user-select:none;border:1px dotted #555}.task-board a{color:#000;text-decoration:none}.task-board-collapsed{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.task-board-title{margin-top:5px;margin-bottom:8px}.task-board-title a:hover{text-decoration:underline}.task-board-saving-state{opacity:.3}.task-board-saving-icon{position:absolute;margin:auto;width:100%;text-align:center;color:#000}.task-board-avatars{text-align:right;float:right}.task-board-change-assignee{cursor:pointer}.task-board-change-assignee:hover{opacity:.6}.task-list-avatars{display:inline-block;float:left}@media (max-width:768px){.task-list-avatars{float:none;display:block}}.task-list-avatars .task-avatar-assignee{font-weight:300;color:#999}.task-list-avatars:hover .task-avatar-assignee{font-weight:400;color:#000}.task-board-icons,.task-list-icons{font-size:.8em;text-align:right}.task-board-icons a,.task-board-icons span.tooltip,.task-list-icons a,.task-list-icons span.tooltip{text-decoration:none}.task-board-icons a:hover,.task-board-icons span.tooltip:hover,.task-list-icons a:hover,.task-list-icons span.tooltip:hover{color:var(--color-primary)}.task-board-icons a:hover i,.task-board-icons span.tooltip:hover i,.task-list-icons a:hover i,.task-list-icons span.tooltip:hover i{color:var(--color-primary)}.task-board-icons .task-score,.task-list-icons .task-score{font-weight:700}.task-board-icons .flag-milestone,.task-list-icons .flag-milestone{color:green}.task-board-icons{margin-top:7px}.task-board-icons a{opacity:.5}.task-board-icons span{opacity:.5;margin-left:4px}.task-board-icons a:hover,.task-board-icons span.tooltip:hover{opacity:1;font-weight:700}.task-board-icons .task-board-icons-row{line-height:22px}.task-list-icons{line-height:22px}.task-list-icons a,.task-list-icons span,.task-list-icons i{color:var(--task-list-icons-color);opacity:1}.task-list-icons span{margin-left:5px}@media (max-width:768px){.task-list-icons{text-align:left}}.task-icon-age{display:inline-block}span.task-icon-age-total{border:1px solid #e5e5e5;padding:1px 3px 1px 3px;border-top-left-radius:3px;border-bottom-left-radius:3px}span.task-icon-age-column{border:1px solid #e5e5e5;border-left:none;margin-left:-5px;padding:1px 3px 1px 3px;border-top-right-radius:3px;border-bottom-right-radius:3px}.task-board span.task-icon-age-total,.task-board span.task-icon-age-column{border-color:#666}.task-board-category-container{text-align:right;margin-top:8px;margin-bottom:8px}.task-board-category{border:1px solid #555;font-size:.9em;font-weight:500;color:#000;padding:1px 3px 1px 2px;border-radius:3px}.task-board-category a:hover{text-decoration:underline}.task-date{font-weight:500;color:#000}span.task-date-today{opacity:1;color:var(--link-color-primary)}span.task-date-overdue{opacity:1;color:#b94a48}.task-tags li{display:inline-block;margin:3px 3px 0 0;padding:1px 3px 1px 3px;color:var(--color-primary);border:1px solid #333;border-radius:4px}.task-summary-container .task-tags{margin-top:10px}#task-summary{margin-bottom:15px}#task-summary h2{color:var(--color-medium);font-size:1.6em;margin-top:0;padding-top:0}.task-summary-container{border:2px solid #000;border-radius:8px;padding:10px}.task-summary-columns{display:flex;flex-flow:row;justify-content:space-between}@media (max-width:768px){.task-summary-columns{flex-flow:column}}.task-summary-column{color:var(--color-dark)}.task-summary-column span{color:var(--color-medium)}.task-summary-column li{line-height:23px}#external-task-view{padding:10px;margin-top:10px;margin-bottom:10px;border:1px dotted #ccc}.task-form-container{box-sizing:border-box;display:flex;flex-wrap:wrap}.task-form-container>*{box-sizing:border-box}.task-form-container>*{width:1%}.task-form-main-column{width:60%}@media (max-width:1000px){.task-form-main-column{width:100%}}.task-form-main-column input[type="text"]{width:700px;max-width:99%}.task-form-secondary-column{max-width:250px;min-width:200px;max-height:600px;padding-left:10px;overflow:auto;width:20%}@media (max-width:1000px){.task-form-secondary-column{width:100%;max-width:99%;max-height:none}}@media (max-width:768px){.task-form-secondary-column{padding-left:0}}.task-form-secondary-column label:first-child{margin-top:0}@media (max-width:1000px){.task-form-secondary-column label:first-child{margin-top:10px}}.task-form-bottom{width:100%}.task-form-bottom label{display:inline-block}.task-form-bottom-column{display:inline-block;width:49%;margin-left:5px;margin-right:5px}.comment-sorting{text-align:right}.comment-sorting a{color:var(--color-medium);font-weight:400;text-decoration:none}.comment-sorting a:hover{color:var(--color-light)}.comment{padding:5px;margin-bottom:15px}.comment-title{border-bottom:1px dotted var(--comment-title-border-color);margin-left:55px}.comment-date{color:var(--color-light);font-weight:200}.comment-visibility{color:var(--color-light);font-weight:200}.comment-actions{text-align:right}.comment-content{margin-left:55px}.comments .text-editor textarea{height:90px}.comments .text-editor .text-editor-preview-area{height:90px}.comments .comment-highlighted{background-color:var(--comment-highlighted-background-color);border:2px solid var(--comment-highlighted-border-color)}.comments .comment-highlighted:hover{background-color:var(--comment-highlighted-hover-background-color)}.comments .comment:hover{background:var(--comment-highlighted-hover-background-color)}.comments .comment:nth-child(even):not(.comment-highlighted){background:var(--comment-nth-background-color)}.comments .comment:nth-child(even):not(.comment-highlighted):hover{background:var(--comment-highlighted-hover-background-color)}.subtask-cell{padding:4px 10px;border-top:1px dotted #dedede;border-left:1px dotted #dedede;display:table-cell;vertical-align:middle}.subtask-cell a{color:var(--color-primary);text-decoration:none}.subtask-cell a:hover,.subtask-cell a:focus{color:var(--link-color-primary)}.subtask-cell:first-child{border-left:none}@media (max-width:768px){.subtask-cell{width:90%;display:block;border-left:none}}.subtasks-table .subtask-table-td{display:flex;white-space:normal;min-width:400px}.subtasks-table .subtask-submenu{display:flex}.js-subtask-toggle-status{display:flex;text-decoration:none}.task-list-subtasks{display:table;width:100%}@media (max-width:768px){.task-list-subtasks{display:block}}.task-list-subtask{display:table-row}@media (max-width:768px){.task-list-subtask{display:block}}@media (max-width:768px){.subtask-assignee,.subtask-time-tracking-cell{display:none}}.subtask-time-tracking{white-space:normal}.task-links-table td{vertical-align:middle}.task-links-task-count{color:var(--color-light);font-weight:400}.task-link-closed{text-decoration:line-through}.task-links-table-td{display:flex}.text-editor{margin-top:10px}.text-editor a{font-size:1em;color:var(--color-light);text-decoration:none;margin-right:10px}.text-editor a:hover{color:var(--link-color-primary)}.text-editor .text-editor-preview-area{border:1px solid #dedede;width:700px;max-width:99%;height:250px;overflow:auto;padding:2px}.text-editor textarea{width:700px;max-width:98%;height:250px}.markdown{line-height:1.4em}.markdown h1{margin-top:5px;margin-bottom:10px;font-weight:700}.markdown h2{font-weight:700}.markdown p{margin-bottom:10px}.markdown ol,.markdown ul{margin-left:25px;margin-top:10px;margin-bottom:10px}.markdown pre{background:#fbfbfb;padding:10px;border-radius:5px;border:1px solid #ddd;overflow:auto;overflow-wrap:initial;color:var(--color-medium)}.markdown blockquote{font-style:italic;border-left:3px solid #ddd;padding-left:10px;margin-bottom:10px;margin-left:20px}.markdown img{display:block;max-width:80%;margin-top:10px}.panel{border-radius:4px;padding:8px 35px 8px 10px;margin-top:10px;margin-bottom:15px;border:1px solid var(--panel-border-color);color:var(--color-primary);background-color:var(--panel-background-color);overflow:auto}.panel li{list-style-type:square;margin-left:20px;line-height:1.35em}.activity-event{margin-bottom:15px;padding:10px}.activity-event:nth-child(even){background:var(--activity-event-background-color)}.activity-event:hover{background:var(--activity-event-hover-color)}.activity-date{margin-left:10px;font-weight:400;color:var(--color-light)}.activity-content{margin-left:55px}.activity-title{font-weight:700;color:var(--activity-title-color);border-bottom:1px dotted var(--activity-title-border-color)}.activity-description{color:var(--color-light);margin-top:10px}@media (max-width:480px){.activity-description{overflow:auto}}.activity-description li{list-style-type:circle}.activity-description ul{margin-top:10px;margin-left:20px}.user-mention-link{font-weight:700;color:var(--user-mention-color);text-decoration:none}.user-mention-link:hover{color:var(--color-medium)}.image-slideshow-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.95);overflow:auto;z-index:100}.image-slideshow-overlay img{display:block;margin:auto}.image-slideshow-overlay figcaption{color:#fff;opacity:.7;position:absolute;bottom:5px;right:15px}.slideshow-icon{color:#fff;position:absolute;font-size:2.5em;opacity:.6}.slideshow-icon:hover{opacity:.9;cursor:pointer}.slideshow-previous-icon{left:10px;top:45%}.slideshow-next-icon{right:10px;top:45%}.slideshow-close-icon{right:10px;top:10px;font-size:1.4em}.slideshow-download-icon{left:10px;bottom:10px;font-size:1.3em}.list-item-links,.list-item-actions{display:inline-block;float:left;margin-left:10px}.list-item-links a{margin:0}.list-item-action-hidden{display:none}.bulk-change-checkbox{float:left}.bulk-change-inputs{float:left;padding-left:10px}.bulk-change-inputs label{margin-top:0;margin-bottom:3px} \ No newline at end of file diff --git a/assets/css/images/ui-bg_flat_0_aaaaaa_40x100.png b/assets/css/images/ui-bg_flat_0_aaaaaa_40x100.png new file mode 100644 index 0000000..a2e6bfc Binary files /dev/null and b/assets/css/images/ui-bg_flat_0_aaaaaa_40x100.png differ diff --git a/assets/css/images/ui-bg_flat_75_ffffff_40x100.png b/assets/css/images/ui-bg_flat_75_ffffff_40x100.png new file mode 100644 index 0000000..e36540b Binary files /dev/null and b/assets/css/images/ui-bg_flat_75_ffffff_40x100.png differ diff --git a/assets/css/images/ui-bg_glass_55_fbf9ee_1x400.png b/assets/css/images/ui-bg_glass_55_fbf9ee_1x400.png new file mode 100644 index 0000000..579a9ca Binary files /dev/null and b/assets/css/images/ui-bg_glass_55_fbf9ee_1x400.png differ diff --git a/assets/css/images/ui-bg_glass_65_ffffff_1x400.png b/assets/css/images/ui-bg_glass_65_ffffff_1x400.png new file mode 100644 index 0000000..1c62dbe Binary files /dev/null and b/assets/css/images/ui-bg_glass_65_ffffff_1x400.png differ diff --git a/assets/css/images/ui-bg_glass_75_dadada_1x400.png b/assets/css/images/ui-bg_glass_75_dadada_1x400.png new file mode 100644 index 0000000..b286bf0 Binary files /dev/null and b/assets/css/images/ui-bg_glass_75_dadada_1x400.png differ diff --git a/assets/css/images/ui-bg_glass_75_e6e6e6_1x400.png b/assets/css/images/ui-bg_glass_75_e6e6e6_1x400.png new file mode 100644 index 0000000..607e1f9 Binary files /dev/null and b/assets/css/images/ui-bg_glass_75_e6e6e6_1x400.png differ diff --git a/assets/css/images/ui-bg_glass_95_fef1ec_1x400.png b/assets/css/images/ui-bg_glass_95_fef1ec_1x400.png new file mode 100644 index 0000000..b35df9d Binary files /dev/null and b/assets/css/images/ui-bg_glass_95_fef1ec_1x400.png differ diff --git a/assets/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/assets/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png new file mode 100644 index 0000000..d3e97a6 Binary files /dev/null and b/assets/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png differ diff --git a/assets/css/images/ui-icons_222222_256x240.png b/assets/css/images/ui-icons_222222_256x240.png new file mode 100644 index 0000000..3bba819 Binary files /dev/null and b/assets/css/images/ui-icons_222222_256x240.png differ diff --git a/assets/css/images/ui-icons_2e83ff_256x240.png b/assets/css/images/ui-icons_2e83ff_256x240.png new file mode 100644 index 0000000..19600a8 Binary files /dev/null and b/assets/css/images/ui-icons_2e83ff_256x240.png differ diff --git a/assets/css/images/ui-icons_444444_256x240.png b/assets/css/images/ui-icons_444444_256x240.png new file mode 100644 index 0000000..19f664d Binary files /dev/null and b/assets/css/images/ui-icons_444444_256x240.png differ diff --git a/assets/css/images/ui-icons_454545_256x240.png b/assets/css/images/ui-icons_454545_256x240.png new file mode 100644 index 0000000..9df8cc5 Binary files /dev/null and b/assets/css/images/ui-icons_454545_256x240.png differ diff --git a/assets/css/images/ui-icons_555555_256x240.png b/assets/css/images/ui-icons_555555_256x240.png new file mode 100644 index 0000000..e965f6d Binary files /dev/null and b/assets/css/images/ui-icons_555555_256x240.png differ diff --git a/assets/css/images/ui-icons_777620_256x240.png b/assets/css/images/ui-icons_777620_256x240.png new file mode 100644 index 0000000..9785948 Binary files /dev/null and b/assets/css/images/ui-icons_777620_256x240.png differ diff --git a/assets/css/images/ui-icons_777777_256x240.png b/assets/css/images/ui-icons_777777_256x240.png new file mode 100644 index 0000000..323c456 Binary files /dev/null and b/assets/css/images/ui-icons_777777_256x240.png differ diff --git a/assets/css/images/ui-icons_888888_256x240.png b/assets/css/images/ui-icons_888888_256x240.png new file mode 100644 index 0000000..4869f3a Binary files /dev/null and b/assets/css/images/ui-icons_888888_256x240.png differ diff --git a/assets/css/images/ui-icons_cc0000_256x240.png b/assets/css/images/ui-icons_cc0000_256x240.png new file mode 100644 index 0000000..45ac778 Binary files /dev/null and b/assets/css/images/ui-icons_cc0000_256x240.png differ diff --git a/assets/css/images/ui-icons_cd0a0a_256x240.png b/assets/css/images/ui-icons_cd0a0a_256x240.png new file mode 100644 index 0000000..6eda2d9 Binary files /dev/null and b/assets/css/images/ui-icons_cd0a0a_256x240.png differ diff --git a/assets/css/images/ui-icons_ffffff_256x240.png b/assets/css/images/ui-icons_ffffff_256x240.png new file mode 100644 index 0000000..fe41d2d Binary files /dev/null and b/assets/css/images/ui-icons_ffffff_256x240.png differ diff --git a/assets/css/light.min.css b/assets/css/light.min.css new file mode 100644 index 0000000..c0e8e3a --- /dev/null +++ b/assets/css/light.min.css @@ -0,0 +1 @@ +:root{--body-background-color:#FFF;--header-background-color:#fbfbfb;--color-primary:#333;--color-light:#777;--color-lighter:#dedede;--color-dark:#000;--color-medium:#555;--color-error:#b94a48;--link-color-primary:#36C;--link-color-focus:#DF5353;--link-color-hover:#333;--alert-color-default:#c09853;--alert-color-success:#468847;--alert-color-error:#b94a48;--alert-color-info:#3a87ad;--alert-color-normal:#333;--alert-background-color-default:#fcf8e3;--alert-background-color-success:#dff0d8;--alert-background-color-error:#f2dede;--alert-background-color-info:#d9edf7;--alert-background-color-normal:#f0f0f0;--alert-border-color-default:#fbeed5;--alert-border-color-success:#d6e9c6;--alert-border-color-error:#eed3d7;--alert-border-color-info:#bce8f1;--alert-border-color-normal:#ddd;--button-default-color:#333;--button-default-background-color:#f5f5f5;--button-default-border-color:#ddd;--button-default-color-focus:#000;--button-default-background-color-focus:#fafafa;--button-default-border-color-focus:#bbb;--button-primary-color:#fff;--button-primary-background-color:#4d90fe;--button-primary-border-color:#3079ed;--button-primary-color-focus:#fff;--button-primary-background-color-focus:#357ae8;--button-primary-border-color-focus:#3079ed;--button-danger-color:#fff;--button-danger-background-color:#d14836;--button-danger-border-color:#b0281a;--button-danger-color-focus:#fff;--button-danger-background-color-focus:#c53727;--button-danger-border-color-focus:#b0281a;--button-disabled-color:#ccc;--button-disabled-background-color:#f7f7f7;--button-disabled-border-color:#ccc;--table-header-background-color:#fbfbfb;--table-nth-background-color:#fefefe;--table-border-color:#eee;--avatar-color-letter:#fff;--activity-title-color:#000;--activity-title-border-color:#efefef;--activity-event-background-color:#fafafa;--activity-event-hover-color:#fff8dc;--user-mention-color:#000;--board-task-limit-color:#DF5353;--table-list-header-border-color:#e5e5e5;--table-list-header-background-color:#fbfbfb;--table-list-nth-background-color:#fefefe;--table-list-border-color:#e5e5e5;--table-list-row-hover-border-color:#ffeb8e;--table-list-row-background-color:#fff8dc;--sidebar-border-color:#efefef;--dropdown-background-color:#fff;--dropdown-border-color:#b2b2b2;--dropdown-li-border-color:#f8f8f8;--input-addon-background-color:rgba(147,128,108,.1);--input-addon-color:#666;--views-background-color:#fafafa;--views-border-color:#ddd;--views-active-color:#000;--input-focus-color:#000;--input-focus-border-color:rgba(82,168,236,.8);--input-focus-shadow-color:rgba(82,168,236,.6);--input-background-color:#fff;--input-border-color:#ccc;--input-placeholder-color:#dedede;--tooltip-background-color:#fff;--tooltip-border-color:#ddd;--tooltip-shadow-color:#aaa;--panel-background-color:#fcfcfc;--panel-border-color:#ddd;--draggable-item-selected-background-color:#fff;--draggable-item-selected-border-color:#666;--draggable-item-hover-background-color:#FEFFF2;--draggable-row-handle-color:#dedede;--draggable-placeholder-background-color:#fafafa;--draggable-placeholder-border-color:#000;--task-list-icons-color:#999;--form-help-color:brown;--form-error-color:#b94a48;--comment-title-border-color:#eee;--comment-nth-background-color:#fbfbfb;--comment-highlighted-background-color:#fff8dc;--comment-highlighted-hover-background-color:#fff8dc;--comment-highlighted-border-color:#ffeb8e}html{color-scheme:light}h1,li,ul,ol,table,tr,td,th,p,blockquote,body{margin:0;padding:0}body{background-color:var(--body-background-color);font-size:100%;padding-bottom:10px;color:var(--color-primary);font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;text-rendering:optimizeLegibility;overflow-x:hidden}small{font-size:.8em}hr{border:0;height:0;border-top:1px solid rgba(0,0,0,.1);border-bottom:1px solid rgba(255,255,255,.3)}.page{margin-left:10px;margin-right:10px}.margin-top{margin-top:20px}.margin-bottom{margin-bottom:20px}.pull-right{text-align:right;margin-left:auto}ul.no-bullet li{list-style-type:none;margin-left:0}#app-loading-icon{position:fixed;right:3px;bottom:3px}.assign-me{vertical-align:bottom}a{color:var(--link-color-primary);border:none}a:focus{color:var(--link-color-focus);outline:0;text-decoration:none}a:hover{color:var(--link-color-hover);text-decoration:none}a .fa{color:var(--color-primary);padding-right:3px;text-decoration:none}h1,h2,h3{font-weight:400;color:var(--color-primary)}h1{font-size:1.5em}h2{font-size:1.4em;margin-bottom:10px}h3{margin-top:10px;font-size:1.2em}table{width:100%;border-collapse:collapse;border-spacing:0;margin-bottom:20px}table.table-fixed{table-layout:fixed;white-space:nowrap}table.table-fixed th{overflow:hidden}table.table-fixed td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}table.table-small{font-size:.8em}table.table-striped tr:nth-child(odd){background:var(--table-nth-background-color)}@media (max-width:768px){table.table-scrolling{overflow-x:auto;display:inline-block;vertical-align:top;max-width:100%;white-space:nowrap}}table th{text-align:left;padding:.5em 3px;border:1px solid var(--table-border-color);background-color:var(--table-header-background-color)}table th a{text-decoration:none;color:var(--color-primary)}table th a:focus,table th a:hover{text-decoration:underline}table td{border:1px solid var(--table-border-color);padding:.5em 3px;vertical-align:top}table td li{margin-left:20px}table td .color-picker-square{display:inline-block;width:12px;height:12px;border:1px solid #000}.task-table a{color:#000}.column-1{width:1%}.column-2{width:2%}.column-3{width:3%}.column-4{width:4%}.column-5{width:5%}.column-6{width:6%}.column-7{width:7%}.column-8{width:8%}.column-9{width:9%}.column-10{width:10%}.column-11{width:11%}.column-12{width:12%}.column-13{width:13%}.column-14{width:14%}.column-15{width:15%}.column-16{width:16%}.column-17{width:17%}.column-18{width:18%}.column-19{width:19%}.column-20{width:20%}.column-21{width:21%}.column-22{width:22%}.column-23{width:23%}.column-24{width:24%}.column-25{width:25%}.column-26{width:26%}.column-27{width:27%}.column-28{width:28%}.column-29{width:29%}.column-30{width:30%}.column-31{width:31%}.column-32{width:32%}.column-33{width:33%}.column-34{width:34%}.column-35{width:35%}.column-36{width:36%}.column-37{width:37%}.column-38{width:38%}.column-39{width:39%}.column-40{width:40%}.column-41{width:41%}.column-42{width:42%}.column-43{width:43%}.column-44{width:44%}.column-45{width:45%}.column-46{width:46%}.column-47{width:47%}.column-48{width:48%}.column-49{width:49%}.column-50{width:50%}.column-51{width:51%}.column-52{width:52%}.column-53{width:53%}.column-54{width:54%}.column-55{width:55%}.column-56{width:56%}.column-57{width:57%}.column-58{width:58%}.column-59{width:59%}.column-60{width:60%}.column-61{width:61%}.column-62{width:62%}.column-63{width:63%}.column-64{width:64%}.column-65{width:65%}.column-66{width:66%}.column-67{width:67%}.column-68{width:68%}.column-69{width:69%}.column-70{width:70%}.column-71{width:71%}.column-72{width:72%}.column-73{width:73%}.column-74{width:74%}.column-75{width:75%}.column-76{width:76%}.column-77{width:77%}.column-78{width:78%}.column-79{width:79%}.column-80{width:80%}.column-81{width:81%}.column-82{width:82%}.column-83{width:83%}.column-84{width:84%}.column-85{width:85%}.column-86{width:86%}.column-87{width:87%}.column-88{width:88%}.column-89{width:89%}.column-90{width:90%}.column-91{width:91%}.column-92{width:92%}.column-93{width:93%}.column-94{width:94%}.column-95{width:95%}.column-96{width:96%}.column-97{width:97%}.column-98{width:98%}.column-99{width:99%}.column-100{width:100%}.draggable-row-handle{cursor:move;color:var(--draggable-row-handle-color)}.draggable-row-handle:hover{color:var(--color-primary)}tr.draggable-item-selected{background:var(--draggable-item-selected-background-color);border:2px solid var(--draggable-item-selected-border-color);box-shadow:4px 2px 10px -4px rgba(0,0,0,.55)}tr.draggable-item-selected td{border-top:none;border-bottom:none}tr.draggable-item-selected td:first-child{border-left:none}tr.draggable-item-selected td:last-child{border-right:none}.table-stripped tr.draggable-item-hover,.table-stripped tr.draggable-item-hover{background:var(--draggable-item-hover-background-color)}.table-list{font-size:.85em;margin-bottom:20px}.table-list-header{background:var(--table-list-header-background-color);border:1px solid var(--table-list-header-border-color);border-radius:5px 5px 0 0;line-height:28px;padding-left:3px;padding-right:3px}.table-list-header a{color:var(--color-primary);font-weight:500;text-decoration:none;margin-right:10px}.table-list-header a:hover,.table-list-header a:focus{color:#767676}.table-list-header .table-list-header-count{color:#767676;display:inline-block;float:left}.table-list-header .table-list-header-menu{text-align:right}.table-list-row{padding-left:3px;padding-right:3px;border-bottom:1px solid var(--table-list-border-color);border-right:1px solid var(--table-list-border-color)}.table-list-row.table-border-left{border-left:1px solid var(--table-list-border-color)}.table-list-row:nth-child(odd){background:var(--table-list-nth-background-color)}.table-list-row:last-child{border-radius:0 0 5px 5px}.table-list-row:hover{background:var(--table-list-row-background-color);border-bottom:1px solid var(--table-list-row-hover-border-color);border-right:1px solid var(--table-list-row-hover-border-color)}.table-list-row .table-list-title{font-weight:500;line-height:23px}.table-list-row .table-list-title.status-closed{text-decoration:line-through;margin-right:10px}.table-list-row .table-list-title.status-closed a{font-style:italic}.table-list-row .table-list-title a{color:var(--color-primary);text-decoration:none}.table-list-row .table-list-title a:hover,.table-list-row .table-list-title a:focus{text-decoration:underline}.table-list-row .table-list-details{color:#999;font-weight:300;line-height:20px}.table-list-row .table-list-details span{margin-left:5px}.table-list-row .table-list-details span:first-child{margin-left:0}.table-list-row .table-list-details li{display:inline;list-style-type:none}.table-list-row .table-list-details li:after{content:', '}.table-list-row .table-list-details li:last-child:after{content:''}.table-list-row .table-list-details strong{font-weight:400;color:#555}.table-list-row .table-list-details-with-icons{float:left}@media (max-width:768px){.table-list-row .table-list-details-with-icons{float:none}}.table-list-row .table-list-icons{font-size:.8em;text-align:right;line-height:30px}@media (max-width:768px){.table-list-row .table-list-icons{text-align:left;line-height:20px}}.table-list-row .table-list-icons span{margin-left:5px}.table-list-row .table-list-icons a{text-decoration:none}.table-list-row .table-list-icons a:hover{color:var(--color-primary)}.table-list-row .table-list-icons a:hover i{color:var(--color-primary)}.table-list-category{font-size:.9em;font-weight:500;color:#000;padding:1px 2px 1px 2px;border-radius:3px;background:#fcfcfc;border:1px solid #ccc}.table-list-category a{text-decoration:none;color:#000}.table-list-category a:hover{color:#36c}fieldset{border:1px solid #ddd;margin-top:10px}legend{font-weight:500;font-size:1.2em}label{cursor:pointer;display:block;margin-top:10px;font-weight:400}input,textarea{font-family:sans-serif;background-color:var(--input-background-color)}input[type="number"],input[type="date"],input[type="email"],input[type="password"],input[type="text"]:not(.input-addon-field){color:var(--color-light);border:1px solid var(--input-border-color);width:300px;max-width:95%;font-size:1em;height:25px;padding-bottom:0;padding-left:4px;-webkit-appearance:none;-moz-appearance:none}input[type="number"]::placeholder,input[type="date"]::placeholder,input[type="email"]::placeholder,input[type="password"]::placeholder,input[type="text"]:not(.input-addon-field)::placeholder{color:var(--input-placeholder-color)}input[type="number"]:focus,input[type="date"]:focus,input[type="email"]:focus,input[type="password"]:focus,input[type="text"]:focus{color:var(--input-focus-color);border-color:var(--input-focus-border-color);outline:0;box-shadow:0 0 8px var(--input-focus-shadow-color)}input[type="number"]{width:70px}input[type="text"]:not(.input-addon-field).form-numeric{width:70px}input[type="text"]:not(.input-addon-field).form-datetime,input[type="text"]:not(.input-addon-field).form-date{width:150px}input[type="text"]:not(.input-addon-field).form-input-large{width:400px}input[type="text"]:not(.input-addon-field).form-input-small{width:150px}textarea:focus{color:var(--input-focus-color);border-color:var(--input-focus-border-color);outline:0;box-shadow:0 0 8px var(--input-focus-shadow-color)}textarea{padding:4px;border:1px solid var(--input-border-color);width:400px;max-width:99%;height:200px;font-size:1em}textarea::placeholder{color:var(--input-placeholder-color)}select{font-size:1em;max-width:95%}select:focus{outline:0}select[multiple]{width:300px}.tag-autocomplete{width:400px}span.select2-container{margin-top:2px}.form-actions{padding-top:20px;clear:both}.form-required{color:red;padding-left:5px;font-weight:700}@media (max-width:480px){.form-required{display:none}}input[type="text"].form-max-width{width:100%}input.form-error,textarea.form-error{border:2px solid var(--form-error-color)}input.form-error:focus,textarea.form-error:focus{box-shadow:none;border:2px solid var(--form-error-color)}.form-errors{color:var(--form-error-color);list-style-type:none}ul.form-errors li{margin-left:0}.form-help{font-size:.8em;color:var(--form-help-color);margin-bottom:15px}.form-inline{padding:0;margin:0;border:none}.form-inline label{display:inline;padding-right:3px}.form-inline input,.form-inline select{margin:0 15px 0 0}.form-inline .form-required{display:none}.form-inline .form-actions{display:inline-block}.form-inline .js-submit-buttons-rendered{display:inline-block}.form-inline-group{display:inline}.form-columns{display:-webkit-flex;display:flex;-webkit-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;flex-wrap:wrap;-webkit-justify-content:flex-start;justify-content:flex-start}.form-columns .form-column{margin-right:25px;flex-grow:1}.form-columns fieldset{margin-top:0}.form-login{max-width:350px;margin:5% auto 0}@media (max-width:480px){.form-login{margin-left:5px}}.form-login li{margin-left:25px;line-height:25px}.form-login h2{margin-bottom:30px;font-weight:700}.reset-password{margin-top:20px;margin-bottom:20px}.reset-password a{color:var(--color-light)}.input-addon{display:flex}.input-addon-field{flex:1;font-size:1em;color:var(--color-light);margin:0;-webkit-appearance:none;-moz-appearance:none}.input-addon-field:first-child{border-radius:5px 0 0 5px}.input-addon-field:last-child{border-radius:0 5px 5px 0}.input-addon-item{background-color:var(--input-addon-background-color);color:var(--input-addon-color);font:inherit;font-weight:400}.input-addon-item:first-child{border-radius:5px 0 0 5px}.input-addon-item:last-child{border-radius:0 5px 5px 0}@media (max-width:480px){.input-addon-item .dropdown .fa-caret-down{display:none}}.input-addon-field,.input-addon-item{border:1px solid rgba(147,128,108,.25);padding:4px .75em}.input-addon-field:not(:first-child),.input-addon-item:not(:first-child){border-left:0}.input-addon .input-addon-field{flex:1 1 auto;width:1%!important}@media (max-width:400px){.input-addon-item{padding:3px}}.icon-success{color:#468847}.icon-error{color:#b94a48}.icon-fade-out{opacity:1;animation:icon-fadeout 5s linear forwards}@keyframes icon-fadeout{0%{opacity:1}100%{opacity:0}}.alert{padding:8px 35px 8px 14px;margin-top:5px;margin-bottom:5px;color:var(--alert-color-default);background-color:var(--alert-background-color-default);border:1px solid var(--alert-border-color-default);border-radius:4px}.alert-success{color:var(--alert-color-success);background-color:var(--alert-background-color-success);border-color:var(--alert-border-color-success)}.alert-error{color:var(--alert-color-error);background-color:var(--alert-background-color-error);border-color:var(--alert-border-color-error)}.alert-info{color:var(--alert-color-info);background-color:var(--alert-background-color-info);border-color:var(--alert-border-color-info)}.alert-normal{color:var(--alert-color-normal);background-color:var(--alert-background-color-normal);border-color:var(--alert-border-color-normal)}.alert ul{margin-top:10px;margin-bottom:10px}.alert li{margin-left:25px}.alert-fade-out{text-align:center;position:fixed;bottom:0;left:20%;width:60%;padding-top:5px;padding-bottom:5px;margin-bottom:0;border-width:1px 0 0;border-radius:4px 4px 0 0;z-index:9999;opacity:1;animation:fadeout 5s linear forwards}@keyframes fadeout{0%{opacity:1}100%{opacity:0;visibility:hidden}}a.btn{text-decoration:none}.btn{-webkit-appearance:none;-moz-appearance:none;font-size:1.2em;font-weight:400;cursor:pointer;display:inline-block;border-radius:2px;padding:3px 10px;margin:0;border:1px solid var(--button-default-border-color);background:var(--button-default-background-color);color:var(--button-default-color)}.btn:hover,.btn:focus{border-color:var(--button-default-border-color-focus);background:var(--button-default-background-color-focus);color:var(--button-default-color-focus)}.btn-red{border-color:var(--button-danger-border-color);background:var(--button-danger-background-color);color:var(--button-danger-color)}.btn-red:hover,.btn-red:focus{border-color:var(--button-danger-border-color-focus);background:var(--button-danger-background-color-focus);color:var(--button-danger-color-focus)}.btn-blue{border-color:var(--button-primary-border-color);background:var(--button-primary-background-color);color:var(--button-primary-color)}.btn-blue:hover,.btn-blue:focus{border-color:var(--button-primary-border-color-focus);background:var(--button-primary-background-color-focus);color:var(--button-primary-color-focus)}.btn:disabled{color:var(--button-disabled-color);border-color:var(--button-disabled-border-color);background:var(--button-disabled-background-color)}.buttons-header{font-size:.8em;margin-top:5px;margin-bottom:15px}.tooltip i.fa{cursor:pointer}.tooltip .fa-info-circle{color:var(--color-light)}#tooltip-container{padding:5px;background:var(--tooltip-background-color);border:1px solid var(--tooltip-border-color);border-radius:4px;box-shadow:0 6px 12px var(--tooltip-shadow-color);position:absolute;min-width:350px}#tooltip-container .markdown p:last-child{margin-bottom:0}#tooltip-container .tooltip-large{width:600px}h2 .dropdown ul{display:none}.dropdown{display:inline;position:relative}.dropdown ul{display:none}.dropdown-smaller{font-size:.85em}ul.dropdown-submenu-open{display:block;position:absolute;z-index:1000;min-width:285px;list-style:none;margin:3px 0 0 1px;padding:6px 0;background-color:var(--dropdown-background-color);border:1px solid var(--dropdown-border-color);border-radius:3px;box-shadow:0 1px 3px rgba(0,0,0,.15)}.dropdown-submenu-open li{display:block;margin:0;padding:8px 10px;font-size:.9em;border-bottom:1px solid var(--dropdown-li-border-color);cursor:pointer}.dropdown-submenu-open li.no-hover{cursor:default}.dropdown-submenu-open li:last-child{border:none}.dropdown-submenu-open li:not(.no-hover):hover{background:#4078C0;color:#fff}.dropdown-submenu-open li:hover a{color:#fff}.dropdown-submenu-open li:hover i{color:#fff}.dropdown-submenu-open a{text-decoration:none;color:var(--color-primary)}.dropdown-submenu-open a:focus{text-decoration:underline}.dropdown-menu-link-text,.dropdown-menu-link-icon{color:var(--color-primary);text-decoration:none}.dropdown-menu-link-icon{display:inline-flex}.dropdown-menu-link-text:hover{text-decoration:underline}td a.dropdown-menu strong{color:var(--color-primary)}td a.dropdown-menu strong i{color:var(--color-primary)}td a.dropdown-menu i{color:#dedede}td a.dropdown-menu:hover strong{color:#555}td a.dropdown-menu:hover strong i{color:#555}td a.dropdown-menu:hover i{color:#333}.accordion-title{font-size:1.2em;cursor:pointer;margin-top:10px}.accordion-content{margin-top:15px;margin-bottom:25px}#select-dropdown-menu{position:absolute;display:block;z-index:1000;min-width:160px;padding:5px 0;background:var(--dropdown-background-color);list-style:none;border:1px solid var(--dropdown-border-color);border-radius:3px;box-shadow:0 6px 12px rgba(0,0,0,.175);overflow:scroll}.select-dropdown-menu-item{white-space:nowrap;overflow:hidden;padding:3px 10px;color:var(--color-medium);cursor:pointer;border-bottom:1px solid var(--dropdown-li-border-color);line-height:1.5em;font-weight:400}.select-dropdown-menu-item.active{color:#fff;background:#428bca}.select-dropdown-menu-item:last-child{border:none}.select-dropdown-input-container{position:relative;border:1px solid var(--input-border-color);border-radius:5px;background-color:var(--input-background-color);max-width:300px}.select-dropdown-input-container input.select-dropdown-input{margin:0 0 0 5px;border:none;height:23px;width:270px}.select-dropdown-input-container input.select-dropdown-input:focus{border:none;box-shadow:none}.select-dropdown-input-container .select-dropdown-chevron{color:var(--color-medium);position:absolute;top:4px;right:5px;cursor:pointer}.select-dropdown-input-container .select-loading-icon{color:var(--color-medium);position:absolute;top:4px;right:5px}#suggest-menu{position:absolute;display:block;z-index:1000;min-width:160px;padding:5px 0;background:#fff;list-style:none;border:1px solid #ccc;border-radius:3px;box-shadow:0 6px 12px rgba(0,0,0,.175)}.suggest-menu-item{white-space:nowrap;padding:3px 10px;color:var(--color-primary);font-weight:700;cursor:pointer}.suggest-menu-item.active{color:#fff;background:#428bca}.suggest-menu-item.active small{color:#fff}.suggest-menu-item small{color:var(--color-light);font-weight:400}#modal-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.9);overflow:auto;z-index:100}#modal-box{position:fixed;max-height:calc(100% - 30px);top:2%;left:50%;transform:translateX(-50%);background:var(--body-background-color);overflow:auto;border-radius:5px}#modal-content{padding:0 5px 5px}#modal-header{text-align:right;padding-right:5px}#modal-close-button{color:var(--color-primary)}#modal-close-button:hover{color:var(--color-error)}.pagination{text-align:center;font-size:.9em}.pagination-showing{margin-right:5px;padding-right:5px;border-right:1px solid #999}.pagination-next{margin-left:5px}.pagination-previous{margin-right:5px}header{display:flex;flex-wrap:wrap;padding:5px 10px;margin-bottom:5px;border-bottom:1px solid #dedede;background-color:var(--header-background-color)}header .title-container{flex:1;min-width:300px}@media (max-width:480px){header .title-container{order:3}}header .board-selector-container{min-width:320px;display:flex;align-items:center}@media (max-width:480px){header .board-selector-container{order:2;min-width:300px}header .board-selector-container input[type=text]{max-width:280px}}header .menus-container{min-width:120px;display:flex;align-items:center;justify-content:flex-end}@media (max-width:480px){header .menus-container{order:1;margin-bottom:5px;margin-left:auto}}header h1{font-size:1.5em}header h1 .tooltip{opacity:.3;font-size:.7em}a i.web-notification-icon{color:var(--link-color-primary)}a i.web-notification-icon:focus,a i.web-notification-icon:hover{color:#000}.logo a{opacity:.5;color:#d40000;text-decoration:none}.logo span{color:var(--color-primary)}.logo a:hover{opacity:.8;color:var(--color-primary)}.logo a:focus span,.logo a:hover span{color:#d40000}.page-header{margin-bottom:20px}.page-header .dropdown{padding-right:10px}.page-header h2{margin:0;padding:0;font-weight:700;border-bottom:1px dotted #ccc}.page-header h2 a{color:var(--color-primary);text-decoration:none}.page-header h2 a:focus,.page-header h2 a:hover{color:var(--color-light)}.page-header ul{text-align:left;margin-top:5px;display:inline-block}.page-header li{display:inline;padding-right:15px}@media (max-width:480px){.page-header li{display:block;line-height:1.5em}}.page-header li.active a{color:var(--color-primary);text-decoration:none;font-weight:700}.page-header li.active a:hover,.page-header li.active a:focus{text-decoration:underline}.menu-inline{margin-bottom:5px}.menu-inline li{display:inline;padding-right:15px}.menu-inline li .active a{font-weight:700;color:#000;text-decoration:none}.sidebar-container{height:100%;display:flex;flex-flow:row}@media (max-width:768px){.sidebar-container{flex-flow:wrap}}.sidebar-content{padding-left:10px;flex:1 100%;max-width:85%;overflow-wrap:break-word}@media (max-width:768px){.sidebar-content{padding-left:0;order:1;max-width:100%}}@media only screen and (min-device-width:768px) and (max-device-width:1024px) and (orientation:landscape) and (-webkit-min-device-pixel-ratio:1){.sidebar-content{max-width:75%}}.sidebar{max-width:25%;min-width:230px}@media (max-width:768px){.sidebar{flex:1 auto;order:2}}.sidebar h2{margin-top:0}.sidebar>ul a{text-decoration:none;color:var(--color-light);font-weight:300}.sidebar>ul a:hover{color:var(--color-primary)}.sidebar>ul li{list-style-type:none;line-height:35px;border-bottom:1px dotted var(--sidebar-border-color);padding-left:13px}.sidebar>ul li:hover{border-left:5px solid #555;padding-left:8px}.sidebar>ul li.active{border-left:5px solid #333;padding-left:8px}.sidebar>ul li.active a{color:var(--color-primary);font-weight:400}.sidebar-icons>ul li{padding-left:0}.sidebar-icons>ul li:hover,.sidebar-icons>ul li.active{padding-left:0;border-left:none}.sidebar>ul li.active a:focus,.sidebar>ul li.active a:hover{color:var(--color-medium)}.sidebar>ul li:last-child{margin-bottom:15px}.avatar img{vertical-align:bottom}.avatar-left{float:left;margin-right:10px}.avatar-inline{display:inline-block;margin-right:3px}.avatar-48 img,.avatar-48 div{border-radius:30px}.avatar-48 .avatar-letter{line-height:48px;width:48px;font-size:25px}.avatar-20 img,.avatar-20 div{border-radius:10px}.avatar-20 .avatar-letter{line-height:20px;width:20px;font-size:11px}.avatar-letter{color:var(--avatar-color-letter);text-align:center}#file-dropzone,#screenshot-zone{position:relative;border:2px dashed #ccc;width:99%;height:250px;overflow:auto}#file-dropzone-inner,#screenshot-inner{position:absolute;left:0;bottom:48%;width:100%;text-align:center;color:#aaa}#screenshot-zone.screenshot-pasted{border:2px solid #333}#file-list{margin:20px}#file-list li{list-style-type:none;padding-top:8px;padding-bottom:8px;border-bottom:1px dotted #ddd;width:95%}#file-list li .file-error{font-weight:700;color:#b94a48}.file-thumbnails{display:-webkit-flex;display:flex;-webkit-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;flex-wrap:wrap;-webkit-justify-content:flex-start;justify-content:flex-start}.file-thumbnail{width:250px;border:1px solid #efefef;border-radius:5px;margin-bottom:20px;box-shadow:4px 2px 10px -6px rgba(0,0,0,.55);margin-right:15px}.file-thumbnail img{cursor:pointer;border-top-left-radius:5px;border-top-right-radius:5px}.file-thumbnail img:hover{opacity:.5}.file-thumbnail-content{padding-left:8px;padding-right:8px}.file-thumbnail-title{font-weight:700;font-size:.9em;color:var(--color-medium);overflow:hidden;text-overflow:ellipsis}.file-thumbnail-description{font-size:.8em;color:var(--color-light);margin-top:8px;margin-bottom:5px}.file-viewer{position:relative}.file-viewer img{max-width:95%;max-height:85%;margin-top:10px}.color-picker{width:220px}.color-picker-option{height:25px}.color-picker-square{display:inline-block;width:18px;height:18px;margin-right:5px;border:1px solid #000}.color-picker-label{display:inline-block;vertical-align:bottom;padding-bottom:3px}.filter-box{max-width:100%}.action-menu{color:var(--color-primary);text-decoration:none}.action-menu:hover,.action-menu:focus{text-decoration:underline}.js-project-creation-options{max-width:500px;border-left:3px dotted #efefef;margin-top:20px;padding-left:15px;padding-bottom:5px;padding-top:5px}.project-overview-columns{display:-webkit-flex;display:flex;-webkit-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;flex-wrap:wrap;-webkit-align-items:center;align-items:center;-webkit-justify-content:center;justify-content:center;margin-bottom:20px;font-size:1.4em}@media (max-width:480px){.project-overview-columns{display:block}}.project-overview-column{text-align:center;margin-right:3%;margin-top:5px;padding:3px 15px 3px 15px;border:1px dashed #ddd}@media (max-width:480px){.project-overview-column{text-align:left}}.project-overview-column small{color:var(--color-light)}.project-overview-column strong{color:var(--color-medium);display:block}@media (max-width:480px){.project-overview-column strong{display:inline}}.project-header{margin-bottom:8px}.project-header .dropdown-component{margin-top:4px;margin-right:5px;float:left}@media (max-width:768px){.project-header .dropdown-component{float:none}}.project-header .views-switcher-component{margin-top:4px;margin-bottom:10px;float:left}@media (max-width:768px){.project-header .views-switcher-component{float:none;margin-bottom:10px}}.project-header .filter-box-component form{margin:0}.views{margin-right:10px;margin-top:1px;font-size:.9em}@media (max-width:560px){.views{width:100%}}@media (max-width:768px){.views{margin-top:10px;font-size:1em}}@media (max-width:480px){.views{margin-top:5px}}.views li{white-space:nowrap;background:var(--views-background-color);border:1px solid var(--views-border-color);border-right:none;padding:4px 8px;display:inline}@media (max-width:560px){.views li{display:block;margin-top:5px;border-radius:5px;border:1px solid var(--views-border-color)}}.views li.active a{font-weight:700;color:var(--views-active-color);text-decoration:none}.views li:first-child{border-top-left-radius:5px;border-bottom-left-radius:5px}.views li:last-child{border-right:1px solid var(--views-border-color);border-top-right-radius:5px;border-bottom-right-radius:5px}.views a{color:var(--color-ligth);text-decoration:none}.views a:hover{color:var(--color-primary);text-decoration:underline}.dashboard-project-stats small{margin-right:10px;color:var(--color-light)}.dashboard-table-link{font-weight:700;color:#000;text-decoration:none}.dashboard-table-link:focus,.dashboard-table-link:hover{color:var(--color-light)}.public-board{margin-top:5px}.public-task{max-width:800px;margin:5px auto 0}#board-container{overflow-x:auto}#board{table-layout:fixed;margin-bottom:0}#board tr.board-swimlane-columns-first{visibility:hidden;padding:0}#board th.board-column-header{width:240px}#board th.board-column-header-first{visibility:hidden;padding:0}#board td{vertical-align:top}.board-container-compact{overflow-x:initial}@media all and (-ms-high-contrast:active),(-ms-high-contrast:none){.board-container-compact #board{table-layout:auto}}#board th.board-column-header.board-column-compact{width:initial}.board-column-collapsed{display:none}.board-column-expanded-header{display:flex;align-items:center}td.board-column-task-collapsed{font-weight:700;background-color:var(--table-header-background-color)}#board th.board-column-header-collapsed{width:28px;min-width:28px;text-align:center;overflow:hidden}.board-rotation-wrapper{position:relative;padding:8px 4px;min-height:150px;overflow:hidden}.board-rotation{white-space:nowrap;transform:rotate(90deg);transform-origin:0 100%}.board-column-title .dropdown-menu{text-decoration:none}.board-add-icon{float:left;padding:0 5px}.board-add-icon i{text-decoration:none;color:var(--link-color-primary);font-size:1.4em}.board-add-icon i:focus,.board-add-icon i:hover{text-decoration:none;color:red}.board-column-header-task-count{color:var(--color-light);font-weight:400;font-size:.85em}a.board-swimlane-toggle{text-decoration:none}a.board-swimlane-toggle:hover,a.board-swimlane-toggle:focus{color:#000;text-decoration:none;border:none}.board-task-list{min-height:60px}.board-task-list-compact{max-height:90vh;overflow-y:auto}.board-task-list .task-board:last-child{margin-bottom:0}.board-task-list-limit{background-color:var(--board-task-limit-color)}.draggable-item{cursor:pointer;user-select:none;-webkit-user-select:none;-moz-user-select:none}.draggable-placeholder{border:2px dashed var(--draggable-placeholder-border-color);background:var(--draggable-placeholder-background-color);height:70px;margin-bottom:10px}div.draggable-item-selected{border:1px solid #000}.task-board-sort-handle{float:left;padding-right:5px}.task-board{position:relative;margin-bottom:4px;border:1px solid #000;padding:2px;word-wrap:break-word;font-size:.9em;border-radius:6px}div.task-board-recent{border-width:2px}div.task-board-status-closed{user-select:none;border:1px dotted #555}.task-board a{color:#000;text-decoration:none}.task-board-collapsed{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.task-board-title{margin-top:5px;margin-bottom:8px}.task-board-title a:hover{text-decoration:underline}.task-board-saving-state{opacity:.3}.task-board-saving-icon{position:absolute;margin:auto;width:100%;text-align:center;color:#000}.task-board-avatars{text-align:right;float:right}.task-board-change-assignee{cursor:pointer}.task-board-change-assignee:hover{opacity:.6}.task-list-avatars{display:inline-block;float:left}@media (max-width:768px){.task-list-avatars{float:none;display:block}}.task-list-avatars .task-avatar-assignee{font-weight:300;color:#999}.task-list-avatars:hover .task-avatar-assignee{font-weight:400;color:#000}.task-board-icons,.task-list-icons{font-size:.8em;text-align:right}.task-board-icons a,.task-board-icons span.tooltip,.task-list-icons a,.task-list-icons span.tooltip{text-decoration:none}.task-board-icons a:hover,.task-board-icons span.tooltip:hover,.task-list-icons a:hover,.task-list-icons span.tooltip:hover{color:var(--color-primary)}.task-board-icons a:hover i,.task-board-icons span.tooltip:hover i,.task-list-icons a:hover i,.task-list-icons span.tooltip:hover i{color:var(--color-primary)}.task-board-icons .task-score,.task-list-icons .task-score{font-weight:700}.task-board-icons .flag-milestone,.task-list-icons .flag-milestone{color:green}.task-board-icons{margin-top:7px}.task-board-icons a{opacity:.5}.task-board-icons span{opacity:.5;margin-left:4px}.task-board-icons a:hover,.task-board-icons span.tooltip:hover{opacity:1;font-weight:700}.task-board-icons .task-board-icons-row{line-height:22px}.task-list-icons{line-height:22px}.task-list-icons a,.task-list-icons span,.task-list-icons i{color:var(--task-list-icons-color);opacity:1}.task-list-icons span{margin-left:5px}@media (max-width:768px){.task-list-icons{text-align:left}}.task-icon-age{display:inline-block}span.task-icon-age-total{border:1px solid #e5e5e5;padding:1px 3px 1px 3px;border-top-left-radius:3px;border-bottom-left-radius:3px}span.task-icon-age-column{border:1px solid #e5e5e5;border-left:none;margin-left:-5px;padding:1px 3px 1px 3px;border-top-right-radius:3px;border-bottom-right-radius:3px}.task-board span.task-icon-age-total,.task-board span.task-icon-age-column{border-color:#666}.task-board-category-container{text-align:right;margin-top:8px;margin-bottom:8px}.task-board-category{border:1px solid #555;font-size:.9em;font-weight:500;color:#000;padding:1px 3px 1px 2px;border-radius:3px}.task-board-category a:hover{text-decoration:underline}.task-date{font-weight:500;color:#000}span.task-date-today{opacity:1;color:var(--link-color-primary)}span.task-date-overdue{opacity:1;color:#b94a48}.task-tags li{display:inline-block;margin:3px 3px 0 0;padding:1px 3px 1px 3px;color:var(--color-primary);border:1px solid #333;border-radius:4px}.task-summary-container .task-tags{margin-top:10px}#task-summary{margin-bottom:15px}#task-summary h2{color:var(--color-medium);font-size:1.6em;margin-top:0;padding-top:0}.task-summary-container{border:2px solid #000;border-radius:8px;padding:10px}.task-summary-columns{display:flex;flex-flow:row;justify-content:space-between}@media (max-width:768px){.task-summary-columns{flex-flow:column}}.task-summary-column{color:var(--color-dark)}.task-summary-column span{color:var(--color-medium)}.task-summary-column li{line-height:23px}#external-task-view{padding:10px;margin-top:10px;margin-bottom:10px;border:1px dotted #ccc}.task-form-container{box-sizing:border-box;display:flex;flex-wrap:wrap}.task-form-container>*{box-sizing:border-box}.task-form-container>*{width:1%}.task-form-main-column{width:60%}@media (max-width:1000px){.task-form-main-column{width:100%}}.task-form-main-column input[type="text"]{width:700px;max-width:99%}.task-form-secondary-column{max-width:250px;min-width:200px;max-height:600px;padding-left:10px;overflow:auto;width:20%}@media (max-width:1000px){.task-form-secondary-column{width:100%;max-width:99%;max-height:none}}@media (max-width:768px){.task-form-secondary-column{padding-left:0}}.task-form-secondary-column label:first-child{margin-top:0}@media (max-width:1000px){.task-form-secondary-column label:first-child{margin-top:10px}}.task-form-bottom{width:100%}.task-form-bottom label{display:inline-block}.task-form-bottom-column{display:inline-block;width:49%;margin-left:5px;margin-right:5px}.comment-sorting{text-align:right}.comment-sorting a{color:var(--color-medium);font-weight:400;text-decoration:none}.comment-sorting a:hover{color:var(--color-light)}.comment{padding:5px;margin-bottom:15px}.comment-title{border-bottom:1px dotted var(--comment-title-border-color);margin-left:55px}.comment-date{color:var(--color-light);font-weight:200}.comment-visibility{color:var(--color-light);font-weight:200}.comment-actions{text-align:right}.comment-content{margin-left:55px}.comments .text-editor textarea{height:90px}.comments .text-editor .text-editor-preview-area{height:90px}.comments .comment-highlighted{background-color:var(--comment-highlighted-background-color);border:2px solid var(--comment-highlighted-border-color)}.comments .comment-highlighted:hover{background-color:var(--comment-highlighted-hover-background-color)}.comments .comment:hover{background:var(--comment-highlighted-hover-background-color)}.comments .comment:nth-child(even):not(.comment-highlighted){background:var(--comment-nth-background-color)}.comments .comment:nth-child(even):not(.comment-highlighted):hover{background:var(--comment-highlighted-hover-background-color)}.subtask-cell{padding:4px 10px;border-top:1px dotted #dedede;border-left:1px dotted #dedede;display:table-cell;vertical-align:middle}.subtask-cell a{color:var(--color-primary);text-decoration:none}.subtask-cell a:hover,.subtask-cell a:focus{color:var(--link-color-primary)}.subtask-cell:first-child{border-left:none}@media (max-width:768px){.subtask-cell{width:90%;display:block;border-left:none}}.subtasks-table .subtask-table-td{display:flex;white-space:normal;min-width:400px}.subtasks-table .subtask-submenu{display:flex}.js-subtask-toggle-status{display:flex;text-decoration:none}.task-list-subtasks{display:table;width:100%}@media (max-width:768px){.task-list-subtasks{display:block}}.task-list-subtask{display:table-row}@media (max-width:768px){.task-list-subtask{display:block}}@media (max-width:768px){.subtask-assignee,.subtask-time-tracking-cell{display:none}}.subtask-time-tracking{white-space:normal}.task-links-table td{vertical-align:middle}.task-links-task-count{color:var(--color-light);font-weight:400}.task-link-closed{text-decoration:line-through}.task-links-table-td{display:flex}.text-editor{margin-top:10px}.text-editor a{font-size:1em;color:var(--color-light);text-decoration:none;margin-right:10px}.text-editor a:hover{color:var(--link-color-primary)}.text-editor .text-editor-preview-area{border:1px solid #dedede;width:700px;max-width:99%;height:250px;overflow:auto;padding:2px}.text-editor textarea{width:700px;max-width:98%;height:250px}.markdown{line-height:1.4em}.markdown h1{margin-top:5px;margin-bottom:10px;font-weight:700}.markdown h2{font-weight:700}.markdown p{margin-bottom:10px}.markdown ol,.markdown ul{margin-left:25px;margin-top:10px;margin-bottom:10px}.markdown pre{background:#fbfbfb;padding:10px;border-radius:5px;border:1px solid #ddd;overflow:auto;overflow-wrap:initial;color:var(--color-medium)}.markdown blockquote{font-style:italic;border-left:3px solid #ddd;padding-left:10px;margin-bottom:10px;margin-left:20px}.markdown img{display:block;max-width:80%;margin-top:10px}.panel{border-radius:4px;padding:8px 35px 8px 10px;margin-top:10px;margin-bottom:15px;border:1px solid var(--panel-border-color);color:var(--color-primary);background-color:var(--panel-background-color);overflow:auto}.panel li{list-style-type:square;margin-left:20px;line-height:1.35em}.activity-event{margin-bottom:15px;padding:10px}.activity-event:nth-child(even){background:var(--activity-event-background-color)}.activity-event:hover{background:var(--activity-event-hover-color)}.activity-date{margin-left:10px;font-weight:400;color:var(--color-light)}.activity-content{margin-left:55px}.activity-title{font-weight:700;color:var(--activity-title-color);border-bottom:1px dotted var(--activity-title-border-color)}.activity-description{color:var(--color-light);margin-top:10px}@media (max-width:480px){.activity-description{overflow:auto}}.activity-description li{list-style-type:circle}.activity-description ul{margin-top:10px;margin-left:20px}.user-mention-link{font-weight:700;color:var(--user-mention-color);text-decoration:none}.user-mention-link:hover{color:var(--color-medium)}.image-slideshow-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.95);overflow:auto;z-index:100}.image-slideshow-overlay img{display:block;margin:auto}.image-slideshow-overlay figcaption{color:#fff;opacity:.7;position:absolute;bottom:5px;right:15px}.slideshow-icon{color:#fff;position:absolute;font-size:2.5em;opacity:.6}.slideshow-icon:hover{opacity:.9;cursor:pointer}.slideshow-previous-icon{left:10px;top:45%}.slideshow-next-icon{right:10px;top:45%}.slideshow-close-icon{right:10px;top:10px;font-size:1.4em}.slideshow-download-icon{left:10px;bottom:10px;font-size:1.3em}.list-item-links,.list-item-actions{display:inline-block;float:left;margin-left:10px}.list-item-links a{margin:0}.list-item-action-hidden{display:none}.bulk-change-checkbox{float:left}.bulk-change-inputs{float:left;padding-left:10px}.bulk-change-inputs label{margin-top:0;margin-bottom:3px} \ No newline at end of file diff --git a/assets/css/print.min.css b/assets/css/print.min.css new file mode 100644 index 0000000..c5e3ca2 --- /dev/null +++ b/assets/css/print.min.css @@ -0,0 +1 @@ +@page{orientation:landscape;-webkit-transform:rotate(-90deg);-moz-transform:rotate(-90deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3)}#board-container{overflow-x:initial!important}.board-task-list{min-height:0!important}.task-board{page-break-inside:avoid}.menu-inline,.project-header,.page-header,.menus-container,.sidebar,.alert,.alert-info,.dropdown>ul{display:none} \ No newline at end of file diff --git a/assets/css/vendor.min.css b/assets/css/vendor.min.css new file mode 100644 index 0000000..db558ff --- /dev/null +++ b/assets/css/vendor.min.css @@ -0,0 +1,16 @@ +/*! jQuery UI - v1.13.2 - 2022-07-14 +* http://jqueryui.com +* Includes: core.css, accordion.css, autocomplete.css, menu.css, button.css, controlgroup.css, checkboxradio.css, datepicker.css, dialog.css, draggable.css, resizable.css, progressbar.css, selectable.css, selectmenu.css, slider.css, sortable.css, spinner.css, tabs.css, tooltip.css, theme.css +* To view and modify this theme, visit http://jqueryui.com/themeroller/?bgShadowXPos=&bgOverlayXPos=&bgErrorXPos=&bgHighlightXPos=&bgContentXPos=&bgHeaderXPos=&bgActiveXPos=&bgHoverXPos=&bgDefaultXPos=&bgShadowYPos=&bgOverlayYPos=&bgErrorYPos=&bgHighlightYPos=&bgContentYPos=&bgHeaderYPos=&bgActiveYPos=&bgHoverYPos=&bgDefaultYPos=&bgShadowRepeat=&bgOverlayRepeat=&bgErrorRepeat=&bgHighlightRepeat=&bgContentRepeat=&bgHeaderRepeat=&bgActiveRepeat=&bgHoverRepeat=&bgDefaultRepeat=&iconsHover=url(%22images%2Fui-icons_555555_256x240.png%22)&iconsHighlight=url(%22images%2Fui-icons_777620_256x240.png%22)&iconsHeader=url(%22images%2Fui-icons_444444_256x240.png%22)&iconsError=url(%22images%2Fui-icons_cc0000_256x240.png%22)&iconsDefault=url(%22images%2Fui-icons_777777_256x240.png%22)&iconsContent=url(%22images%2Fui-icons_444444_256x240.png%22)&iconsActive=url(%22images%2Fui-icons_ffffff_256x240.png%22)&bgImgUrlShadow=&bgImgUrlOverlay=&bgImgUrlHover=&bgImgUrlHighlight=&bgImgUrlHeader=&bgImgUrlError=&bgImgUrlDefault=&bgImgUrlContent=&bgImgUrlActive=&opacityFilterShadow=Alpha(Opacity%3D30)&opacityFilterOverlay=Alpha(Opacity%3D30)&opacityShadowPerc=30&opacityOverlayPerc=30&iconColorHover=%23555555&iconColorHighlight=%23777620&iconColorHeader=%23444444&iconColorError=%23cc0000&iconColorDefault=%23777777&iconColorContent=%23444444&iconColorActive=%23ffffff&bgImgOpacityShadow=0&bgImgOpacityOverlay=0&bgImgOpacityError=95&bgImgOpacityHighlight=55&bgImgOpacityContent=75&bgImgOpacityHeader=75&bgImgOpacityActive=65&bgImgOpacityHover=75&bgImgOpacityDefault=75&bgTextureShadow=flat&bgTextureOverlay=flat&bgTextureError=flat&bgTextureHighlight=flat&bgTextureContent=flat&bgTextureHeader=flat&bgTextureActive=flat&bgTextureHover=flat&bgTextureDefault=flat&cornerRadius=3px&fwDefault=normal&ffDefault=Arial%2CHelvetica%2Csans-serif&fsDefault=1em&cornerRadiusShadow=8px&thicknessShadow=5px&offsetLeftShadow=0px&offsetTopShadow=0px&opacityShadow=.3&bgColorShadow=%23666666&opacityOverlay=.3&bgColorOverlay=%23aaaaaa&fcError=%235f3f3f&borderColorError=%23f1a899&bgColorError=%23fddfdf&fcHighlight=%23777620&borderColorHighlight=%23dad55e&bgColorHighlight=%23fffa90&fcContent=%23333333&borderColorContent=%23dddddd&bgColorContent=%23ffffff&fcHeader=%23333333&borderColorHeader=%23dddddd&bgColorHeader=%23e9e9e9&fcActive=%23ffffff&borderColorActive=%23003eff&bgColorActive=%23007fff&fcHover=%232b2b2b&borderColorHover=%23cccccc&bgColorHover=%23ededed&fcDefault=%23454545&borderColorDefault=%23c5c5c5&bgColorDefault=%23f6f6f6 +* Copyright jQuery Foundation and other contributors; Licensed MIT */ + +.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;-ms-filter:"alpha(opacity=0)"}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important;pointer-events:none}.ui-icon{display:inline-block;vertical-align:middle;margin-top:-.25em;position:relative;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-icon-block{left:50%;margin-left:-8px;display:block}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-accordion .ui-accordion-header{display:block;cursor:pointer;position:relative;margin:2px 0 0 0;padding:.5em .5em .5em .7em;font-size:100%}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;overflow:auto}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-menu{list-style:none;padding:0;margin:0;display:block;outline:0}.ui-menu .ui-menu{position:absolute}.ui-menu .ui-menu-item{margin:0;cursor:pointer;list-style-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")}.ui-menu .ui-menu-item-wrapper{position:relative;padding:3px 1em 3px .4em}.ui-menu .ui-menu-divider{margin:5px 0;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-state-focus,.ui-menu .ui-state-active{margin:-1px}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item-wrapper{padding-left:2em}.ui-menu .ui-icon{position:absolute;top:0;bottom:0;left:.2em;margin:auto 0}.ui-menu .ui-menu-icon{left:auto;right:0}.ui-button{padding:.4em 1em;display:inline-block;position:relative;line-height:normal;margin-right:.1em;cursor:pointer;vertical-align:middle;text-align:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;overflow:visible}.ui-button,.ui-button:link,.ui-button:visited,.ui-button:hover,.ui-button:active{text-decoration:none}.ui-button-icon-only{width:2em;box-sizing:border-box;text-indent:-9999px;white-space:nowrap}input.ui-button.ui-button-icon-only{text-indent:0}.ui-button-icon-only .ui-icon{position:absolute;top:50%;left:50%;margin-top:-8px;margin-left:-8px}.ui-button.ui-icon-notext .ui-icon{padding:0;width:2.1em;height:2.1em;text-indent:-9999px;white-space:nowrap}input.ui-button.ui-icon-notext .ui-icon{width:auto;height:auto;text-indent:0;white-space:normal;padding:.4em 1em}input.ui-button::-moz-focus-inner,button.ui-button::-moz-focus-inner{border:0;padding:0}.ui-controlgroup{vertical-align:middle;display:inline-block}.ui-controlgroup > .ui-controlgroup-item{float:left;margin-left:0;margin-right:0}.ui-controlgroup > .ui-controlgroup-item:focus,.ui-controlgroup > .ui-controlgroup-item.ui-visual-focus{z-index:9999}.ui-controlgroup-vertical > .ui-controlgroup-item{display:block;float:none;width:100%;margin-top:0;margin-bottom:0;text-align:left}.ui-controlgroup-vertical .ui-controlgroup-item{box-sizing:border-box}.ui-controlgroup .ui-controlgroup-label{padding:.4em 1em}.ui-controlgroup .ui-controlgroup-label span{font-size:80%}.ui-controlgroup-horizontal .ui-controlgroup-label + .ui-controlgroup-item{border-left:none}.ui-controlgroup-vertical .ui-controlgroup-label + .ui-controlgroup-item{border-top:none}.ui-controlgroup-horizontal .ui-controlgroup-label.ui-widget-content{border-right:none}.ui-controlgroup-vertical .ui-controlgroup-label.ui-widget-content{border-bottom:none}.ui-controlgroup-vertical .ui-spinner-input{width:75%;width:calc( 100% - 2.4em )}.ui-controlgroup-vertical .ui-spinner .ui-spinner-up{border-top-style:solid}.ui-checkboxradio-label .ui-icon-background{box-shadow:inset 1px 1px 1px #ccc;border-radius:.12em;border:none}.ui-checkboxradio-radio-label .ui-icon-background{width:16px;height:16px;border-radius:1em;overflow:visible;border:none}.ui-checkboxradio-radio-label.ui-checkboxradio-checked .ui-icon,.ui-checkboxradio-radio-label.ui-checkboxradio-checked:hover .ui-icon{background-image:none;width:8px;height:8px;border-width:4px;border-style:solid}.ui-checkboxradio-disabled{pointer-events:none}.ui-datepicker{width:17em;padding:.2em .2em 0;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-prev-hover{left:1px}.ui-datepicker .ui-datepicker-next-hover{right:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:45%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%;font-size:0}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-datepicker .ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat;left:.5em;top:.3em}.ui-dialog{position:absolute;top:0;left:0;padding:.2em;outline:0}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 0;white-space:nowrap;width:90%;overflow:hidden;text-overflow:ellipsis}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:20px;margin:-10px 0 0 0;padding:1px;height:20px}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:none;overflow:auto}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin-top:.5em;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-n{height:2px;top:0}.ui-dialog .ui-resizable-e{width:2px;right:0}.ui-dialog .ui-resizable-s{height:2px;bottom:0}.ui-dialog .ui-resizable-w{width:2px;left:0}.ui-dialog .ui-resizable-se,.ui-dialog .ui-resizable-sw,.ui-dialog .ui-resizable-ne,.ui-dialog .ui-resizable-nw{width:7px;height:7px}.ui-dialog .ui-resizable-se{right:0;bottom:0}.ui-dialog .ui-resizable-sw{left:0;bottom:0}.ui-dialog .ui-resizable-ne{right:0;top:0}.ui-dialog .ui-resizable-nw{left:0;top:0}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-draggable-handle{-ms-touch-action:none;touch-action:none}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block;-ms-touch-action:none;touch-action:none}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-progressbar{height:2em;text-align:left;overflow:hidden}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}.ui-progressbar .ui-progressbar-overlay{background:url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw==");height:100%;-ms-filter:"alpha(opacity=25)";opacity:0.25}.ui-progressbar-indeterminate .ui-progressbar-value{background-image:none}.ui-selectable{-ms-touch-action:none;touch-action:none}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-selectmenu-menu{padding:0;margin:0;position:absolute;top:0;left:0;display:none}.ui-selectmenu-menu .ui-menu{overflow:auto;overflow-x:hidden;padding-bottom:1px}.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup{font-size:1em;font-weight:bold;line-height:1.5;padding:2px 0.4em;margin:0.5em 0 0 0;height:auto;border:0}.ui-selectmenu-open{display:block}.ui-selectmenu-text{display:block;margin-right:20px;overflow:hidden;text-overflow:ellipsis}.ui-selectmenu-button.ui-button{text-align:left;white-space:nowrap;width:14em}.ui-selectmenu-icon.ui-icon{float:right;margin-top:0}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:pointer;-ms-touch-action:none;touch-action:none}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider.ui-state-disabled .ui-slider-handle,.ui-slider.ui-state-disabled .ui-slider-range{filter:inherit}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-sortable-handle{-ms-touch-action:none;touch-action:none}.ui-spinner{position:relative;display:inline-block;overflow:hidden;padding:0;vertical-align:middle}.ui-spinner-input{border:none;background:none;color:inherit;padding:.222em 0;margin:.2em 0;vertical-align:middle;margin-left:.4em;margin-right:2em}.ui-spinner-button{width:1.6em;height:50%;font-size:.5em;padding:0;margin:0;text-align:center;position:absolute;cursor:default;display:block;overflow:hidden;right:0}.ui-spinner a.ui-spinner-button{border-top-style:none;border-bottom-style:none;border-right-style:none}.ui-spinner-up{top:0}.ui-spinner-down{bottom:0}.ui-tabs{position:relative;padding:.2em}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:0;margin:1px .2em 0 0;border-bottom-width:0;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav .ui-tabs-anchor{float:left;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-active{margin-bottom:-1px;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor{cursor:text}.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:none}.ui-tooltip{padding:8px;position:absolute;z-index:9999;max-width:300px}body .ui-tooltip{border-width:2px}.ui-widget{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget.ui-widget-content{border:1px solid #c5c5c5}.ui-widget-content{border:1px solid #ddd;background:#fff;color:#333}.ui-widget-content a{color:#333}.ui-widget-header{border:1px solid #ddd;background:#e9e9e9;color:#333;font-weight:bold}.ui-widget-header a{color:#333}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default,.ui-button,html .ui-button.ui-state-disabled:hover,html .ui-button.ui-state-disabled:active{border:1px solid #c5c5c5;background:#f6f6f6;font-weight:normal;color:#454545}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited,a.ui-button,a:link.ui-button,a:visited.ui-button,.ui-button{color:#454545;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus,.ui-button:hover,.ui-button:focus{border:1px solid #ccc;background:#ededed;font-weight:normal;color:#2b2b2b}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited,a.ui-button:hover,a.ui-button:focus{color:#2b2b2b;text-decoration:none}.ui-visual-focus{box-shadow:0 0 3px 1px rgb(94,158,214)}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active,a.ui-button:active,.ui-button:active,.ui-button.ui-state-active:hover{border:1px solid #003eff;background:#007fff;font-weight:normal;color:#fff}.ui-icon-background,.ui-state-active .ui-icon-background{border:#003eff;background-color:#fff}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#fff;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #dad55e;background:#fffa90;color:#777620}.ui-state-checked{border:1px solid #dad55e;background:#fffa90}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#777620}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #f1a899;background:#fddfdf;color:#5f3f3f}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#5f3f3f}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#5f3f3f}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;-ms-filter:"alpha(opacity=70)";font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;-ms-filter:"alpha(opacity=35)";background-image:none}.ui-state-disabled .ui-icon{-ms-filter:"alpha(opacity=35)"}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_444444_256x240.png")}.ui-widget-header .ui-icon{background-image:url("images/ui-icons_444444_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon,.ui-button:hover .ui-icon,.ui-button:focus .ui-icon{background-image:url("images/ui-icons_555555_256x240.png")}.ui-state-active .ui-icon,.ui-button:active .ui-icon{background-image:url("images/ui-icons_ffffff_256x240.png")}.ui-state-highlight .ui-icon,.ui-button .ui-state-highlight.ui-icon{background-image:url("images/ui-icons_777620_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("images/ui-icons_cc0000_256x240.png")}.ui-button .ui-icon{background-image:url("images/ui-icons_777777_256x240.png")}.ui-icon-blank.ui-icon-blank.ui-icon-blank{background-image:none}.ui-icon-caret-1-n{background-position:0 0}.ui-icon-caret-1-ne{background-position:-16px 0}.ui-icon-caret-1-e{background-position:-32px 0}.ui-icon-caret-1-se{background-position:-48px 0}.ui-icon-caret-1-s{background-position:-65px 0}.ui-icon-caret-1-sw{background-position:-80px 0}.ui-icon-caret-1-w{background-position:-96px 0}.ui-icon-caret-1-nw{background-position:-112px 0}.ui-icon-caret-2-n-s{background-position:-128px 0}.ui-icon-caret-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-65px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-65px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:1px -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:3px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:3px}.ui-widget-overlay{background:#aaa;opacity:.003;-ms-filter:Alpha(Opacity=.3)}.ui-widget-shadow{-webkit-box-shadow:0 0 5px #666;box-shadow:0 0 5px #666}/*! jQuery Timepicker Addon - v1.6.3 - 2016-04-20 +* http://trentrichardson.com/examples/timepicker +* Copyright (c) 2016 Trent Richardson; Licensed MIT */ + +.ui-timepicker-div .ui-widget-header{margin-bottom:8px}.ui-timepicker-div dl{text-align:left}.ui-timepicker-div dl dt{float:left;clear:left;padding:0 0 0 5px}.ui-timepicker-div dl dd{margin:0 10px 10px 40%}.ui-timepicker-div td{font-size:90%}.ui-tpicker-grid-label{background:0 0;border:0;margin:0;padding:0}.ui-timepicker-div .ui_tpicker_unit_hide{display:none}.ui-timepicker-div .ui_tpicker_time .ui_tpicker_time_input{background:0 0;color:inherit;border:0;outline:0;border-bottom:solid 1px #555;width:95%}.ui-timepicker-div .ui_tpicker_time .ui_tpicker_time_input:focus{border-bottom-color:#aaa}.ui-timepicker-rtl{direction:rtl}.ui-timepicker-rtl dl{text-align:right;padding:0 5px 0 0}.ui-timepicker-rtl dl dt{float:right;clear:right}.ui-timepicker-rtl dl dd{margin:0 40% 10px 10px}.ui-timepicker-div.ui-timepicker-oneLine{padding-right:2px}.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_time,.ui-timepicker-div.ui-timepicker-oneLine dt{display:none}.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_time_label{display:block;padding-top:2px}.ui-timepicker-div.ui-timepicker-oneLine dl{text-align:right}.ui-timepicker-div.ui-timepicker-oneLine dl dd,.ui-timepicker-div.ui-timepicker-oneLine dl dd>div{display:inline-block;margin:0}.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_minute:before,.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_second:before{content:':';display:inline-block}.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_millisec:before,.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_microsec:before{content:'.';display:inline-block}.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_unit_hide,.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_unit_hide:before{display:none}.select2-container{box-sizing:border-box;display:inline-block;margin:0;position:relative;vertical-align:middle}.select2-container .select2-selection--single{box-sizing:border-box;cursor:pointer;display:block;height:28px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{display:block;padding-left:8px;padding-right:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-selection--single .select2-selection__clear{position:relative}.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:8px;padding-left:20px}.select2-container .select2-selection--multiple{box-sizing:border-box;cursor:pointer;display:block;min-height:32px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--multiple .select2-selection__rendered{display:inline-block;overflow:hidden;padding-left:8px;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-search--inline{float:left}.select2-container .select2-search--inline .select2-search__field{box-sizing:border-box;border:none;font-size:100%;margin-top:5px;padding:0}.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-dropdown{background-color:white;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:block;position:absolute;left:-100000px;width:100%;z-index:1051}.select2-results{display:block}.select2-results__options{list-style:none;margin:0;padding:0}.select2-results__option{padding:6px;user-select:none;-webkit-user-select:none}.select2-results__option[aria-selected]{cursor:pointer}.select2-container--open .select2-dropdown{left:0}.select2-container--open .select2-dropdown--above{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--open .select2-dropdown--below{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-search--dropdown{display:block;padding:4px}.select2-search--dropdown .select2-search__field{padding:4px;width:100%;box-sizing:border-box}.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-search--dropdown.select2-search--hide{display:none}.select2-close-mask{border:0;margin:0;padding:0;display:block;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:99;background-color:#fff;filter:alpha(opacity=0)}.select2-hidden-accessible{border:0 !important;clip:rect(0 0 0 0) !important;height:1px !important;margin:-1px !important;overflow:hidden !important;padding:0 !important;position:absolute !important;width:1px !important}.select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #aaa;border-radius:4px}.select2-container--default .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--default .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold}.select2-container--default .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--default .select2-selection--single .select2-selection__arrow{height:26px;position:absolute;top:1px;right:1px;width:20px}.select2-container--default .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow{left:1px;right:auto}.select2-container--default.select2-container--disabled .select2-selection--single{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear{display:none}.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--default .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text}.select2-container--default .select2-selection--multiple .select2-selection__rendered{box-sizing:border-box;list-style:none;margin:0;padding:0 5px;width:100%}.select2-container--default .select2-selection--multiple .select2-selection__placeholder{color:#999;margin-top:5px;float:left}.select2-container--default .select2-selection--multiple .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-top:5px;margin-right:10px}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{color:#999;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#333}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline{float:right}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--default.select2-container--focus .select2-selection--multiple{border:solid black 1px;outline:0}.select2-container--default.select2-container--disabled .select2-selection--multiple{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection__choice__remove{display:none}.select2-container--default.select2-container--open.select2-container--above .select2-selection--single,.select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple{border-top-left-radius:0;border-top-right-radius:0}.select2-container--default.select2-container--open.select2-container--below .select2-selection--single,.select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--default .select2-search--dropdown .select2-search__field{border:1px solid #aaa}.select2-container--default .select2-search--inline .select2-search__field{background:transparent;border:none;outline:0;box-shadow:none;-webkit-appearance:textfield}.select2-container--default .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--default .select2-results__option[role=group]{padding:0}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd}.select2-container--default .select2-results__option .select2-results__option{padding-left:1em}.select2-container--default .select2-results__option .select2-results__option .select2-results__group{padding-left:0}.select2-container--default .select2-results__option .select2-results__option .select2-results__option{margin-left:-1em;padding-left:2em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-2em;padding-left:3em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-3em;padding-left:4em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-4em;padding-left:5em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-5em;padding-left:6em}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#5897fb;color:white}.select2-container--default .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic .select2-selection--single{background-color:#f7f7f7;border:1px solid #aaa;border-radius:4px;outline:0;background-image:-webkit-linear-gradient(top, #fff 50%, #eee 100%);background-image:-o-linear-gradient(top, #fff 50%, #eee 100%);background-image:linear-gradient(to bottom, #fff 50%, #eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic .select2-selection--single:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--classic .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-right:10px}.select2-container--classic .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--classic .select2-selection--single .select2-selection__arrow{background-color:#ddd;border:none;border-left:1px solid #aaa;border-top-right-radius:4px;border-bottom-right-radius:4px;height:26px;position:absolute;top:1px;right:1px;width:20px;background-image:-webkit-linear-gradient(top, #eee 50%, #ccc 100%);background-image:-o-linear-gradient(top, #eee 50%, #ccc 100%);background-image:linear-gradient(to bottom, #eee 50%, #ccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0)}.select2-container--classic .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow{border:none;border-right:1px solid #aaa;border-radius:0;border-top-left-radius:4px;border-bottom-left-radius:4px;left:1px;right:auto}.select2-container--classic.select2-container--open .select2-selection--single{border:1px solid #5897fb}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow{background:transparent;border:none}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single{border-top:none;border-top-left-radius:0;border-top-right-radius:0;background-image:-webkit-linear-gradient(top, #fff 0%, #eee 50%);background-image:-o-linear-gradient(top, #fff 0%, #eee 50%);background-image:linear-gradient(to bottom, #fff 0%, #eee 50%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-linear-gradient(top, #eee 50%, #fff 100%);background-image:-o-linear-gradient(top, #eee 50%, #fff 100%);background-image:linear-gradient(to bottom, #eee 50%, #fff 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0)}.select2-container--classic .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text;outline:0}.select2-container--classic .select2-selection--multiple:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--multiple .select2-selection__rendered{list-style:none;margin:0;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__clear{display:none}.select2-container--classic .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove{color:#888;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover{color:#555}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{float:right}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--classic.select2-container--open .select2-selection--multiple{border:1px solid #5897fb}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--classic .select2-search--dropdown .select2-search__field{border:1px solid #aaa;outline:0}.select2-container--classic .select2-search--inline .select2-search__field{outline:0;box-shadow:none}.select2-container--classic .select2-dropdown{background-color:#fff;border:1px solid transparent}.select2-container--classic .select2-dropdown--above{border-bottom:none}.select2-container--classic .select2-dropdown--below{border-top:none}.select2-container--classic .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--classic .select2-results__option[role=group]{padding:0}.select2-container--classic .select2-results__option[aria-disabled=true]{color:grey}.select2-container--classic .select2-results__option--highlighted[aria-selected]{background-color:#3875d7;color:#fff}.select2-container--classic .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic.select2-container--open .select2-dropdown{border-color:#5897fb} +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} +.c3 svg{font:10px sans-serif;-webkit-tap-highlight-color:transparent}.c3 line,.c3 path{fill:none;stroke:#000}.c3 text{-webkit-user-select:none;-moz-user-select:none;user-select:none}.c3-bars path,.c3-event-rect,.c3-legend-item-tile,.c3-xgrid-focus,.c3-ygrid{shape-rendering:crispEdges}.c3-chart-arc path{stroke:#fff}.c3-chart-arc rect{stroke:#fff;stroke-width:1}.c3-chart-arc text{fill:#fff;font-size:13px}.c3-grid line{stroke:#aaa}.c3-grid text{fill:#aaa}.c3-xgrid,.c3-ygrid{stroke-dasharray:3 3}.c3-text.c3-empty{fill:grey;font-size:2em}.c3-line{stroke-width:1px}.c3-circle._expanded_{stroke-width:1px;stroke:#fff}.c3-selected-circle{fill:#fff;stroke-width:2px}.c3-bar{stroke-width:0}.c3-bar._expanded_{fill-opacity:1;fill-opacity:.75}.c3-target.c3-focused{opacity:1}.c3-target.c3-focused path.c3-line,.c3-target.c3-focused path.c3-step{stroke-width:2px}.c3-target.c3-defocused{opacity:.3!important}.c3-region{fill:#4682b4;fill-opacity:.1}.c3-brush .extent{fill-opacity:.1}.c3-legend-item{font-size:12px}.c3-legend-item-hidden{opacity:.15}.c3-legend-background{opacity:.75;fill:#fff;stroke:#d3d3d3;stroke-width:1}.c3-title{font:14px sans-serif}.c3-tooltip-container{z-index:10}.c3-tooltip{border-collapse:collapse;border-spacing:0;background-color:#fff;empty-cells:show;-webkit-box-shadow:7px 7px 12px -9px #777;-moz-box-shadow:7px 7px 12px -9px #777;box-shadow:7px 7px 12px -9px #777;opacity:.9}.c3-tooltip tr{border:1px solid #ccc}.c3-tooltip th{background-color:#aaa;font-size:14px;padding:2px 5px;text-align:left;color:#fff}.c3-tooltip td{font-size:13px;padding:3px 6px;background-color:#fff;border-left:1px dotted #999}.c3-tooltip td>span{display:inline-block;width:10px;height:10px;margin-right:6px}.c3-tooltip td.value{text-align:right}.c3-area{stroke-width:0;opacity:.2}.c3-chart-arcs-title{dominant-baseline:middle;font-size:1.3em}.c3-chart-arcs .c3-chart-arcs-background{fill:#e0e0e0;stroke:#fff}.c3-chart-arcs .c3-chart-arcs-gauge-unit{fill:#000;font-size:16px}.c3-chart-arcs .c3-chart-arcs-gauge-max{fill:#777}.c3-chart-arcs .c3-chart-arcs-gauge-min{fill:#777}.c3-chart-arc .c3-gauge-value{fill:#000}.c3-chart-arc.c3-target g path{opacity:1}.c3-chart-arc.c3-target.c3-focused g path{opacity:1} \ No newline at end of file diff --git a/assets/fonts/FontAwesome.otf b/assets/fonts/FontAwesome.otf new file mode 100644 index 0000000..401ec0f Binary files /dev/null and b/assets/fonts/FontAwesome.otf differ diff --git a/assets/fonts/fontawesome-webfont.eot b/assets/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..e9f60ca Binary files /dev/null and b/assets/fonts/fontawesome-webfont.eot differ diff --git a/assets/fonts/fontawesome-webfont.svg b/assets/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000..855c845 --- /dev/null +++ b/assets/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/fonts/fontawesome-webfont.ttf b/assets/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..35acda2 Binary files /dev/null and b/assets/fonts/fontawesome-webfont.ttf differ diff --git a/assets/fonts/fontawesome-webfont.woff b/assets/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..400014a Binary files /dev/null and b/assets/fonts/fontawesome-webfont.woff differ diff --git a/assets/fonts/fontawesome-webfont.woff2 b/assets/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000..4d13fc6 Binary files /dev/null and b/assets/fonts/fontawesome-webfont.woff2 differ diff --git a/assets/img/adaptive-favicon.svg b/assets/img/adaptive-favicon.svg new file mode 100644 index 0000000..288e825 --- /dev/null +++ b/assets/img/adaptive-favicon.svg @@ -0,0 +1,13 @@ + + + + + + + diff --git a/assets/img/favicon.png b/assets/img/favicon.png new file mode 100644 index 0000000..99f5ccb Binary files /dev/null and b/assets/img/favicon.png differ diff --git a/assets/img/inkscape-optimized-icon.svg b/assets/img/inkscape-optimized-icon.svg new file mode 100644 index 0000000..4d7b92b --- /dev/null +++ b/assets/img/inkscape-optimized-icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/img/inkscape-path-icon.svg b/assets/img/inkscape-path-icon.svg new file mode 100644 index 0000000..625faea --- /dev/null +++ b/assets/img/inkscape-path-icon.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/assets/img/inkscape-text-icon.svg b/assets/img/inkscape-text-icon.svg new file mode 100644 index 0000000..7109242 --- /dev/null +++ b/assets/img/inkscape-text-icon.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + K + B + + diff --git a/assets/img/touch-icon-ipad-retina.png b/assets/img/touch-icon-ipad-retina.png new file mode 100644 index 0000000..51702e7 Binary files /dev/null and b/assets/img/touch-icon-ipad-retina.png differ diff --git a/assets/img/touch-icon-ipad.png b/assets/img/touch-icon-ipad.png new file mode 100644 index 0000000..a92969c Binary files /dev/null and b/assets/img/touch-icon-ipad.png differ diff --git a/assets/img/touch-icon-iphone-retina.png b/assets/img/touch-icon-iphone-retina.png new file mode 100644 index 0000000..8c35490 Binary files /dev/null and b/assets/img/touch-icon-iphone-retina.png differ diff --git a/assets/img/touch-icon-iphone.png b/assets/img/touch-icon-iphone.png new file mode 100644 index 0000000..5d8d555 Binary files /dev/null and b/assets/img/touch-icon-iphone.png differ diff --git a/assets/js/app.min.js b/assets/js/app.min.js new file mode 100644 index 0000000..3cfc0b0 --- /dev/null +++ b/assets/js/app.min.js @@ -0,0 +1,333 @@ +(function(){var properties=['direction','boxSizing','width','height','overflowX','overflowY','borderTopWidth','borderRightWidth','borderBottomWidth','borderLeftWidth','borderStyle','paddingTop','paddingRight','paddingBottom','paddingLeft','fontStyle','fontVariant','fontWeight','fontStretch','fontSize','fontSizeAdjust','lineHeight','fontFamily','textAlign','textTransform','textIndent','textDecoration','letterSpacing','wordSpacing','tabSize','MozTabSize'];var isBrowser=(typeof window!=='undefined');var isFirefox=(isBrowser&&window.mozInnerScreenX!=null);function getCaretCoordinates(element,position,options){if(!isBrowser){throw new Error('textarea-caret-position#getCaretCoordinates should only be called in a browser')} +var debug=options&&options.debug||!1;if(debug){var el=document.querySelector('#input-textarea-caret-position-mirror-div');if(el)el.parentNode.removeChild(el)} +var div=document.createElement('div');div.id='input-textarea-caret-position-mirror-div';document.body.appendChild(div);var style=div.style;var computed=window.getComputedStyle?window.getComputedStyle(element):element.currentStyle;var isInput=element.nodeName==='INPUT';style.whiteSpace='pre-wrap';if(!isInput) +style.wordWrap='break-word';style.position='absolute';if(!debug) +style.visibility='hidden';properties.forEach(function(prop){if(isInput&&prop==='lineHeight'){style.lineHeight=computed.height}else{style[prop]=computed[prop]}});if(isFirefox){if(element.scrollHeight>parseInt(computed.height)) +style.overflowY='scroll'}else{style.overflow='hidden'} +div.textContent=element.value.substring(0,position);if(isInput) +div.textContent=div.textContent.replace(/\s/g,'\u00a0');var span=document.createElement('span');span.textContent=element.value.substring(position)||'.';div.appendChild(span);var coordinates={top:span.offsetTop+parseInt(computed.borderTopWidth),left:span.offsetLeft+parseInt(computed.borderLeftWidth),height:parseInt(computed.lineHeight)};if(debug){span.style.backgroundColor='#aaa'}else{document.body.removeChild(div)} +return coordinates} +if(typeof module!='undefined'&&typeof module.exports!='undefined'){module.exports=getCaretCoordinates}else if(isBrowser){window.getCaretCoordinates=getCaretCoordinates}}());(function(){var keyboardeventKeyPolyfill={polyfill:polyfill,keys:{3:'Cancel',6:'Help',8:'Backspace',9:'Tab',12:'Clear',13:'Enter',16:'Shift',17:'Control',18:'Alt',19:'Pause',20:'CapsLock',27:'Escape',28:'Convert',29:'NonConvert',30:'Accept',31:'ModeChange',32:' ',33:'PageUp',34:'PageDown',35:'End',36:'Home',37:'ArrowLeft',38:'ArrowUp',39:'ArrowRight',40:'ArrowDown',41:'Select',42:'Print',43:'Execute',44:'PrintScreen',45:'Insert',46:'Delete',48:['0',')'],49:['1','!'],50:['2','@'],51:['3','#'],52:['4','$'],53:['5','%'],54:['6','^'],55:['7','&'],56:['8','*'],57:['9','('],91:'OS',93:'ContextMenu',144:'NumLock',145:'ScrollLock',181:'VolumeMute',182:'VolumeDown',183:'VolumeUp',186:[';',':'],187:['=','+'],188:[',','<'],189:['-','_'],190:['.','>'],191:['/','?'],192:['`','~'],219:['[','{'],220:['\\','|'],221:[']','}'],222:["'",'"'],224:'Meta',225:'AltGraph',246:'Attn',247:'CrSel',248:'ExSel',249:'EraseEof',250:'Play',251:'ZoomOut'}};var i;for(i=1;i<25;i++){keyboardeventKeyPolyfill.keys[111+i]='F'+i} +var letter='';for(i=65;i<91;i++){letter=String.fromCharCode(i);keyboardeventKeyPolyfill.keys[i]=[letter.toLowerCase(),letter.toUpperCase()]} +function polyfill(){if(!('KeyboardEvent' in window)||'key' in KeyboardEvent.prototype){return!1} +var proto={get:function(x){var key=keyboardeventKeyPolyfill.keys[this.which||this.keyCode];if(Array.isArray(key)){key=key[+this.shiftKey]} +return key}};Object.defineProperty(KeyboardEvent.prototype,'key',proto);return proto} +keyboardeventKeyPolyfill.polyfill()})();if(!Element.prototype.matches){Element.prototype.matches=Element.prototype.matchesSelector||Element.prototype.mozMatchesSelector||Element.prototype.msMatchesSelector||Element.prototype.oMatchesSelector||Element.prototype.webkitMatchesSelector||function(s){var matches=(this.document||this.ownerDocument).querySelectorAll(s),i=matches.length;while(--i>=0&&matches.item(i)!==this){} +return i>-1}} +var KB={components:{},utils:{},html:{},http:{},listeners:{clicks:{},changes:{},keys:[],internals:{}}};KB.on=function(eventType,callback){if(!this.listeners.internals.hasOwnProperty(eventType)){this.listeners.internals[eventType]=[]} +this.listeners.internals[eventType].push(callback)};KB.trigger=function(eventType,eventData){if(this.listeners.internals.hasOwnProperty(eventType)){for(var i=0;i0){var reset=!0;for(var i=0;i-1){window.location=redirect.split('#')[0]}else if(redirect){window.location=redirect}else if(location){window.location=location}else if(request.getResponseHeader('Content-Type')==='application/json'){try{return JSON.parse(request.responseText)}catch(e){}} +return request.responseText} +this.execute=function(){var request=new XMLHttpRequest();request.open(method,url,!0);request.setRequestHeader('X-Requested-With','XMLHttpRequest');for(var header in headers){if(headers.hasOwnProperty(header)){request.setRequestHeader(header,headers[header])}} +request.onerror=function(){errorCallback()};request.onreadystatechange=function(){if(request.readyState===XMLHttpRequest.DONE){var response=parseResponse(request);switch(request.status){case 200:successCallback(response);return;case 401:authErrorCallback(response);errorCallback(response);break;case 0:netErrorCallback(response);errorCallback(response);break;default:errorCallback(response);break}}};request.send(body);return this};this.success=function(callback){successCallback=callback;return this};this.authError=function(callback){authErrorCallback=callback;return this};this.netError=function(callback){netErrorCallback=callback;return this};this.error=function(callback){errorCallback=callback;return this}};KB.http.get=function(url){return(new KB.http.request('GET',url)).execute()};KB.http.postJson=function(url,body){var headers={'Content-Type':'application/json','Accept':'application/json'};return(new KB.http.request('POST',url,headers,JSON.stringify(body))).execute()};KB.http.postForm=function(url,formElement){var formData=new FormData(formElement);return(new KB.http.request('POST',url,{},formData)).execute()};KB.http.uploadFile=function(url,file,csrf,onProgress,onComplete,onError,onServerError,onRequestTooLarge){var fd=new FormData();fd.append('files[]',file);fd.append('csrf_token',csrf);var xhr=new XMLHttpRequest();xhr.upload.addEventListener('progress',onProgress);xhr.upload.addEventListener('error',onError);xhr.open('POST',url,!0);xhr.setRequestHeader('X-Requested-With','XMLHttpRequest');xhr.onreadystatechange=function(){if(xhr.readyState===XMLHttpRequest.DONE){if(xhr.status===200){onComplete()}else if(xhr.status===413){if(typeof onRequestTooLarge!=='undefined'){onRequestTooLarge()}else{onError(xhr.responseText)}}else if(typeof onServerError!=='undefined'){onServerError(JSON.parse(xhr.responseText))}}};xhr.send(fd)};(function(){var isOpen=!1;var isFormDirty=!1;function onOverlayClick(e){if(e.target.matches('#modal-overlay')&&isFormDirty===!1){e.stopPropagation();e.preventDefault();destroy()}} +function onCloseButtonClick(){KB.trigger('modal.close')} +function onFormChange(){isFormDirty=!0} +function onFormSubmit(){KB.trigger('modal.loading');submitForm()} +function getForm(){return document.querySelector('#modal-content form:not(.js-modal-ignore-form)')} +function submitForm(){var form=getForm();if(form){var url=form.getAttribute('action');if(url){KB.http.postForm(url,form).success(function(response){KB.trigger('modal.stop');if(response){replace(response)}else{destroy()}}).error(function(response){KB.trigger('modal.stop');if(response.hasOwnProperty('message')){window.alert(response.message)}})}}} +function afterRendering(){var formElement=KB.find('#modal-content form');if(formElement){formElement.on('change',onFormChange,!1);formElement.on('submit',onFormSubmit,!1)} +var autoFocusElement=document.querySelector('#modal-content input[autofocus]');if(autoFocusElement){autoFocusElement.focus()} +KB.render();_KB.datePicker();_KB.autoComplete();_KB.tagAutoComplete();_KB.get('Task').onPopoverOpened();KB.trigger('modal.afterRender')} +function replace(html){var contentElement=KB.find('#modal-content');if(contentElement){contentElement.replace(KB.dom('div').attr('id','modal-content').html(html).build());afterRendering()}} +function create(html,width,overlayClickDestroy){var closeButtonElement=KB.dom('a').attr('href','#').attr('id','modal-close-button').html('').click(onCloseButtonClick).build();var headerElement=KB.dom('div').attr('id','modal-header').add(closeButtonElement).build();var contentElement=KB.dom('div').attr('id','modal-content').html(html).build();var boxElement=KB.dom('div').attr('id','modal-box').style('width',width).add(headerElement).add(contentElement).build();var overlayElement=KB.dom('div').attr('id','modal-overlay').add(boxElement).build();if(overlayClickDestroy){overlayElement.addEventListener('click',onOverlayClick,!1)} +document.body.appendChild(overlayElement);afterRendering()} +function destroy(){isOpen=!1;isFormDirty=!1;var overlayElement=KB.find('#modal-overlay');if(overlayElement){KB.trigger('modal.beforeDestroy');overlayElement.remove()}} +function getWidth(size){var viewport=KB.utils.getViewportSize();if(viewport.width<700){return'99%'} +switch(size){case 'large':return viewport.width<1350?'98%':'1350px';case 'medium':return viewport.width<1024?'70%':'1024px'} +return viewport.width<800?'75%':'800px'} +KB.on('modal.close',function(){destroy()});KB.on('modal.submit',function(){submitForm()});KB.modal={open:function(url,size,overlayClickDestroy){KB.trigger('modal.open');_KB.get('Dropdown').close();destroy();if(typeof overlayClickDestroy==='undefined'){overlayClickDestroy=!0} +KB.http.get(url).success(function(response){isOpen=!0;create(response,getWidth(size),overlayClickDestroy)})},close:function(){destroy()},isOpen:function(){return isOpen},replace:function(url){KB.http.get(url).success(function(response){replace(response)})},replaceHtml:replace,getForm:getForm,submitForm:submitForm}}());KB.on('dom.ready',function(){function onMouseOver(mytarget){if(!exists()){create(mytarget)}} +function onMouseLeaveContainer(){setTimeout(destroy,500)} +function mouseLeftParent(){setTimeout(destroyIfNotOnTooltip,500)} +function mouseOnTooltip(){document.getElementById("tooltip-container").mouseOnTooltip=!0} +function destroyIfNotOnTooltip(){var div=document.getElementById("tooltip-container");if(div!=null&&!div.mouseOnTooltip)destroy()} +function create(element){var contentElement=element.querySelector("script");if(contentElement){render(element,contentElement.innerHTML);return} +var link=element.dataset.href;if(link){fetch(link,function(html){if(html){render(element,html)}})}} +function fetch(url,callback){var request=new XMLHttpRequest();request.open("GET",url,!0);request.setRequestHeader("X-Requested-With","XMLHttpRequest");request.onreadystatechange=function(){if(request.readyState===XMLHttpRequest.DONE){if(request.status===200){callback(request.responseText)}}};request.send(null)} +function render(element,html){var containerElement=document.createElement("div");containerElement.id="tooltip-container";containerElement.innerHTML=html;containerElement.addEventListener("mouseleave",onMouseLeaveContainer,!1);containerElement.addEventListener("mouseenter",mouseOnTooltip,!1);containerElement.mouseOnTooltip=!1;var elementRect=element.getBoundingClientRect();var top=elementRect.top+window.scrollY+elementRect.height;containerElement.style.top=top-20+"px";if(elementRect.left>(window.innerWidth-600)){var right=window.innerWidth-elementRect.right-window.scrollX;containerElement.style.right=right-25+"px"}else{var left=elementRect.left+window.scrollX;containerElement.style.left=left-25+"px"} +document.body.appendChild(containerElement);document.body.onclick=function(event){if(!containerElement.contains(event.target)){destroy()}}} +function destroy(){var element=document.getElementById("tooltip-container");if(element){element.parentNode.removeChild(element)}} +function exists(){return!!document.getElementById("tooltip-container")} +document.body.addEventListener('mouseover',function(e){if(e.target.classList.contains('tooltip')){onMouseOver(e.target)} +if(e.target.classList.contains('fa')&&e.target.parentNode.classList.contains('tooltip')){onMouseOver(e.target.parentNode)}});document.body.addEventListener('mouseout',function(e){if(e.target.classList.contains('tooltip')){mouseLeftParent()} +if(e.target.classList.contains('fa')&&e.target.parentNode.classList.contains('tooltip')){mouseLeftParent()}})});KB.utils.formatDuration=function(d){if(d>=86400){return Math.round(d/86400)+"d"}else if(d>=3600){return Math.round(d/3600)+"h"}else if(d>=60){return Math.round(d/60)+"m"} +return d+"s"};KB.utils.getSelectionPosition=function(element){var selectionStart,selectionEnd;if(element.value.length0){columns.push([currentValue])}}else{if(j>0){columns[j].push(currentValue);if(typeof columns[0][i]==='undefined'){columns[0].push(0)} +columns[0][i]+=currentValue}else{categories.push(outputFormat(inputFormat.parse(currentValue)))}}}} +KB.dom(containerElement).add(KB.dom('div').attr('id','chart').build());c3.generate({data:{columns:columns},axis:{x:{type:'category',categories:categories}}})}});KB.component('chart-project-cumulative-flow',function(containerElement,options){this.render=function(){var metrics=options.metrics;var columns=[];var groups=[];var categories=[];var inputFormat=d3.time.format("%Y-%m-%d");var outputFormat=d3.time.format(options.dateFormat);for(var i=0;i0){groups.push(currentValue);columns.push([currentValue])}}else{if(j>0){columns[j-1].push(currentValue)}else{categories.push(outputFormat(inputFormat.parse(currentValue)))}}}} +KB.dom(containerElement).add(KB.dom('div').attr('id','chart').build());c3.generate({data:{columns:columns.reverse(),type:'area-spline',groups:[groups],order:null},axis:{x:{type:'category',categories:categories}}})}});KB.component('chart-project-estimated-actual-column',function(containerElement,options){this.render=function(){var spent=[options.labelSpent];var estimated=[options.labelEstimated];var columns=[];for(var column in options.metrics){spent.push(options.metrics[column].hours_spent);estimated.push(options.metrics[column].hours_estimated);columns.push(options.metrics[column].title)} +KB.dom(containerElement).add(KB.dom('div').attr('id','chart').build());c3.generate({data:{columns:[spent,estimated],type:'bar'},bar:{width:{ratio:0.2}},axis:{x:{type:'category',categories:columns}},legend:{show:!0}})}});KB.component('chart-project-lead-cycle-time',function(containerElement,options){this.render=function(){var metrics=options.metrics;var cycle=[options.labelCycle];var lead=[options.labelLead];var categories=[];var types={};types[options.labelCycle]='area';types[options.labelLead]='area-spline';var colors={};colors[options.labelLead]='#afb42b';colors[options.labelCycle]='#4e342e';for(var i=0;i i.fa-pencil-square-o)');if($editButton.length===0){console.error('Could not find the edit button');return!1} +$editButton[0].click();var $previewButton=$editorContainer.find('.text-editor-toolbar a:has(> i.fa-eye)');if($previewButton.length===0){console.error('Could not find the preview button');return!1} +$previewButton[0].click();return!1});KB.component('confirm-buttons',function(containerElement,options){var isLoading=!1;function onSubmit(){isLoading=!0;KB.find('#modal-confirm-button').replace(buildButton());KB.http.get(options.url)} +function onCancel(){KB.trigger('modal.close')} +function onStop(){isLoading=!1;KB.find('#modal-confirm-button').replace(buildButton())} +function buildButton(){var button=KB.dom('button').click(onSubmit).attr('id','modal-confirm-button').attr('type','button').attr('class','btn btn-red');if(isLoading){button.disable().add(KB.dom('i').attr('class','fa fa-spinner fa-pulse').build()).text(' ')} +if(options.tabindex){button.attr('tabindex',options.tabindex)} +return button.text(options.submitLabel).build()} +this.render=function(){KB.on('modal.stop',onStop);KB.on('modal.close',function(){KB.removeListener('modal.stop',onStop)});var element=KB.dom('div').attr('class','form-actions').add(buildButton()).text(' '+options.orLabel+' ').add(KB.dom('a').attr('href','#').click(onCancel).text(options.cancelLabel).build()).build();containerElement.appendChild(element)}});KB.component('external-task-view',function(containerElement,options){this.render=function(){KB.dom(containerElement).html('
');$.ajax({cache:!1,url:options.url,success:function(data){KB.dom(containerElement).html('
'+data+'
')}})}});var files=[];KB.component('file-upload-task-create',function(containerElement,options){var inputFileElement=null;var dropzoneElement=null;var totalSize=0;var currentScreenshotSize=0;var inputElement=null;function onFileLoaded(e){createImage(e.target.result);sizeOfImage(e.target.result)} +function sizeOfImage(e){totalSize-=currentScreenshotSize;currentScreenshotSize=e.length;totalSize+=currentScreenshotSize;checkMaxSize()} +function onPaste(e){if(e.clipboardData&&e.clipboardData.items){var items=e.clipboardData.items;if(items){for(var i=0;ioptions.maxSize){isOversize=!0} +if(isOversize){KB.trigger('modal.disable');if(!message){messageElement.insertBefore(KB.dom('div').attr('id','message-container').attr('class','alert alert-error').text(options.labelOversize).build(),messageElement.firstChild)}}else{KB.trigger('modal.enable');if(message){message.remove()}}} +function onFileChange(){for(var i=0;i0){KB.dom(dropzoneElement).empty().add(buildFileListElement())}else{KB.dom(dropzoneElement).empty().add(buildInnerDropzoneElement())} +checkMaxSize()} +function buildFileInputElement(){return KB.dom('input').attr('id','file-input-element').attr('type','file').attr('name','files[]').attr('multiple',!0).on('change',onFileChange).hide().build()} +function buildInnerDropzoneElement(){var dropzoneLinkElement=KB.dom('a').attr('href','#').text(options.labelChooseFiles).click(onClickFileBrowser).build();return KB.dom('div').attr('id','file-dropzone-inner').text(options.labelDropzone+' '+options.labelOr+' ').add(dropzoneLinkElement).build()} +function buildDropzoneElement(){var dropzoneElement=KB.dom('div').attr('id','file-dropzone').add(buildInnerDropzoneElement()).build();dropzoneElement.ondragover=onDragOver;dropzoneElement.ondrop=onDrop;return dropzoneElement} +function buildFileListItem(index){var deleteElement=KB.dom('span').attr('id','file-delete-'+index).html('').on('click',function(){totalSize-=files[index].size;files.splice(index,1);KB.find('#file-item-'+index).remove();showFiles()}).build();var itemElement=KB.dom('li').attr('id','file-item-'+index).add(deleteElement).text(' '+files[index].name+' ');return itemElement.build()} +function buildFileListElement(){var fileListElement=KB.dom('ul').attr('id','file-list').build();for(var i=0;i0){KB.http.uploadFile(options.url,files[currentFileIndex],options.csrf,onProgress,onComplete,onError,onServerError,onRequestTooLarge)}} +function showFiles(){if(files.length>0){KB.trigger('modal.enable');KB.dom(dropzoneElement).empty().add(buildFileListElement())}else{KB.trigger('modal.disable');KB.dom(dropzoneElement).empty().add(buildInnerDropzoneElement())}} +function buildFileInputElement(){return KB.dom('input').attr('id','file-input-element').attr('type','file').attr('name','files[]').attr('multiple',!0).on('change',onFileChange).hide().build()} +function buildInnerDropzoneElement(){var dropzoneLinkElement=KB.dom('a').attr('href','#').text(options.labelChooseFiles).click(onClickFileBrowser).build();return KB.dom('div').attr('id','file-dropzone-inner').text(options.labelDropzone+' '+options.labelOr+' ').add(dropzoneLinkElement).build()} +function buildDropzoneElement(){var dropzoneElement=KB.dom('div').attr('id','file-dropzone').add(buildInnerDropzoneElement()).build();dropzoneElement.ondragover=onDragOver;dropzoneElement.ondrop=onDrop;dropzoneElement.ondragover=onDragOver;return dropzoneElement} +function buildFileListItem(index){var isOversize=!1;var progressElement=KB.dom('progress').attr('id','file-progress-'+index).attr('value',0).build();var percentageElement=KB.dom('span').attr('id','file-percentage-'+index).text('(0%)').build();var deleteElement=KB.dom('span').attr('id','file-delete-'+index).html('').on('click',function(){files.splice(index,1);KB.find('#file-item-'+index).remove();showFiles()}).build();var itemElement=KB.dom('li').attr('id','file-item-'+index).add(deleteElement).add(progressElement).text(' '+files[index].name+' ').add(percentageElement);if(options.maxSize>0&&files[index].size>options.maxSize){itemElement.add(KB.dom('div').addClass('file-error').text(options.labelOversize).build());isOversize=!0} +if(isOversize){KB.trigger('modal.disable')} +return itemElement.build()} +function buildFileListElement(){var fileListElement=KB.dom('ul').attr('id','file-list').build();for(var i=0;i=options.images.length){index=0} +currentImage=options.images[index];break}} +renderSlide()} +function renderPreviousSlide(){destroySlide();for(var i=0;i1){if(document.activeElement.tagName==='INPUT'||document.activeElement.tagName==='TEXTAREA'){$(document.activeElement).parents("form").submit()}}}} +KB.onKey('?',function(){if(!KB.modal.isOpen()){KB.modal.open(KB.find('body').data('keyboardShortcutUrl'),'',!0)}});KB.onKey('Escape',function(){if(!KB.exists('#suggest-menu')){KB.trigger('modal.close');_KB.get("Dropdown").close()}},!0);KB.onKey('Enter',submitForm,!0,!0);KB.onKey('Enter',submitForm,!0,!1,!0);KB.onKey('b',function(){if(!KB.modal.isOpen()){KB.trigger('board.selector.open')}});if(KB.exists('#board')){KB.onKey('c',function(){if(!KB.modal.isOpen()){_KB.get('BoardHorizontalScrolling').toggle()}});KB.onKey('s',function(){if(!KB.modal.isOpen()){_KB.get('BoardCollapsedMode').toggle()}});KB.onKey('n',function(){if(!KB.modal.isOpen()){KB.modal.open(KB.find('#board').data('taskCreationUrl'),'large',!1)}})} +if(KB.exists('#task-view')){KB.onKey('e',function(){if(!KB.modal.isOpen()){KB.modal.open(KB.find('#task-view').data('editUrl'),'large',!1)}});KB.onKey('c',function(){if(!KB.modal.isOpen()){KB.modal.open(KB.find('#task-view').data('commentUrl'),'medium',!1)}});KB.onKey('s',function(){if(!KB.modal.isOpen()){KB.modal.open(KB.find('#task-view').data('subtaskUrl'),'medium',!1)}});KB.onKey('l',function(){if(!KB.modal.isOpen()){KB.modal.open(KB.find('#task-view').data('internalLinkUrl'),'medium',!1)}})} +KB.onKey('f',function(){if(!KB.modal.isOpen()){KB.focus('#form-search')}});KB.onKey('r',function(){if(!KB.modal.isOpen()){var reset=$(".filter-reset").data("filter");var input=$("#form-search");input.val(reset);$("form.search").submit()}});KB.onKey('v+o',function(){goToLink('a.view-overview')});KB.onKey('v+b',function(){goToLink('a.view-board')});KB.onKey('v+l',function(){goToLink('a.view-listing')})};document.addEventListener("DOMContentLoaded",function(){function selectAllItems(event){event.preventDefault();var items=document.querySelectorAll("input[data-list-item=selectable]");for(var i=0;i0){showActionMenu()}else if(selectedItems.length==0){hideActionMenu()}} +function showActionMenu(){var element=document.querySelector(".list-item-actions");if(element){element.classList.remove("list-item-action-hidden")}} +function hideActionMenu(){var element=document.querySelector(".list-item-actions");if(element&&!element.classList.contains("list-item-action-hidden")){element.classList.add("list-item-action-hidden")}} +function onActionClick(event){event.preventDefault();var selectedItems=document.querySelectorAll("input[data-list-item=selectable]:checked");var taskIDs=[];for(var i=0;i0){result.index=result.index-1} +KB.dom(result.items[result.index]).addClass('active')} +function moveDown(){var result=resetSelection();if(result.indexvalue2?1:0)})}else{elements.sort(function(a,b){var value1=a['data-label'].toLowerCase();var value2=b['data-label'].toLowerCase();return value1value2?1:0)})} +return elements} +function filterItems(text,items){var filteredItems=[];var hasActiveItem=!1;for(var i=0;i-1){var item=items[i];if(typeof options.defaultValue!=='undefined'&&String(options.defaultValue)===item['data-value']){item.class+=' active';hasActiveItem=!0} +filteredItems.push(item)}} +if(!hasActiveItem&&filteredItems.length>0){filteredItems[0].class+=' active'} +return filteredItems} +function buildDropdownMenu(){var itemElements=filterItems(inputElement.value,buildItems(options.items));var componentPosition=componentElement.getBoundingClientRect();var windowPosition=document.body.scrollTop||document.documentElement.scrollTop;if(itemElements.length===0){return null} +return KB.dom('ul').attr('id','select-dropdown-menu').style('top',(windowPosition+componentPosition.bottom)+'px').style('left',componentPosition.left+'px').style('width',componentPosition.width+'px').style('maxHeight',(window.innerHeight-componentPosition.bottom-20)+'px').mouseover(onItemMouseOver).click(onItemClick).for('li',itemElements).build()} +function destroyDropdownMenu(){var menuElement=KB.find('#select-dropdown-menu');if(menuElement!==null){menuElement.remove()} +document.removeEventListener('keydown',onKeyDown,!1);document.removeEventListener('click',onDocumentClick,!1)} +function renderDropdownMenu(){var element=buildDropdownMenu();if(element!==null){document.body.appendChild(element)} +document.addEventListener('keydown',onKeyDown,!1);document.addEventListener('click',onDocumentClick,!1)} +function getPlaceholderValue(){if(options.defaultValue&&options.defaultValue in options.items){return options.items[options.defaultValue]} +if(options.placeholder){return options.placeholder} +return''} +function getAriaLabelValue(){if(options.ariaLabel){return options.ariaLabel} +return''} +this.render=function(){KB.on('select.dropdown.loading.start',onLoadingStart);KB.on('select.dropdown.loading.stop',onLoadingStop);KB.on('modal.close',function(){KB.removeListener('select.dropdown.loading.start',onLoadingStart);KB.removeListener('select.dropdown.loading.stop',onLoadingStop)});chevronIconElement=KB.dom('i').attr('class','fa fa-chevron-down select-dropdown-chevron').click(toggleDropdownMenu).build();loadingIconElement=KB.dom('span').hide().addClass('select-loading-icon').add(KB.dom('i').attr('class','fa fa-spinner fa-pulse').build()).build();inputHiddenElement=KB.dom('input').attr('type','hidden').attr('name',options.name).attr('value',options.defaultValue||'').build();inputElement=KB.dom('input').attr('type','text').attr('placeholder',getPlaceholderValue()).attr('aria-label',getAriaLabelValue()).addClass('select-dropdown-input').on('focus',toggleDropdownMenu).on('input',onInputChanged,!0).build();componentElement=KB.dom('div').addClass('select-dropdown-input-container').add(inputHiddenElement).add(inputElement).add(chevronIconElement).add(loadingIconElement).build();containerElement.appendChild(componentElement);if(options.onFocus){options.onFocus.forEach(function(eventName){KB.on(eventName,function(){inputElement.focus()})})} +window.addEventListener('scroll',onScroll,!1)}});KB.interval(60,function(){var statusUrl=KB.find('body').data('statusUrl');var loginUrl=KB.find('body').data('loginUrl');if(KB.find('.form-login')===null){KB.http.get(statusUrl).authError(function(){window.location=loginUrl})}});KB.component('submit-buttons',function(containerElement,options){var isLoading=!1;var isDisabled=options.disabled||!1;var submitLabel=options.submitLabel;var formActionElement=null;var submitButtonElement=null;function onSubmit(){isLoading=!0;replaceButton();KB.trigger('modal.submit')} +function onCancel(){KB.trigger('modal.close')} +function onStop(){isLoading=!1;replaceButton()} +function onDisable(){isLoading=!1;isDisabled=!0;replaceButton()} +function onEnable(){isLoading=!1;isDisabled=!1;replaceButton()} +function onHide(){KB.dom(formActionElement).hide()} +function onUpdateSubmitLabel(eventData){submitLabel=eventData.submitLabel;replaceButton()} +function buildButton(){var button=KB.dom('button').attr('type','submit').attr('class','btn btn-'+(options.color||'blue'));if(KB.modal.isOpen()){button.click(onSubmit)} +if(options.tabindex){button.attr('tabindex',options.tabindex)} +if(isLoading){button.disable().add(KB.dom('i').attr('class','fa fa-spinner fa-pulse').build()).text(' ')} +if(isDisabled){button.disable()} +return button.text(submitLabel).build()} +function replaceButton(){var buttonElement=buildButton();KB.dom(submitButtonElement).replace(buttonElement);submitButtonElement=buttonElement} +this.render=function(){KB.on('modal.stop',onStop);KB.on('modal.disable',onDisable);KB.on('modal.enable',onEnable);KB.on('modal.hide',onHide);KB.on('modal.submit.label',onUpdateSubmitLabel);KB.on('modal.close',function(){KB.removeListener('modal.stop',onStop);KB.removeListener('modal.disable',onDisable);KB.removeListener('modal.enable',onEnable);KB.removeListener('modal.hide',onHide);KB.removeListener('modal.submit.label',onUpdateSubmitLabel)});submitButtonElement=buildButton();var formActionElementBuilder=KB.dom('div').attr('class','form-actions').add(submitButtonElement);if(KB.modal.isOpen()){formActionElementBuilder.text(' '+options.orLabel+' ').add(KB.dom('a').attr('href','#').click(onCancel).text(options.cancelLabel).build())} +formActionElement=formActionElementBuilder.build();containerElement.appendChild(formActionElement)}});(function(){function savePosition(subtaskId,position){var url=$(".subtasks-table").data("save-position-url");$.ajax({cache:!1,url:url,contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify({"subtask_id":subtaskId,"position":position})})} +function bootstrap(){$(".draggable-row-handle").mouseenter(function(){$(this).parent().parent().addClass("draggable-item-hover")}).mouseleave(function(){$(this).parent().parent().removeClass("draggable-item-hover")});$(".subtasks-table tbody").sortable({forcePlaceholderSize:!0,handle:"td:first i",helper:function(e,ui){ui.children().each(function(){$(this).width($(this).width())});return ui},stop:function(event,ui){var subtask=ui.item;subtask.removeClass("draggable-item-selected");savePosition(subtask.data("subtask-id"),subtask.index()+1)},start:function(event,ui){ui.item.addClass("draggable-item-selected")}}).disableSelection()} +KB.on('dom.ready',bootstrap);KB.on('subtasks.reloaded',bootstrap)}());KB.on('dom.ready',function(){$(document).on('click','.js-subtask-toggle-status',function(e){var el=$(this);var url=el.attr('href');e.preventDefault();$.ajax({cache:!1,url:url,success:function(data){if(url.indexOf('fragment=table')!=-1){$('.subtasks-table').replaceWith(data)}else if(url.indexOf('fragment=rows')!=-1){$(el).closest('.task-list-subtasks').replaceWith(data)}else{$(el).closest('.subtask-title').replaceWith(data)} +KB.trigger('subtasks.reloaded')}})});$(document).on('click','.js-subtask-toggle-timer',function(e){var el=$(this);e.preventDefault();$.ajax({cache:!1,url:el.attr('href'),success:function(data){$(el).closest('.subtask-time-tracking').replaceWith(data)}})})});KB.component('suggest-menu',function(containerElement,options){function onKeyDown(e){switch(KB.utils.getKey(e)){case 'Escape':destroy();break;case 'ArrowUp':e.preventDefault();e.stopImmediatePropagation();moveUp();break;case 'ArrowDown':e.preventDefault();e.stopImmediatePropagation();moveDown();break;case 'Enter':e.preventDefault();e.stopImmediatePropagation();insertSelectedItem();break}} +function onClick(){insertSelectedItem()} +function onMouseOver(element){if(KB.dom(element).hasClass('suggest-menu-item')){KB.find('.suggest-menu-item.active').removeClass('active');KB.dom(element).addClass('active')}} +function insertSelectedItem(){containerElement.focus();var element=KB.find('.suggest-menu-item.active');var value=element.data('value');var trigger=element.data('trigger');var content=containerElement.value;var text=getLastWord(containerElement);var substitute=trigger+value+' ';var selectionPosition=KB.utils.getSelectionPosition(containerElement);var before=content.substring(0,selectionPosition.selectionStart-text.length);var after=content.substring(selectionPosition.selectionEnd);var position=before.length+substitute.length;containerElement.value=before+substitute+after;containerElement.setSelectionRange(position,position);destroy()} +function getLastWord(element){var lines=element.value.substring(0,element.selectionEnd).split("\n");var lastLine=lines[lines.length-1];var words=lastLine.split(' ');return words[words.length-1]} +function getParentElement(){var selectors=['#modal-content form','#modal-content','body'];for(var i=0;i0){result.index=result.index-1} +KB.dom(result.items[result.index]).addClass('active')} +function moveDown(){var result=resetSelection();if(result.index0){renderMenu(buildItems(trigger,items))}} +function filterItems(text,items){var filteredItems=[];if(text.length===0){return items} +for(var i=0;i0){container.add(KB.html.label(options.positionLabel,'form-position')).add(KB.dom('select').attr('id','form-position').for('option',tasks).build()).add(KB.html.radio(options.beforeLabel,'positionChoice','before')).add(KB.html.radio(options.afterLabel,'positionChoice','after'))} +return container.build()} +this.render=function(){KB.on('modal.submit',onSubmit);KB.on('modal.close',function(){KB.removeListener('modal.submit',onSubmit)});var form=KB.dom('div').add(KB.dom('div').attr('id','message-container').build()).add(KB.html.label(options.swimlaneLabel,'form-swimlanes')).add(buildSwimlaneSelect()).add(KB.html.label(options.columnLabel,'form-columns')).add(buildColumnSelect()).add(buildTasks()).build();containerElement.appendChild(form)}});KB.onClick('.js-template',function(e){var template=KB.dom(e.target).data('template');var target=KB.dom(e.target).data('templateTarget');var targetField=KB.find(target);if(targetField){targetField.build().value=template} +_KB.controllers.Dropdown.close()});KB.component('text-editor',function(containerElement,options){var textarea,viewModeElement,writeModeElement,previewElement,selectionStart,selectionEnd;this.render=function(){writeModeElement=buildWriteMode();viewModeElement=buildViewMode();containerElement.appendChild(KB.dom('div').attr('class','text-editor').add(viewModeElement).add(writeModeElement).build());if(options.autofocus){textarea.focus()}};function buildViewMode(){var toolbarElement=KB.dom('div').attr('class','text-editor-toolbar').for('a',[{href:'#',html:' '+options.labelWrite,click:function(){toggleViewMode()}}]).build();previewElement=KB.dom('div').attr('class','text-editor-preview-area markdown').build();return KB.dom('div').attr('class','text-editor-view-mode').add(toolbarElement).add(previewElement).hide().build()} +function buildWriteMode(){var toolbarElement=KB.dom('div').attr('class','text-editor-toolbar').for('a',[{href:'#',html:' '+options.labelPreview,click:function(){toggleViewMode()}},{href:'#',html:'',click:function(){insertEnclosedTag('**')}},{href:'#',html:'',click:function(){insertEnclosedTag('_')}},{href:'#',html:'',click:function(){insertEnclosedTag('~~')}},{href:'#',html:'',click:function(){insertLinkTag()}},{href:'#',html:'',click:function(){insertPrependTag('> ')}},{href:'#',html:'',click:function(){insertPrependTag('* ')}},{href:'#',html:'',click:function(){insertBlockTag('```')}}]).build();var textareaElement=KB.dom('textarea');textareaElement.attr('name',options.name);if(options.tabindex){textareaElement.attr('tabindex',options.tabindex)} +if(options.required){textareaElement.attr('required','required')} +if(options.ariaLabel){textareaElement.attr('aria-label',options.ariaLabel)} +var textWrapper=KB.dom(containerElement).find('script');textareaElement.html(textWrapper.innerHTML);if(options.placeholder){textareaElement.attr('placeholder',options.placeholder)} +textarea=textareaElement.build();if(options.suggestOptions){KB.getComponent('suggest-menu',textarea,options.suggestOptions).render()} +return KB.dom('div').attr('class','text-editor-write-mode').add(toolbarElement).add(textarea).build()} +function toggleViewMode(){$.ajax({cache:!1,type:'POST',url:options.previewUrl,data:{'text':textarea.value},success:function(data){KB.dom(previewElement).html(data)}});KB.dom(viewModeElement).toggle();KB.dom(writeModeElement).toggle()} +function getSelectedText(){return textarea.value.substring(textarea.selectionStart,textarea.selectionEnd)} +function replaceTextRange(s,start,end,substitute){return s.substring(0,start)+substitute+s.substring(end)} +function insertEnclosedTag(tag){var selectedText=getSelectedText();insertText(tag+selectedText+tag);setCursorBeforeClosingTag(tag)} +function insertBlockTag(tag){var selectedText=getSelectedText();insertText('\n'+tag+'\n'+selectedText+'\n'+tag);setCursorBeforeClosingTag(tag,2)} +function insertPrependTag(tag){var selectedText=getSelectedText();if(selectedText.indexOf('\n')===-1){insertText('\n'+tag+selectedText)}else{var lines=selectedText.split('\n');for(var i=0;i0){linkLabel=selectedText;selectionStartOffset=-1*(linkUrl.length+1);selectionEndOffset=selectionStartOffset+linkUrl.length} +insertText('['+linkLabel+']('+linkUrl+')');var selectionPosition=KB.utils.getSelectionPosition(textarea);var currentSelectionStart=selectionPosition.selectionStart;textarea.setSelectionRange(currentSelectionStart+selectionStartOffset,currentSelectionStart+selectionEndOffset)} +function insertText(replacedText){textarea.focus();var result=!1;var selectionPosition=KB.utils.getSelectionPosition(textarea);selectionStart=selectionPosition.selectionStart;selectionEnd=selectionPosition.selectionEnd;if(document.queryCommandSupported('insertText')){result=document.execCommand('insertText',!1,replacedText)} +if(!result){try{document.execCommand('ms-beginUndoUnit')}catch(error){} +textarea.value=replaceTextRange(textarea.value,selectionStart,selectionEnd,replacedText);try{document.execCommand('ms-endUndoUnit')}catch(error){}}} +function setCursorBeforeClosingTag(tag,offset){offset=offset||0;var position=selectionEnd+tag.length+offset;textarea.setSelectionRange(position,position)}});document.addEventListener('DOMContentLoaded',function(){KB.render();KB.listen();KB.keyboardShortcuts();KB.trigger('dom.ready')});var Kanboard={};Kanboard.App=function(){this.controllers={}};Kanboard.App.prototype.get=function(controller){return this.controllers[controller]};Kanboard.App.prototype.execute=function(){for(var className in Kanboard){if(className!=="App"){var controller=new Kanboard[className](this);this.controllers[className]=controller;if(typeof controller.execute==="function"){controller.execute()} +if(typeof controller.listen==="function"){controller.listen()} +if(typeof controller.focus==="function"){controller.focus()}}} +this.focus();this.datePicker();this.autoComplete();this.tagAutoComplete()};Kanboard.App.prototype.focus=function(){$(document).on('focus','.auto-select',function(){$(this).select()});$(document).on('mouseup','.auto-select',function(e){e.preventDefault()})};Kanboard.App.prototype.datePicker=function(){var bodyElement=$("body");var dateFormat=bodyElement.data("js-date-format");var timeFormat=bodyElement.data("js-time-format");var lang=$("html").attr("lang");$.datepicker.setDefaults($.datepicker.regional[lang]);$.timepicker.setDefaults($.timepicker.regional[lang]);$(".form-date").datepicker({showOtherMonths:!0,selectOtherMonths:!0,dateFormat:dateFormat,constrainInput:!1});$(".form-datetime").datetimepicker({controlType:'select',dateFormat:dateFormat,timeFormat:timeFormat,constrainInput:!1,amNames:['am','AM'],pmNames:['pm','PM']})};Kanboard.App.prototype.tagAutoComplete=function(){$(".tag-autocomplete").select2({tags:!0})};Kanboard.App.prototype.autoComplete=function(){$(".autocomplete").each(function(){var input=$(this);var field=input.data("dst-field");var extraFields=input.data("dst-extra-fields");if($('#form-'+field).val()===''){input.parent().find("button[type=submit]").attr('disabled','disabled')} +input.autocomplete({source:input.data("search-url"),minLength:1,select:function(event,ui){$("input[name="+field+"]").val(ui.item.id);if(extraFields){var fields=extraFields.split(',');for(var i=0;i ')};Kanboard.App.prototype.hideLoadingIcon=function(){$("#app-loading-icon").remove()};Kanboard.BoardCollapsedMode=function(app){this.app=app};Kanboard.BoardCollapsedMode.prototype.toggle=function(){var self=this;this.app.showLoadingIcon();$.ajax({cache:!1,url:$('.filter-display-mode:not([style="display: none;"]) a').attr('href'),success:function(data){$('.filter-display-mode').toggle();self.app.get("BoardDragAndDrop").refresh(data)}})};Kanboard.BoardColumnView=function(app){this.app=app};Kanboard.BoardColumnView.prototype.execute=function(){if(this.app.hasId("board")){this.render()}};Kanboard.BoardColumnView.prototype.listen=function(){var self=this;$(document).on("click",".board-toggle-column-view",function(){self.toggle($(this).data("column-id"))})};Kanboard.BoardColumnView.prototype.onBoardRendered=function(){this.render()};Kanboard.BoardColumnView.prototype.render=function(){var self=this;$(".board-column-header").each(function(){var columnId=$(this).data('column-id');if(localStorage.getItem("hidden_column_"+columnId)){self.hideColumn(columnId)}})};Kanboard.BoardColumnView.prototype.toggle=function(columnId){if(localStorage.getItem("hidden_column_"+columnId)){this.showColumn(columnId)}else{this.hideColumn(columnId)} +this.app.get("BoardDragAndDrop").dragAndDrop()};Kanboard.BoardColumnView.prototype.hideColumn=function(columnId){$(".board-column-"+columnId+" .board-column-expanded").hide();$(".board-column-"+columnId+" .board-column-collapsed").show();$(".board-column-header-"+columnId+" .board-column-expanded").hide();$(".board-column-header-"+columnId+" .board-column-collapsed").show();$(".board-column-header-"+columnId).each(function(){$(this).removeClass("board-column-compact");$(this).addClass("board-column-header-collapsed")});$(".board-column-"+columnId).each(function(){$(this).addClass("board-column-task-collapsed")});$(".board-column-"+columnId+" .board-rotation").each(function(){$(this).css("width",$(".board-column-"+columnId+"").height())});localStorage.setItem("hidden_column_"+columnId,1)};Kanboard.BoardColumnView.prototype.showColumn=function(columnId){$(".board-column-"+columnId+" .board-column-expanded").show();$(".board-column-"+columnId+" .board-column-collapsed").hide();$(".board-column-header-"+columnId+" .board-column-expanded").show();$(".board-column-header-"+columnId+" .board-column-collapsed").hide();$(".board-column-header-"+columnId).removeClass("board-column-header-collapsed");$(".board-column-"+columnId).removeClass("board-column-task-collapsed");if(localStorage.getItem("horizontal_scroll")==0){$(".board-column-header-"+columnId).addClass("board-column-compact")} +localStorage.removeItem("hidden_column_"+columnId)};Kanboard.BoardHorizontalScrolling=function(app){this.app=app};Kanboard.BoardHorizontalScrolling.prototype.execute=function(){if(this.app.hasId("board")){this.render()}};Kanboard.BoardHorizontalScrolling.prototype.listen=function(){var self=this;$(document).on('click',".filter-toggle-scrolling",function(e){e.preventDefault();self.toggle()})};Kanboard.BoardHorizontalScrolling.prototype.onBoardRendered=function(){this.render()};Kanboard.BoardHorizontalScrolling.prototype.toggle=function(){var scrolling=localStorage.getItem("horizontal_scroll")||1;localStorage.setItem("horizontal_scroll",scrolling==0?1:0);this.render()};Kanboard.BoardHorizontalScrolling.prototype.render=function(){if(localStorage.getItem("horizontal_scroll")==0){$(".filter-wide").show();$(".filter-compact").hide();$("#board-container").addClass("board-container-compact");$("#board th:not(.board-column-header-collapsed)").addClass("board-column-compact")}else{$(".filter-wide").hide();$(".filter-compact").show();$("#board-container").removeClass("board-container-compact");$("#board th").removeClass("board-column-compact")}};Kanboard.BoardPolling=function(app){this.app=app};Kanboard.BoardPolling.prototype.execute=function(){if(this.app.hasId("board")){var interval=parseInt($("#board").attr("data-check-interval"));if(interval>0){window.setInterval(this.check.bind(this),interval*1000)}}};Kanboard.BoardPolling.prototype.check=function(){if(KB.utils.isVisible()&&!this.app.get("BoardDragAndDrop").savingInProgress){var self=this;this.app.showLoadingIcon();$.ajax({cache:!1,url:$("#board").data("check-url"),statusCode:{200:function(data){self.app.get("BoardDragAndDrop").refresh(data)},304:function(){self.app.hideLoadingIcon()}}})}};Kanboard.BoardVerticalScrolling=function(app){this.app=app};Kanboard.BoardVerticalScrolling.prototype.execute=function(){if(this.app.hasId("board")){this.render()}};Kanboard.BoardVerticalScrolling.prototype.listen=function(){var self=this;$(document).on('click',".filter-vert-toggle-collapse",function(e){e.preventDefault();self.toggle()})};Kanboard.BoardVerticalScrolling.prototype.onBoardRendered=function(){this.render()};Kanboard.BoardVerticalScrolling.prototype.toggle=function(){var self=this;var scrolling=localStorage.getItem("vertical_scroll")||1;localStorage.setItem("vertical_scroll",scrolling==0?1:0);var task_lists=$(".board-task-list");task_lists.each(function(){$(this).css("min-height","")});this.render();self.app.get("BoardDragAndDrop").dragAndDrop()};Kanboard.BoardVerticalScrolling.prototype.render=function(){if(localStorage.getItem("vertical_scroll")==0){$(".filter-vert-expand").show();$(".filter-vert-collapse").hide();$("#board td .board-task-list").addClass("board-task-list-compact")}else{$(".filter-vert-expand").hide();$(".filter-vert-collapse").show();$("#board td .board-task-list").removeClass("board-task-list-compact")}};Kanboard.Column=function(app){this.app=app};Kanboard.Column.prototype.listen=function(){this.dragAndDrop()};Kanboard.Column.prototype.dragAndDrop=function(){var self=this;$(".draggable-row-handle").mouseenter(function(){$(this).parent().parent().addClass("draggable-item-hover")}).mouseleave(function(){$(this).parent().parent().removeClass("draggable-item-hover")});$(".columns-table tbody").sortable({forcePlaceholderSize:!0,handle:"td:first i",helper:function(e,ui){ui.children().each(function(){$(this).width($(this).width())});return ui},stop:function(event,ui){var column=ui.item;column.removeClass("draggable-item-selected");self.savePosition(column.data("column-id"),column.index()+1)},start:function(event,ui){ui.item.addClass("draggable-item-selected")}}).disableSelection()};Kanboard.Column.prototype.savePosition=function(columnId,position){var url=$(".columns-table").data("save-position-url");var self=this;this.app.showLoadingIcon();$.ajax({cache:!1,url:url,contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify({"column_id":columnId,"position":position}),complete:function(){self.app.hideLoadingIcon()}})};Kanboard.Dropdown=function(app){this.app=app};Kanboard.Dropdown.prototype.listen=function(){var self=this;$(document).on('click',function(){self.close()});$(document).on('click','.active-dropdown-menu',function(){self.close()});$(document).on('click','.dropdown-menu',function(e){e.preventDefault();e.stopImmediatePropagation();self.close();$(this).removeClass('dropdown-menu');$(this).addClass('active-dropdown-menu');var submenu=$(this).next('ul');var offset=$(this).offset();$("body").append(jQuery("
",{"id":"dropdown"}));submenu.clone().appendTo("#dropdown");var clone=$("#dropdown ul");clone.addClass('dropdown-submenu-open');var submenuHeight=clone.outerHeight();var submenuWidth=clone.outerWidth();if(offset.top+submenuHeight-$(window).scrollTop()<$(window).height()||$(window).scrollTop()+offset.top$(window).width()){var newOffset=offset.left-submenuWidth+$(this).outerWidth();if(newOffset<0){newOffset=15} +clone.css('left',newOffset)}else{clone.css('left',offset.left)} +if(document.getElementById('dropdown')!==null){KB.trigger('dropdown.afterRender')}});$(document).on('click','.dropdown-submenu-open li',function(e){if($(e.target).is('li')){KB.trigger('dropdown.clicked');var element=$(this).find('a:visible');if(element.length>0){element[0].click()}}})};Kanboard.Dropdown.prototype.close=function(){if(document.getElementById('dropdown')!==null){KB.trigger('dropdown.beforeDestroy')} +$('.active-dropdown-menu').addClass('dropdown-menu');$('.active-dropdown-menu').removeClass('active-dropdown-menu');$("#dropdown").remove()};Kanboard.Search=function(app){this.app=app};Kanboard.Search.prototype.focus=function(){$(document).on("focus","#form-search",function(){var input=$("#form-search");if(input[0].setSelectionRange){var len=input.val().length*2;input[0].setSelectionRange(len,len)}})};Kanboard.Search.prototype.listen=function(){$(document).on("click",".filter-helper",function(e){e.preventDefault();var filter=$(this).data("filter");var appendFilter=$(this).data("append-filter");var uniqueFilter=$(this).data("unique-filter");var input=$("#form-search");if(uniqueFilter){var attribute=uniqueFilter.substr(0,uniqueFilter.indexOf(':'));filter=input.val().replace(new RegExp('('+attribute+':[#a-z0-9]+)','g'),'');filter=filter.replace(new RegExp('('+attribute+':"(.+)")','g'),'');filter=filter.trim();filter+=' '+uniqueFilter}else if(appendFilter){filter=input.val()+" "+appendFilter} +input.val(filter);$("form.search").submit()})};Kanboard.Swimlane=function(app){this.app=app};Kanboard.Swimlane.prototype.execute=function(){if($(".swimlanes-table").length){this.dragAndDrop()} +if($("#board").length){this.expandSingleSwimlane()}};Kanboard.Swimlane.prototype.listen=function(){var self=this;$(document).on('click',".board-swimlane-toggle",function(e){e.preventDefault();var swimlaneId=$(this).data('swimlane-id');if(self.isCollapsed(swimlaneId)){self.expand(swimlaneId)}else{self.collapse(swimlaneId)}})};Kanboard.Swimlane.prototype.onBoardRendered=function(){var swimlaneIds=this.getAllCollapsed();for(var i=0;i-1){swimlaneIds.splice(index,1)} +localStorage.setItem(this.getStorageKey(),JSON.stringify(swimlaneIds));$('.board-swimlane-columns-'+swimlaneId).css('display','table-row');$('.board-swimlane-tasks-'+swimlaneId).css('display','table-row');$('.hide-icon-swimlane-'+swimlaneId).css('display','inline');$('.show-icon-swimlane-'+swimlaneId).css('display','none')};Kanboard.Swimlane.prototype.collapse=function(swimlaneId){var swimlaneIds=this.getAllCollapsed();if(swimlaneIds.indexOf(swimlaneId)<0){swimlaneIds.push(swimlaneId);localStorage.setItem(this.getStorageKey(),JSON.stringify(swimlaneIds))} +$('.board-swimlane-columns-'+swimlaneId+':not(:first-child)').css('display','none');$('.board-swimlane-tasks-'+swimlaneId).css('display','none');$('.hide-icon-swimlane-'+swimlaneId).css('display','none');$('.show-icon-swimlane-'+swimlaneId).css('display','inline')};Kanboard.Swimlane.prototype.isCollapsed=function(swimlaneId){return this.getAllCollapsed().indexOf(swimlaneId)>-1};Kanboard.Swimlane.prototype.getAllCollapsed=function(){return JSON.parse(localStorage.getItem(this.getStorageKey()))||[]};Kanboard.Swimlane.prototype.dragAndDrop=function(){var self=this;$(".draggable-row-handle").mouseenter(function(){$(this).parent().parent().addClass("draggable-item-hover")}).mouseleave(function(){$(this).parent().parent().removeClass("draggable-item-hover")});$(".swimlanes-table tbody").sortable({forcePlaceholderSize:!0,handle:"td:first i",helper:function(e,ui){ui.children().each(function(){$(this).width($(this).width())});return ui},stop:function(event,ui){var swimlane=ui.item;swimlane.removeClass("draggable-item-selected");self.savePosition(swimlane.data("swimlane-id"),swimlane.index()+1)},start:function(event,ui){ui.item.addClass("draggable-item-selected")}}).disableSelection()};Kanboard.Swimlane.prototype.savePosition=function(swimlaneId,position){var url=$(".swimlanes-table").data("save-position-url");var self=this;this.app.showLoadingIcon();$.ajax({cache:!1,url:url,contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify({"swimlane_id":swimlaneId,"position":position}),complete:function(){self.app.hideLoadingIcon()}})};Kanboard.Swimlane.prototype.expandSingleSwimlane=function(){if($("tr.board-swimlane").length==1){localStorage.removeItem(this.getStorageKey())}} +Kanboard.Task=function(app){this.app=app};Kanboard.Task.prototype.onPopoverOpened=function(){var self=this;self.renderColorPicker();$(document).on("click",".assign-me",function(e){var currentId=$(this).data("current-id");var dropdownId="#modal-box #"+$(this).data("target-id");e.preventDefault();if($(dropdownId+' option[value='+currentId+']').length){$(dropdownId).val(currentId)}})};Kanboard.Task.prototype.renderColorPicker=function(){function renderColorOption(color){return $('
'+'
'+'
'+color.text+'
'+'
')} +$(".color-picker").select2({minimumResultsForSearch:Infinity,templateResult:renderColorOption,templateSelection:renderColorOption})};Kanboard.BoardDragAndDrop=function(app){this.app=app;this.savingInProgress=!1};Kanboard.BoardDragAndDrop.prototype.execute=function(){if(this.app.hasId("board")){this.executeListeners();this.dragAndDrop()}};Kanboard.BoardDragAndDrop.prototype.dragAndDrop=function(){var self=this;var dropzone=$(".board-task-list");var params={forcePlaceholderSize:!0,tolerance:"pointer",connectWith:".sortable-column:visible",placeholder:"draggable-placeholder",items:".draggable-item",stop:function(event,ui){var task=ui.item;var taskId=task.attr('data-task-id');var taskPosition=task.attr('data-position');var taskColumnId=task.attr('data-column-id');var taskSwimlaneId=task.attr('data-swimlane-id');var newColumnId=task.parent().attr("data-column-id");var newSwimlaneId=task.parent().attr('data-swimlane-id');var newPosition=task.index()+1;task.removeClass("draggable-item-selected");if(newColumnId!=taskColumnId||newSwimlaneId!=taskSwimlaneId||newPosition!=taskPosition){self.changeTaskState(taskId);self.save(taskId,taskColumnId,newColumnId,newPosition,newSwimlaneId)}},start:function(event,ui){ui.item.addClass("draggable-item-selected");ui.placeholder.height(ui.item.height())}};if(isMobile.any){$(".task-board-sort-handle").css("display","inline");params.handle=".task-board-sort-handle"} +dropzone.each(function(){$(this).css("min-height",$(this).parent().height())});dropzone.sortable(params)};Kanboard.BoardDragAndDrop.prototype.changeTaskState=function(taskId){var task=$("div[data-task-id="+taskId+"]");task.addClass('task-board-saving-state');task.find('.task-board-saving-icon').show()};Kanboard.BoardDragAndDrop.prototype.save=function(taskId,srcColumnId,dstColumnId,position,swimlaneId){var self=this;self.app.showLoadingIcon();self.savingInProgress=!0;$.ajax({cache:!1,url:$("#board").data("save-url"),contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify({"task_id":taskId,"src_column_id":srcColumnId,"dst_column_id":dstColumnId,"swimlane_id":swimlaneId,"position":position}),success:function(data){self.refresh(data);self.savingInProgress=!1},error:function(){self.app.hideLoadingIcon();self.savingInProgress=!1},statusCode:{403:function(data){window.alert(data.responseJSON.message);document.location.reload(!0)}}})};Kanboard.BoardDragAndDrop.prototype.refresh=function(data){$("#board-container").replaceWith(data);this.app.hideLoadingIcon();this.executeListeners();this.dragAndDrop()};Kanboard.BoardDragAndDrop.prototype.executeListeners=function(){for(var className in this.app.controllers){var controller=this.app.get(className);if(typeof controller.onBoardRendered==="function"){controller.onBoardRendered()}}};var _KB=null;jQuery(document).ready(function(){_KB=new Kanboard.App();_KB.execute()}) \ No newline at end of file diff --git a/assets/js/vendor.min.js b/assets/js/vendor.min.js new file mode 100644 index 0000000..c4a4057 --- /dev/null +++ b/assets/js/vendor.min.js @@ -0,0 +1,3329 @@ +/*! jQuery v3.6.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,y=n.hasOwnProperty,a=y.toString,l=a.call(Object),v={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&v(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!y||!y.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ve(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ye(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ve(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],y=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||y.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||y.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||y.push(".#.+[+~]"),e.querySelectorAll("\\\f"),y.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),y=y.length&&new RegExp(y.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),v=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&v(p,e)?-1:t==C||t.ownerDocument==p&&v(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!y||!y.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),v.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",v.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",v.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),v.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(v.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return B(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=_e(v.pixelPosition,function(e,t){if(t)return t=Be(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return B(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0",options:{classes:{},disabled:!1,create:null},_createWidget:function(t,e){e=V(e||this.defaultElement||this)[0],this.element=V(e),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=V(),this.hoverable=V(),this.focusable=V(),this.classesElementLookup={},e!==this&&(V.data(e,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===e&&this.destroy()}}),this.document=V(e.style?e.ownerDocument:e.document||e),this.window=V(this.document[0].defaultView||this.document[0].parentWindow)),this.options=V.widget.extend({},this.options,this._getCreateOptions(),t),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:V.noop,_create:V.noop,_init:V.noop,destroy:function(){var i=this;this._destroy(),V.each(this.classesElementLookup,function(t,e){i._removeClass(e,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:V.noop,widget:function(){return this.element},option:function(t,e){var i,s,n,o=t;if(0===arguments.length)return V.widget.extend({},this.options);if("string"==typeof t)if(o={},t=(i=t.split(".")).shift(),i.length){for(s=o[t]=V.widget.extend({},this.options[t]),n=0;n
"),i=e.children()[0];return V("body").append(e),t=i.offsetWidth,e.css("overflow","scroll"),t===(i=i.offsetWidth)&&(i=e[0].clientWidth),e.remove(),s=t-i},getScrollInfo:function(t){var e=t.isWindow||t.isDocument?"":t.element.css("overflow-x"),i=t.isWindow||t.isDocument?"":t.element.css("overflow-y"),e="scroll"===e||"auto"===e&&t.widthx(k(s),k(n))?o.important="horizontal":o.important="vertical",u.using.call(this,t,o)}),a.offset(V.extend(h,{using:t}))})},V.ui.position={fit:{left:function(t,e){var i=e.within,s=i.isWindow?i.scrollLeft:i.offset.left,n=i.width,o=t.left-e.collisionPosition.marginLeft,a=s-o,r=o+e.collisionWidth-n-s;e.collisionWidth>n?0n?0")[0],w=d.each;function P(t){return null==t?t+"":"object"==typeof t?p[e.call(t)]||"object":typeof t}function M(t,e,i){var s=v[e.type]||{};return null==t?i||!e.def?null:e.def:(t=s.floor?~~t:parseFloat(t),isNaN(t)?e.def:s.mod?(t+s.mod)%s.mod:Math.min(s.max,Math.max(0,t)))}function S(s){var n=m(),o=n._rgba=[];return s=s.toLowerCase(),w(g,function(t,e){var i=e.re.exec(s),i=i&&e.parse(i),e=e.space||"rgba";if(i)return i=n[e](i),n[_[e].cache]=i[_[e].cache],o=n._rgba=i._rgba,!1}),o.length?("0,0,0,0"===o.join()&&d.extend(o,B.transparent),n):B[s]}function H(t,e,i){return 6*(i=(i+1)%1)<1?t+(e-t)*i*6:2*i<1?e:3*i<2?t+(e-t)*(2/3-i)*6:t}y.style.cssText="background-color:rgba(1,1,1,.5)",b.rgba=-1o.mod/2?s+=o.mod:s-n>o.mod/2&&(s-=o.mod)),l[i]=M((n-s)*a+s,e)))}),this[e](l)},blend:function(t){if(1===this._rgba[3])return this;var e=this._rgba.slice(),i=e.pop(),s=m(t)._rgba;return m(d.map(e,function(t,e){return(1-i)*s[e]+i*t}))},toRgbaString:function(){var t="rgba(",e=d.map(this._rgba,function(t,e){return null!=t?t:2").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),e={width:i.width(),height:i.height()},n=document.activeElement;try{n.id}catch(t){n=document.body}return i.wrap(t),i[0]!==n&&!V.contains(i[0],n)||V(n).trigger("focus"),t=i.parent(),"static"===i.css("position")?(t.css({position:"relative"}),i.css({position:"relative"})):(V.extend(s,{position:i.css("position"),zIndex:i.css("z-index")}),V.each(["top","left","bottom","right"],function(t,e){s[e]=i.css(e),isNaN(parseInt(s[e],10))&&(s[e]="auto")}),i.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),i.css(e),t.css(s).show()},removeWrapper:function(t){var e=document.activeElement;return t.parent().is(".ui-effects-wrapper")&&(t.parent().replaceWith(t),t[0]!==e&&!V.contains(t[0],e)||V(e).trigger("focus")),t}}),V.extend(V.effects,{version:"1.13.2",define:function(t,e,i){return i||(i=e,e="effect"),V.effects.effect[t]=i,V.effects.effect[t].mode=e,i},scaledDimensions:function(t,e,i){if(0===e)return{height:0,width:0,outerHeight:0,outerWidth:0};var s="horizontal"!==i?(e||100)/100:1,e="vertical"!==i?(e||100)/100:1;return{height:t.height()*e,width:t.width()*s,outerHeight:t.outerHeight()*e,outerWidth:t.outerWidth()*s}},clipToBox:function(t){return{width:t.clip.right-t.clip.left,height:t.clip.bottom-t.clip.top,left:t.clip.left,top:t.clip.top}},unshift:function(t,e,i){var s=t.queue();1").insertAfter(t).css({display:/^(inline|ruby)/.test(t.css("display"))?"inline-block":"block",visibility:"hidden",marginTop:t.css("marginTop"),marginBottom:t.css("marginBottom"),marginLeft:t.css("marginLeft"),marginRight:t.css("marginRight"),float:t.css("float")}).outerWidth(t.outerWidth()).outerHeight(t.outerHeight()).addClass("ui-effects-placeholder"),t.data(j+"placeholder",e)),t.css({position:i,left:s.left,top:s.top}),e},removePlaceholder:function(t){var e=j+"placeholder",i=t.data(e);i&&(i.remove(),t.removeData(e))},cleanUp:function(t){V.effects.restoreStyle(t),V.effects.removePlaceholder(t)},setTransition:function(s,t,n,o){return o=o||{},V.each(t,function(t,e){var i=s.cssUnit(e);0");l.appendTo("body").addClass(t.className).css({top:s.top-a,left:s.left-r,height:i.innerHeight(),width:i.innerWidth(),position:n?"fixed":"absolute"}).animate(o,t.duration,t.easing,function(){l.remove(),"function"==typeof e&&e()})}}),V.fx.step.clip=function(t){t.clipInit||(t.start=V(t.elem).cssClip(),"string"==typeof t.end&&(t.end=G(t.end,t.elem)),t.clipInit=!0),V(t.elem).cssClip({top:t.pos*(t.end.top-t.start.top)+t.start.top,right:t.pos*(t.end.right-t.start.right)+t.start.right,bottom:t.pos*(t.end.bottom-t.start.bottom)+t.start.bottom,left:t.pos*(t.end.left-t.start.left)+t.start.left})},Y={},V.each(["Quad","Cubic","Quart","Quint","Expo"],function(e,t){Y[t]=function(t){return Math.pow(t,e+2)}}),V.extend(Y,{Sine:function(t){return 1-Math.cos(t*Math.PI/2)},Circ:function(t){return 1-Math.sqrt(1-t*t)},Elastic:function(t){return 0===t||1===t?t:-Math.pow(2,8*(t-1))*Math.sin((80*(t-1)-7.5)*Math.PI/15)},Back:function(t){return t*t*(3*t-2)},Bounce:function(t){for(var e,i=4;t<((e=Math.pow(2,--i))-1)/11;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*e-2)/22-t,2)}}),V.each(Y,function(t,e){V.easing["easeIn"+t]=e,V.easing["easeOut"+t]=function(t){return 1-e(1-t)},V.easing["easeInOut"+t]=function(t){return t<.5?e(2*t)/2:1-e(-2*t+2)/2}});y=V.effects,V.effects.define("blind","hide",function(t,e){var i={up:["bottom","top"],vertical:["bottom","top"],down:["top","bottom"],left:["right","left"],horizontal:["right","left"],right:["left","right"]},s=V(this),n=t.direction||"up",o=s.cssClip(),a={clip:V.extend({},o)},r=V.effects.createPlaceholder(s);a.clip[i[n][0]]=a.clip[i[n][1]],"show"===t.mode&&(s.cssClip(a.clip),r&&r.css(V.effects.clipToBox(a)),a.clip=o),r&&r.animate(V.effects.clipToBox(a),t.duration,t.easing),s.animate(a,{queue:!1,duration:t.duration,easing:t.easing,complete:e})}),V.effects.define("bounce",function(t,e){var i,s,n=V(this),o=t.mode,a="hide"===o,r="show"===o,l=t.direction||"up",h=t.distance,c=t.times||5,o=2*c+(r||a?1:0),u=t.duration/o,d=t.easing,p="up"===l||"down"===l?"top":"left",f="up"===l||"left"===l,g=0,t=n.queue().length;for(V.effects.createPlaceholder(n),l=n.css(p),h=h||n["top"==p?"outerHeight":"outerWidth"]()/3,r&&((s={opacity:1})[p]=l,n.css("opacity",0).css(p,f?2*-h:2*h).animate(s,u,d)),a&&(h/=Math.pow(2,c-1)),(s={})[p]=l;g").css({position:"absolute",visibility:"visible",left:-s*p,top:-i*f}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:p,height:f,left:n+(u?a*p:0),top:o+(u?r*f:0),opacity:u?0:1}).animate({left:n+(u?0:a*p),top:o+(u?0:r*f),opacity:u?1:0},t.duration||500,t.easing,m)}),V.effects.define("fade","toggle",function(t,e){var i="show"===t.mode;V(this).css("opacity",i?0:1).animate({opacity:i?1:0},{queue:!1,duration:t.duration,easing:t.easing,complete:e})}),V.effects.define("fold","hide",function(e,t){var i=V(this),s=e.mode,n="show"===s,o="hide"===s,a=e.size||15,r=/([0-9]+)%/.exec(a),l=!!e.horizFirst?["right","bottom"]:["bottom","right"],h=e.duration/2,c=V.effects.createPlaceholder(i),u=i.cssClip(),d={clip:V.extend({},u)},p={clip:V.extend({},u)},f=[u[l[0]],u[l[1]]],s=i.queue().length;r&&(a=parseInt(r[1],10)/100*f[o?0:1]),d.clip[l[0]]=a,p.clip[l[0]]=a,p.clip[l[1]]=0,n&&(i.cssClip(p.clip),c&&c.css(V.effects.clipToBox(p)),p.clip=u),i.queue(function(t){c&&c.animate(V.effects.clipToBox(d),h,e.easing).animate(V.effects.clipToBox(p),h,e.easing),t()}).animate(d,h,e.easing).animate(p,h,e.easing).queue(t),V.effects.unshift(i,s,4)}),V.effects.define("highlight","show",function(t,e){var i=V(this),s={backgroundColor:i.css("backgroundColor")};"hide"===t.mode&&(s.opacity=0),V.effects.saveStyle(i),i.css({backgroundImage:"none",backgroundColor:t.color||"#ffff99"}).animate(s,{queue:!1,duration:t.duration,easing:t.easing,complete:e})}),V.effects.define("size",function(s,e){var n,i=V(this),t=["fontSize"],o=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],a=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],r=s.mode,l="effect"!==r,h=s.scale||"both",c=s.origin||["middle","center"],u=i.css("position"),d=i.position(),p=V.effects.scaledDimensions(i),f=s.from||p,g=s.to||V.effects.scaledDimensions(i,0);V.effects.createPlaceholder(i),"show"===r&&(r=f,f=g,g=r),n={from:{y:f.height/p.height,x:f.width/p.width},to:{y:g.height/p.height,x:g.width/p.width}},"box"!==h&&"both"!==h||(n.from.y!==n.to.y&&(f=V.effects.setTransition(i,o,n.from.y,f),g=V.effects.setTransition(i,o,n.to.y,g)),n.from.x!==n.to.x&&(f=V.effects.setTransition(i,a,n.from.x,f),g=V.effects.setTransition(i,a,n.to.x,g))),"content"!==h&&"both"!==h||n.from.y!==n.to.y&&(f=V.effects.setTransition(i,t,n.from.y,f),g=V.effects.setTransition(i,t,n.to.y,g)),c&&(c=V.effects.getBaseline(c,p),f.top=(p.outerHeight-f.outerHeight)*c.y+d.top,f.left=(p.outerWidth-f.outerWidth)*c.x+d.left,g.top=(p.outerHeight-g.outerHeight)*c.y+d.top,g.left=(p.outerWidth-g.outerWidth)*c.x+d.left),delete f.outerHeight,delete f.outerWidth,i.css(f),"content"!==h&&"both"!==h||(o=o.concat(["marginTop","marginBottom"]).concat(t),a=a.concat(["marginLeft","marginRight"]),i.find("*[width]").each(function(){var t=V(this),e=V.effects.scaledDimensions(t),i={height:e.height*n.from.y,width:e.width*n.from.x,outerHeight:e.outerHeight*n.from.y,outerWidth:e.outerWidth*n.from.x},e={height:e.height*n.to.y,width:e.width*n.to.x,outerHeight:e.height*n.to.y,outerWidth:e.width*n.to.x};n.from.y!==n.to.y&&(i=V.effects.setTransition(t,o,n.from.y,i),e=V.effects.setTransition(t,o,n.to.y,e)),n.from.x!==n.to.x&&(i=V.effects.setTransition(t,a,n.from.x,i),e=V.effects.setTransition(t,a,n.to.x,e)),l&&V.effects.saveStyle(t),t.css(i),t.animate(e,s.duration,s.easing,function(){l&&V.effects.restoreStyle(t)})})),i.animate(g,{queue:!1,duration:s.duration,easing:s.easing,complete:function(){var t=i.offset();0===g.opacity&&i.css("opacity",f.opacity),l||(i.css("position","static"===u?"relative":u).offset(t),V.effects.saveStyle(i)),e()}})}),V.effects.define("scale",function(t,e){var i=V(this),s=t.mode,s=parseInt(t.percent,10)||(0===parseInt(t.percent,10)||"effect"!==s?0:100),s=V.extend(!0,{from:V.effects.scaledDimensions(i),to:V.effects.scaledDimensions(i,s,t.direction||"both"),origin:t.origin||["middle","center"]},t);t.fade&&(s.from.opacity=1,s.to.opacity=0),V.effects.effect.size.call(this,s,e)}),V.effects.define("puff","hide",function(t,e){t=V.extend(!0,{},t,{fade:!0,percent:parseInt(t.percent,10)||150});V.effects.effect.scale.call(this,t,e)}),V.effects.define("pulsate","show",function(t,e){var i=V(this),s=t.mode,n="show"===s,o=2*(t.times||5)+(n||"hide"===s?1:0),a=t.duration/o,r=0,l=1,s=i.queue().length;for(!n&&i.is(":visible")||(i.css("opacity",0).show(),r=1);l li > :first-child").add(t.find("> :not(li)").even())},heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},hideProps:{borderTopWidth:"hide",borderBottomWidth:"hide",paddingTop:"hide",paddingBottom:"hide",height:"hide"},showProps:{borderTopWidth:"show",borderBottomWidth:"show",paddingTop:"show",paddingBottom:"show",height:"show"},_create:function(){var t=this.options;this.prevShow=this.prevHide=V(),this._addClass("ui-accordion","ui-widget ui-helper-reset"),this.element.attr("role","tablist"),t.collapsible||!1!==t.active&&null!=t.active||(t.active=0),this._processPanels(),t.active<0&&(t.active+=this.headers.length),this._refresh()},_getCreateEventData:function(){return{header:this.active,panel:this.active.length?this.active.next():V()}},_createIcons:function(){var t,e=this.options.icons;e&&(t=V(""),this._addClass(t,"ui-accordion-header-icon","ui-icon "+e.header),t.prependTo(this.headers),t=this.active.children(".ui-accordion-header-icon"),this._removeClass(t,e.header)._addClass(t,null,e.activeHeader)._addClass(this.headers,"ui-accordion-icons"))},_destroyIcons:function(){this._removeClass(this.headers,"ui-accordion-icons"),this.headers.children(".ui-accordion-header-icon").remove()},_destroy:function(){var t;this.element.removeAttr("role"),this.headers.removeAttr("role aria-expanded aria-selected aria-controls tabIndex").removeUniqueId(),this._destroyIcons(),t=this.headers.next().css("display","").removeAttr("role aria-hidden aria-labelledby").removeUniqueId(),"content"!==this.options.heightStyle&&t.css("height","")},_setOption:function(t,e){"active"!==t?("event"===t&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(e)),this._super(t,e),"collapsible"!==t||e||!1!==this.options.active||this._activate(0),"icons"===t&&(this._destroyIcons(),e&&this._createIcons())):this._activate(e)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t),this._toggleClass(null,"ui-state-disabled",!!t),this._toggleClass(this.headers.add(this.headers.next()),null,"ui-state-disabled",!!t)},_keydown:function(t){if(!t.altKey&&!t.ctrlKey){var e=V.ui.keyCode,i=this.headers.length,s=this.headers.index(t.target),n=!1;switch(t.keyCode){case e.RIGHT:case e.DOWN:n=this.headers[(s+1)%i];break;case e.LEFT:case e.UP:n=this.headers[(s-1+i)%i];break;case e.SPACE:case e.ENTER:this._eventHandler(t);break;case e.HOME:n=this.headers[0];break;case e.END:n=this.headers[i-1]}n&&(V(t.target).attr("tabIndex",-1),V(n).attr("tabIndex",0),V(n).trigger("focus"),t.preventDefault())}},_panelKeyDown:function(t){t.keyCode===V.ui.keyCode.UP&&t.ctrlKey&&V(t.currentTarget).prev().trigger("focus")},refresh:function(){var t=this.options;this._processPanels(),!1===t.active&&!0===t.collapsible||!this.headers.length?(t.active=!1,this.active=V()):!1===t.active?this._activate(0):this.active.length&&!V.contains(this.element[0],this.active[0])?this.headers.length===this.headers.find(".ui-state-disabled").length?(t.active=!1,this.active=V()):this._activate(Math.max(0,t.active-1)):t.active=this.headers.index(this.active),this._destroyIcons(),this._refresh()},_processPanels:function(){var t=this.headers,e=this.panels;"function"==typeof this.options.header?this.headers=this.options.header(this.element):this.headers=this.element.find(this.options.header),this._addClass(this.headers,"ui-accordion-header ui-accordion-header-collapsed","ui-state-default"),this.panels=this.headers.next().filter(":not(.ui-accordion-content-active)").hide(),this._addClass(this.panels,"ui-accordion-content","ui-helper-reset ui-widget-content"),e&&(this._off(t.not(this.headers)),this._off(e.not(this.panels)))},_refresh:function(){var i,t=this.options,e=t.heightStyle,s=this.element.parent();this.active=this._findActive(t.active),this._addClass(this.active,"ui-accordion-header-active","ui-state-active")._removeClass(this.active,"ui-accordion-header-collapsed"),this._addClass(this.active.next(),"ui-accordion-content-active"),this.active.next().show(),this.headers.attr("role","tab").each(function(){var t=V(this),e=t.uniqueId().attr("id"),i=t.next(),s=i.uniqueId().attr("id");t.attr("aria-controls",s),i.attr("aria-labelledby",e)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}).next().attr({"aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}).next().attr({"aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._createIcons(),this._setupEvents(t.event),"fill"===e?(i=s.height(),this.element.siblings(":visible").each(function(){var t=V(this),e=t.css("position");"absolute"!==e&&"fixed"!==e&&(i-=t.outerHeight(!0))}),this.headers.each(function(){i-=V(this).outerHeight(!0)}),this.headers.next().each(function(){V(this).height(Math.max(0,i-V(this).innerHeight()+V(this).height()))}).css("overflow","auto")):"auto"===e&&(i=0,this.headers.next().each(function(){var t=V(this).is(":visible");t||V(this).show(),i=Math.max(i,V(this).css("height","").height()),t||V(this).hide()}).height(i))},_activate:function(t){t=this._findActive(t)[0];t!==this.active[0]&&(t=t||this.active[0],this._eventHandler({target:t,currentTarget:t,preventDefault:V.noop}))},_findActive:function(t){return"number"==typeof t?this.headers.eq(t):V()},_setupEvents:function(t){var i={keydown:"_keydown"};t&&V.each(t.split(" "),function(t,e){i[e]="_eventHandler"}),this._off(this.headers.add(this.headers.next())),this._on(this.headers,i),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._hoverable(this.headers),this._focusable(this.headers)},_eventHandler:function(t){var e=this.options,i=this.active,s=V(t.currentTarget),n=s[0]===i[0],o=n&&e.collapsible,a=o?V():s.next(),r=i.next(),a={oldHeader:i,oldPanel:r,newHeader:o?V():s,newPanel:a};t.preventDefault(),n&&!e.collapsible||!1===this._trigger("beforeActivate",t,a)||(e.active=!o&&this.headers.index(s),this.active=n?V():s,this._toggle(a),this._removeClass(i,"ui-accordion-header-active","ui-state-active"),e.icons&&(i=i.children(".ui-accordion-header-icon"),this._removeClass(i,null,e.icons.activeHeader)._addClass(i,null,e.icons.header)),n||(this._removeClass(s,"ui-accordion-header-collapsed")._addClass(s,"ui-accordion-header-active","ui-state-active"),e.icons&&(n=s.children(".ui-accordion-header-icon"),this._removeClass(n,null,e.icons.header)._addClass(n,null,e.icons.activeHeader)),this._addClass(s.next(),"ui-accordion-content-active")))},_toggle:function(t){var e=t.newPanel,i=this.prevShow.length?this.prevShow:t.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=e,this.prevHide=i,this.options.animate?this._animate(e,i,t):(i.hide(),e.show(),this._toggleComplete(t)),i.attr({"aria-hidden":"true"}),i.prev().attr({"aria-selected":"false","aria-expanded":"false"}),e.length&&i.length?i.prev().attr({tabIndex:-1,"aria-expanded":"false"}):e.length&&this.headers.filter(function(){return 0===parseInt(V(this).attr("tabIndex"),10)}).attr("tabIndex",-1),e.attr("aria-hidden","false").prev().attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_animate:function(t,i,e){var s,n,o,a=this,r=0,l=t.css("box-sizing"),h=t.length&&(!i.length||t.index()",delay:300,options:{icons:{submenu:"ui-icon-caret-1-e"},items:"> *",menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.lastMousePosition={x:null,y:null},this.element.uniqueId().attr({role:this.options.role,tabIndex:0}),this._addClass("ui-menu","ui-widget ui-widget-content"),this._on({"mousedown .ui-menu-item":function(t){t.preventDefault(),this._activateItem(t)},"click .ui-menu-item":function(t){var e=V(t.target),i=V(V.ui.safeActiveElement(this.document[0]));!this.mouseHandled&&e.not(".ui-state-disabled").length&&(this.select(t),t.isPropagationStopped()||(this.mouseHandled=!0),e.has(".ui-menu").length?this.expand(t):!this.element.is(":focus")&&i.closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":"_activateItem","mousemove .ui-menu-item":"_activateItem",mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(t,e){var i=this.active||this._menuItems().first();e||this.focus(t,i)},blur:function(t){this._delay(function(){V.contains(this.element[0],V.ui.safeActiveElement(this.document[0]))||this.collapseAll(t)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(t){this._closeOnDocumentClick(t)&&this.collapseAll(t,!0),this.mouseHandled=!1}})},_activateItem:function(t){var e,i;this.previousFilter||t.clientX===this.lastMousePosition.x&&t.clientY===this.lastMousePosition.y||(this.lastMousePosition={x:t.clientX,y:t.clientY},e=V(t.target).closest(".ui-menu-item"),i=V(t.currentTarget),e[0]===i[0]&&(i.is(".ui-state-active")||(this._removeClass(i.siblings().children(".ui-state-active"),null,"ui-state-active"),this.focus(t,i))))},_destroy:function(){var t=this.element.find(".ui-menu-item").removeAttr("role aria-disabled").children(".ui-menu-item-wrapper").removeUniqueId().removeAttr("tabIndex role aria-haspopup");this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeAttr("role aria-labelledby aria-expanded aria-hidden aria-disabled tabIndex").removeUniqueId().show(),t.children().each(function(){var t=V(this);t.data("ui-menu-submenu-caret")&&t.remove()})},_keydown:function(t){var e,i,s,n=!0;switch(t.keyCode){case V.ui.keyCode.PAGE_UP:this.previousPage(t);break;case V.ui.keyCode.PAGE_DOWN:this.nextPage(t);break;case V.ui.keyCode.HOME:this._move("first","first",t);break;case V.ui.keyCode.END:this._move("last","last",t);break;case V.ui.keyCode.UP:this.previous(t);break;case V.ui.keyCode.DOWN:this.next(t);break;case V.ui.keyCode.LEFT:this.collapse(t);break;case V.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(t);break;case V.ui.keyCode.ENTER:case V.ui.keyCode.SPACE:this._activate(t);break;case V.ui.keyCode.ESCAPE:this.collapse(t);break;default:e=this.previousFilter||"",s=n=!1,i=96<=t.keyCode&&t.keyCode<=105?(t.keyCode-96).toString():String.fromCharCode(t.keyCode),clearTimeout(this.filterTimer),i===e?s=!0:i=e+i,e=this._filterMenuItems(i),(e=s&&-1!==e.index(this.active.next())?this.active.nextAll(".ui-menu-item"):e).length||(i=String.fromCharCode(t.keyCode),e=this._filterMenuItems(i)),e.length?(this.focus(t,e),this.previousFilter=i,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter}n&&t.preventDefault()},_activate:function(t){this.active&&!this.active.is(".ui-state-disabled")&&(this.active.children("[aria-haspopup='true']").length?this.expand(t):this.select(t))},refresh:function(){var t,e,s=this,n=this.options.icons.submenu,i=this.element.find(this.options.menus);this._toggleClass("ui-menu-icons",null,!!this.element.find(".ui-icon").length),e=i.filter(":not(.ui-menu)").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var t=V(this),e=t.prev(),i=V("").data("ui-menu-submenu-caret",!0);s._addClass(i,"ui-menu-icon","ui-icon "+n),e.attr("aria-haspopup","true").prepend(i),t.attr("aria-labelledby",e.attr("id"))}),this._addClass(e,"ui-menu","ui-widget ui-widget-content ui-front"),(t=i.add(this.element).find(this.options.items)).not(".ui-menu-item").each(function(){var t=V(this);s._isDivider(t)&&s._addClass(t,"ui-menu-divider","ui-widget-content")}),i=(e=t.not(".ui-menu-item, .ui-menu-divider")).children().not(".ui-menu").uniqueId().attr({tabIndex:-1,role:this._itemRole()}),this._addClass(e,"ui-menu-item")._addClass(i,"ui-menu-item-wrapper"),t.filter(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!V.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(t,e){var i;"icons"===t&&(i=this.element.find(".ui-menu-icon"),this._removeClass(i,null,this.options.icons.submenu)._addClass(i,null,e.submenu)),this._super(t,e)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",String(t)),this._toggleClass(null,"ui-state-disabled",!!t)},focus:function(t,e){var i;this.blur(t,t&&"focus"===t.type),this._scrollIntoView(e),this.active=e.first(),i=this.active.children(".ui-menu-item-wrapper"),this._addClass(i,null,"ui-state-active"),this.options.role&&this.element.attr("aria-activedescendant",i.attr("id")),i=this.active.parent().closest(".ui-menu-item").children(".ui-menu-item-wrapper"),this._addClass(i,null,"ui-state-active"),t&&"keydown"===t.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),(i=e.children(".ui-menu")).length&&t&&/^mouse/.test(t.type)&&this._startOpening(i),this.activeMenu=e.parent(),this._trigger("focus",t,{item:e})},_scrollIntoView:function(t){var e,i,s;this._hasScroll()&&(i=parseFloat(V.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(V.css(this.activeMenu[0],"paddingTop"))||0,e=t.offset().top-this.activeMenu.offset().top-i-s,i=this.activeMenu.scrollTop(),s=this.activeMenu.height(),t=t.outerHeight(),e<0?this.activeMenu.scrollTop(i+e):s",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,liveRegionTimer:null,_create:function(){var i,s,n,t=this.element[0].nodeName.toLowerCase(),e="textarea"===t,t="input"===t;this.isMultiLine=e||!t&&this._isContentEditable(this.element),this.valueMethod=this.element[e||t?"val":"text"],this.isNewMenu=!0,this._addClass("ui-autocomplete-input"),this.element.attr("autocomplete","off"),this._on(this.element,{keydown:function(t){if(this.element.prop("readOnly"))s=n=i=!0;else{s=n=i=!1;var e=V.ui.keyCode;switch(t.keyCode){case e.PAGE_UP:i=!0,this._move("previousPage",t);break;case e.PAGE_DOWN:i=!0,this._move("nextPage",t);break;case e.UP:i=!0,this._keyEvent("previous",t);break;case e.DOWN:i=!0,this._keyEvent("next",t);break;case e.ENTER:this.menu.active&&(i=!0,t.preventDefault(),this.menu.select(t));break;case e.TAB:this.menu.active&&this.menu.select(t);break;case e.ESCAPE:this.menu.element.is(":visible")&&(this.isMultiLine||this._value(this.term),this.close(t),t.preventDefault());break;default:s=!0,this._searchTimeout(t)}}},keypress:function(t){if(i)return i=!1,void(this.isMultiLine&&!this.menu.element.is(":visible")||t.preventDefault());if(!s){var e=V.ui.keyCode;switch(t.keyCode){case e.PAGE_UP:this._move("previousPage",t);break;case e.PAGE_DOWN:this._move("nextPage",t);break;case e.UP:this._keyEvent("previous",t);break;case e.DOWN:this._keyEvent("next",t)}}},input:function(t){if(n)return n=!1,void t.preventDefault();this._searchTimeout(t)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(t){clearTimeout(this.searching),this.close(t),this._change(t)}}),this._initSource(),this.menu=V("